gdsdiff 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gdsdiff/__init__.py +148 -0
- gdsdiff/__main__.py +7 -0
- gdsdiff/_environment.py +12 -0
- gdsdiff/_version.py +34 -0
- gdsdiff/acceptance_evidence.py +530 -0
- gdsdiff/api.py +2955 -0
- gdsdiff/backends/__init__.py +26 -0
- gdsdiff/backends/base.py +77 -0
- gdsdiff/backends/boundary_loop_ctypes.py +191 -0
- gdsdiff/backends/cpu.py +20 -0
- gdsdiff/backends/cpu_ctypes.py +255 -0
- gdsdiff/backends/cuda.py +40 -0
- gdsdiff/backends/cuda_ctypes.py +1297 -0
- gdsdiff/backends/exact.py +424 -0
- gdsdiff/backends/geometry_ctypes.py +252 -0
- gdsdiff/backends/identity.py +53 -0
- gdsdiff/backends/metal.py +198 -0
- gdsdiff/backends/metal_ctypes.py +866 -0
- gdsdiff/backends/polygon_extract_ctypes.py +233 -0
- gdsdiff/backends/structural_ctypes.py +130 -0
- gdsdiff/boundary_loops.py +616 -0
- gdsdiff/cache.py +544 -0
- gdsdiff/canonicalize.py +176 -0
- gdsdiff/cli.py +352 -0
- gdsdiff/config.py +257 -0
- gdsdiff/cuda_setup.py +174 -0
- gdsdiff/design_history.py +65 -0
- gdsdiff/diagnostics.py +42 -0
- gdsdiff/diff_gds.py +66 -0
- gdsdiff/exact_diff_markdown.py +979 -0
- gdsdiff/exact_oracle.py +649 -0
- gdsdiff/extract.py +376 -0
- gdsdiff/gds_metadata.py +43 -0
- gdsdiff/geometry.py +112 -0
- gdsdiff/grid.py +143 -0
- gdsdiff/hierarchy.py +215 -0
- gdsdiff/hierarchy_extract.py +263 -0
- gdsdiff/inventory.py +153 -0
- gdsdiff/multiprocessing_support.py +39 -0
- gdsdiff/native.py +135 -0
- gdsdiff/native_cache.py +265 -0
- gdsdiff/native_src/cpu_prefilter.cpp +349 -0
- gdsdiff/native_src/gds_boundary_loops.cpp +505 -0
- gdsdiff/native_src/gds_geometry_scan.cpp +601 -0
- gdsdiff/native_src/gds_polygon_extract.cpp +594 -0
- gdsdiff/native_src/gds_structural_scan.cpp +335 -0
- gdsdiff/native_src/gdsdiff/CMakeLists.txt +74 -0
- gdsdiff/native_src/gdsdiff/core.cpp +191 -0
- gdsdiff/native_src/gdsdiff/core.hpp +96 -0
- gdsdiff/native_src/gdsdiff/cuda_assignment.cu +304 -0
- gdsdiff/native_src/gdsdiff/cuda_assignment.cuh +33 -0
- gdsdiff/native_src/gdsdiff/cuda_candidates.cu +133 -0
- gdsdiff/native_src/gdsdiff/cuda_candidates.cuh +17 -0
- gdsdiff/native_src/gdsdiff/cuda_ctypes.cu +4528 -0
- gdsdiff/native_src/gdsdiff/cuda_memory.cu +194 -0
- gdsdiff/native_src/gdsdiff/cuda_memory.cuh +34 -0
- gdsdiff/native_src/gdsdiff/metal_ctypes.mm +2791 -0
- gdsdiff/native_src/gdsdiff/python_bindings.cpp +21 -0
- gdsdiff/native_src/gdsdiff/test_core.cpp +93 -0
- gdsdiff/native_src/gdsdiff/test_cuda_assignment.cu +109 -0
- gdsdiff/native_src/gdsdiff/test_cuda_candidates.cu +94 -0
- gdsdiff/native_src/gdsdiff/test_cuda_memory.cu +97 -0
- gdsdiff/policy.py +305 -0
- gdsdiff/polygon_components.py +860 -0
- gdsdiff/profiles.py +152 -0
- gdsdiff/py.typed +1 -0
- gdsdiff/rect_coverage.py +384 -0
- gdsdiff/report.py +354 -0
- gdsdiff/schemas/gdsdiff_report-v1.schema.json +92 -0
- gdsdiff/semantics.py +75 -0
- gdsdiff/source_edge_diff.py +1120 -0
- gdsdiff/spatial.py +71 -0
- gdsdiff/structural_guard.py +161 -0
- gdsdiff/surplus_coverage.py +255 -0
- gdsdiff/synthetic_suite.py +1643 -0
- gdsdiff/templates.py +709 -0
- gdsdiff/tiling.py +153 -0
- gdsdiff/windowed_exact.py +270 -0
- gdsdiff/windows.py +184 -0
- gdsdiff-0.1.0.dist-info/METADATA +135 -0
- gdsdiff-0.1.0.dist-info/RECORD +85 -0
- gdsdiff-0.1.0.dist-info/WHEEL +5 -0
- gdsdiff-0.1.0.dist-info/entry_points.txt +4 -0
- gdsdiff-0.1.0.dist-info/licenses/LICENSE +674 -0
- gdsdiff-0.1.0.dist-info/top_level.txt +1 -0
gdsdiff/policy.py
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
|
|
5
|
+
import gdstk
|
|
6
|
+
|
|
7
|
+
from .config import AllowedRegion, LayerSpec
|
|
8
|
+
from .exact_oracle import DiffFragment, ExactOracleResult, LayerExactDiff, format_twice_area
|
|
9
|
+
from .geometry import polygon_bbox, polygon_twice_area
|
|
10
|
+
from .grid import snap_to_grid
|
|
11
|
+
from .semantics import CompareStatus, DifferenceDirection, FailurePolicy
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class ClassifiedComponent:
|
|
16
|
+
layer: LayerSpec
|
|
17
|
+
direction: DifferenceDirection
|
|
18
|
+
classification: str
|
|
19
|
+
points: tuple[tuple[int, int], ...]
|
|
20
|
+
bbox: tuple[int, int, int, int]
|
|
21
|
+
twice_area: int
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def area_dbu2_string(self) -> str:
|
|
25
|
+
return format_twice_area(self.twice_area)
|
|
26
|
+
|
|
27
|
+
def to_json(self) -> dict[str, object]:
|
|
28
|
+
return {
|
|
29
|
+
"layer": self.layer.to_json(),
|
|
30
|
+
"direction": self.direction.value,
|
|
31
|
+
"classification": self.classification,
|
|
32
|
+
"bbox": list(self.bbox),
|
|
33
|
+
"area_dbu2": self.area_dbu2_string,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True)
|
|
38
|
+
class LayerPolicyResult:
|
|
39
|
+
layer: LayerSpec
|
|
40
|
+
expected_components: tuple[ClassifiedComponent, ...] = field(default_factory=tuple)
|
|
41
|
+
unexpected_components: tuple[ClassifiedComponent, ...] = field(default_factory=tuple)
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def expected_twice_area(self) -> int:
|
|
45
|
+
return sum(component.twice_area for component in self.expected_components)
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def unexpected_twice_area(self) -> int:
|
|
49
|
+
return sum(component.twice_area for component in self.unexpected_components)
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def components(self) -> tuple[ClassifiedComponent, ...]:
|
|
53
|
+
return tuple(sorted(self.expected_components + self.unexpected_components, key=_component_sort_key))
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass(frozen=True)
|
|
57
|
+
class PolicyClassificationResult:
|
|
58
|
+
layers: tuple[LayerPolicyResult, ...]
|
|
59
|
+
effective_policy: dict[str, object]
|
|
60
|
+
failure_policy: FailurePolicy = FailurePolicy.FAIL_ON_UNEXPECTED
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def expected_twice_area(self) -> int:
|
|
64
|
+
return sum(layer.expected_twice_area for layer in self.layers)
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def unexpected_twice_area(self) -> int:
|
|
68
|
+
return sum(layer.unexpected_twice_area for layer in self.layers)
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def status(self) -> CompareStatus:
|
|
72
|
+
if self.unexpected_twice_area:
|
|
73
|
+
return CompareStatus.UNEXPECTED_DIFFERENCES
|
|
74
|
+
if self.expected_twice_area:
|
|
75
|
+
return CompareStatus.EXPECTED_DIFFERENCES_ONLY
|
|
76
|
+
return CompareStatus.EQUIVALENT
|
|
77
|
+
|
|
78
|
+
def to_json(self) -> dict[str, object]:
|
|
79
|
+
return {
|
|
80
|
+
"status": self.status.value,
|
|
81
|
+
"failure_policy": self.failure_policy.value,
|
|
82
|
+
"expected_area_dbu2": format_twice_area(self.expected_twice_area),
|
|
83
|
+
"unexpected_area_dbu2": format_twice_area(self.unexpected_twice_area),
|
|
84
|
+
"effective_policy": self.effective_policy,
|
|
85
|
+
"layers": [
|
|
86
|
+
{
|
|
87
|
+
"layer": layer.layer.to_json(),
|
|
88
|
+
"expected_area_dbu2": format_twice_area(layer.expected_twice_area),
|
|
89
|
+
"unexpected_area_dbu2": format_twice_area(layer.unexpected_twice_area),
|
|
90
|
+
"components": [component.to_json() for component in layer.components],
|
|
91
|
+
}
|
|
92
|
+
for layer in self.layers
|
|
93
|
+
],
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def classify_exact_differences(
|
|
98
|
+
exact_result: ExactOracleResult,
|
|
99
|
+
allowed_regions: tuple[AllowedRegion, ...] | list[AllowedRegion] = (),
|
|
100
|
+
*,
|
|
101
|
+
failure_policy: FailurePolicy | str = FailurePolicy.FAIL_ON_UNEXPECTED,
|
|
102
|
+
precision: float = 1.0,
|
|
103
|
+
) -> PolicyClassificationResult:
|
|
104
|
+
failure_policy = FailurePolicy.coerce(failure_policy)
|
|
105
|
+
policy = _compile_policy(tuple(allowed_regions))
|
|
106
|
+
layer_results = []
|
|
107
|
+
for layer_diff in exact_result.layers:
|
|
108
|
+
expected: list[ClassifiedComponent] = []
|
|
109
|
+
unexpected: list[ClassifiedComponent] = []
|
|
110
|
+
_classify_fragments(
|
|
111
|
+
layer_diff.layer,
|
|
112
|
+
DifferenceDirection.REMOVAL,
|
|
113
|
+
layer_diff.old_minus_new,
|
|
114
|
+
policy,
|
|
115
|
+
expected,
|
|
116
|
+
unexpected,
|
|
117
|
+
precision=precision,
|
|
118
|
+
)
|
|
119
|
+
_classify_fragments(
|
|
120
|
+
layer_diff.layer,
|
|
121
|
+
DifferenceDirection.ADDITION,
|
|
122
|
+
layer_diff.new_minus_old,
|
|
123
|
+
policy,
|
|
124
|
+
expected,
|
|
125
|
+
unexpected,
|
|
126
|
+
precision=precision,
|
|
127
|
+
)
|
|
128
|
+
layer_results.append(
|
|
129
|
+
LayerPolicyResult(
|
|
130
|
+
layer=layer_diff.layer,
|
|
131
|
+
expected_components=tuple(sorted(expected, key=_component_sort_key)),
|
|
132
|
+
unexpected_components=tuple(sorted(unexpected, key=_component_sort_key)),
|
|
133
|
+
)
|
|
134
|
+
)
|
|
135
|
+
return PolicyClassificationResult(
|
|
136
|
+
layers=tuple(sorted(layer_results, key=lambda result: result.layer)),
|
|
137
|
+
effective_policy=_effective_policy_json(tuple(allowed_regions)),
|
|
138
|
+
failure_policy=failure_policy,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@dataclass(frozen=True)
|
|
143
|
+
class _CompiledPolicy:
|
|
144
|
+
bbox_regions: dict[tuple[LayerSpec, DifferenceDirection], tuple[AllowedRegion, ...]]
|
|
145
|
+
whole_layer: frozenset[tuple[LayerSpec, DifferenceDirection]]
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _compile_policy(allowed_regions: tuple[AllowedRegion, ...]) -> _CompiledPolicy:
|
|
149
|
+
bbox_regions: dict[tuple[LayerSpec, DifferenceDirection], list[AllowedRegion]] = {}
|
|
150
|
+
whole_layer: set[tuple[LayerSpec, DifferenceDirection]] = set()
|
|
151
|
+
for region in allowed_regions:
|
|
152
|
+
for layer in region.layers:
|
|
153
|
+
for direction in _region_directions(region.direction):
|
|
154
|
+
key = (layer, direction)
|
|
155
|
+
if region.whole_layer:
|
|
156
|
+
whole_layer.add(key)
|
|
157
|
+
else:
|
|
158
|
+
bbox_regions.setdefault(key, []).append(region)
|
|
159
|
+
return _CompiledPolicy(
|
|
160
|
+
bbox_regions={key: tuple(value) for key, value in bbox_regions.items()},
|
|
161
|
+
whole_layer=frozenset(whole_layer),
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _region_directions(direction: DifferenceDirection) -> tuple[DifferenceDirection, ...]:
|
|
166
|
+
if direction in {DifferenceDirection.BOTH, DifferenceDirection.XOR}:
|
|
167
|
+
return DifferenceDirection.ADDITION, DifferenceDirection.REMOVAL
|
|
168
|
+
if direction == DifferenceDirection.ADDITION:
|
|
169
|
+
return (DifferenceDirection.ADDITION,)
|
|
170
|
+
if direction == DifferenceDirection.REMOVAL:
|
|
171
|
+
return (DifferenceDirection.REMOVAL,)
|
|
172
|
+
raise ValueError(f"unsupported direction for allowed region: {direction}")
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _classify_fragments(
|
|
176
|
+
layer: LayerSpec,
|
|
177
|
+
direction: DifferenceDirection,
|
|
178
|
+
fragments: tuple[DiffFragment, ...],
|
|
179
|
+
policy: _CompiledPolicy,
|
|
180
|
+
expected: list[ClassifiedComponent],
|
|
181
|
+
unexpected: list[ClassifiedComponent],
|
|
182
|
+
*,
|
|
183
|
+
precision: float,
|
|
184
|
+
) -> None:
|
|
185
|
+
if not fragments:
|
|
186
|
+
return
|
|
187
|
+
key = (layer, direction)
|
|
188
|
+
if key in policy.whole_layer:
|
|
189
|
+
expected.extend(_components_from_fragments(layer, direction, "expected", fragments))
|
|
190
|
+
return
|
|
191
|
+
regions = policy.bbox_regions.get(key, ())
|
|
192
|
+
for fragment in fragments:
|
|
193
|
+
if not regions:
|
|
194
|
+
unexpected.append(_component_from_fragment(layer, direction, "unexpected", fragment))
|
|
195
|
+
continue
|
|
196
|
+
fragment_expected, fragment_unexpected = _split_fragment_by_regions(
|
|
197
|
+
fragment,
|
|
198
|
+
regions,
|
|
199
|
+
layer,
|
|
200
|
+
precision=precision,
|
|
201
|
+
)
|
|
202
|
+
expected.extend(
|
|
203
|
+
_component_from_points(layer, direction, "expected", points)
|
|
204
|
+
for points in fragment_expected
|
|
205
|
+
)
|
|
206
|
+
unexpected.extend(
|
|
207
|
+
_component_from_points(layer, direction, "unexpected", points)
|
|
208
|
+
for points in fragment_unexpected
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _split_fragment_by_regions(
|
|
213
|
+
fragment: DiffFragment,
|
|
214
|
+
regions: tuple[AllowedRegion, ...],
|
|
215
|
+
layer: LayerSpec,
|
|
216
|
+
*,
|
|
217
|
+
precision: float,
|
|
218
|
+
) -> tuple[tuple[tuple[tuple[int, int], ...], ...], tuple[tuple[tuple[int, int], ...], ...]]:
|
|
219
|
+
origin = fragment.bbox[0], fragment.bbox[1]
|
|
220
|
+
fragment_poly = _polygon_from_points(fragment.points, layer, origin)
|
|
221
|
+
masks = [_region_polygon(region, layer, origin) for region in regions]
|
|
222
|
+
allowed = gdstk.boolean([fragment_poly], masks, "and", precision=precision) or []
|
|
223
|
+
outside = gdstk.boolean([fragment_poly], masks, "not", precision=precision) or []
|
|
224
|
+
return _global_points_from_gdstk(allowed, origin), _global_points_from_gdstk(outside, origin)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _polygon_from_points(points: tuple[tuple[int, int], ...], layer: LayerSpec, origin: tuple[int, int]) -> gdstk.Polygon:
|
|
228
|
+
ox, oy = origin
|
|
229
|
+
return gdstk.Polygon([(x - ox, y - oy) for x, y in points], layer=layer.layer, datatype=layer.datatype)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _region_polygon(region: AllowedRegion, layer: LayerSpec, origin: tuple[int, int]) -> gdstk.Polygon:
|
|
233
|
+
if region.bbox is None:
|
|
234
|
+
raise ValueError("whole-layer regions should not be converted to bbox masks")
|
|
235
|
+
ox, oy = origin
|
|
236
|
+
x0, y0, x1, y1 = region.bbox
|
|
237
|
+
halo = region.halo_dbu
|
|
238
|
+
return gdstk.rectangle(
|
|
239
|
+
(x0 - halo - ox, y0 - halo - oy),
|
|
240
|
+
(x1 + halo - ox, y1 + halo - oy),
|
|
241
|
+
layer=layer.layer,
|
|
242
|
+
datatype=layer.datatype,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _global_points_from_gdstk(polygons: list[gdstk.Polygon], origin: tuple[int, int]) -> tuple[tuple[tuple[int, int], ...], ...]:
|
|
247
|
+
ox, oy = origin
|
|
248
|
+
result = []
|
|
249
|
+
for polygon in polygons:
|
|
250
|
+
points = tuple((snap_to_grid(point[0], 1) + ox, snap_to_grid(point[1], 1) + oy) for point in polygon.points)
|
|
251
|
+
if len(points) >= 3 and polygon_twice_area(points):
|
|
252
|
+
result.append(points)
|
|
253
|
+
return tuple(result)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _components_from_fragments(
|
|
257
|
+
layer: LayerSpec,
|
|
258
|
+
direction: DifferenceDirection,
|
|
259
|
+
classification: str,
|
|
260
|
+
fragments: tuple[DiffFragment, ...],
|
|
261
|
+
) -> tuple[ClassifiedComponent, ...]:
|
|
262
|
+
return tuple(_component_from_fragment(layer, direction, classification, fragment) for fragment in fragments if fragment.twice_area)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def _component_from_fragment(
|
|
266
|
+
layer: LayerSpec,
|
|
267
|
+
direction: DifferenceDirection,
|
|
268
|
+
classification: str,
|
|
269
|
+
fragment: DiffFragment,
|
|
270
|
+
) -> ClassifiedComponent:
|
|
271
|
+
return ClassifiedComponent(
|
|
272
|
+
layer=layer,
|
|
273
|
+
direction=direction,
|
|
274
|
+
classification=classification,
|
|
275
|
+
points=fragment.points,
|
|
276
|
+
bbox=fragment.bbox,
|
|
277
|
+
twice_area=fragment.twice_area,
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _component_from_points(
|
|
282
|
+
layer: LayerSpec,
|
|
283
|
+
direction: DifferenceDirection,
|
|
284
|
+
classification: str,
|
|
285
|
+
points: tuple[tuple[int, int], ...],
|
|
286
|
+
) -> ClassifiedComponent:
|
|
287
|
+
return ClassifiedComponent(
|
|
288
|
+
layer=layer,
|
|
289
|
+
direction=direction,
|
|
290
|
+
classification=classification,
|
|
291
|
+
points=points,
|
|
292
|
+
bbox=polygon_bbox(points),
|
|
293
|
+
twice_area=polygon_twice_area(points),
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def _component_sort_key(component: ClassifiedComponent) -> tuple[object, ...]:
|
|
298
|
+
return (component.layer, component.direction.value, component.classification, component.bbox, component.points)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def _effective_policy_json(allowed_regions: tuple[AllowedRegion, ...]) -> dict[str, object]:
|
|
302
|
+
return {
|
|
303
|
+
"allowed_region_count": len(allowed_regions),
|
|
304
|
+
"allowed_regions": [region.to_json() for region in allowed_regions],
|
|
305
|
+
}
|