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
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from .base import FileIdentityResult
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AcceleratedIdentityUnavailable(RuntimeError):
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def compare_file_identity_accelerated(
|
|
13
|
+
old_path: str | Path,
|
|
14
|
+
new_path: str | Path,
|
|
15
|
+
*,
|
|
16
|
+
preferred_backend: str | None = None,
|
|
17
|
+
) -> FileIdentityResult:
|
|
18
|
+
"""Compare equal-size file bytes through the best available accelerator.
|
|
19
|
+
|
|
20
|
+
The wrapper is backend neutral so CUDA and Metal can share the comparator's
|
|
21
|
+
API-level fast path.
|
|
22
|
+
"""
|
|
23
|
+
errors: list[str] = []
|
|
24
|
+
backend_order = _backend_order(preferred_backend)
|
|
25
|
+
for backend in backend_order:
|
|
26
|
+
try:
|
|
27
|
+
if backend == "cuda":
|
|
28
|
+
from .cuda_ctypes import compare_files_cuda
|
|
29
|
+
|
|
30
|
+
result = compare_files_cuda(old_path, new_path)
|
|
31
|
+
elif backend == "metal":
|
|
32
|
+
from .metal_ctypes import compare_files_metal
|
|
33
|
+
|
|
34
|
+
result = compare_files_metal(old_path, new_path)
|
|
35
|
+
else:
|
|
36
|
+
continue
|
|
37
|
+
return FileIdentityResult(
|
|
38
|
+
old_size=result.old_size,
|
|
39
|
+
new_size=result.new_size,
|
|
40
|
+
same=result.same,
|
|
41
|
+
method=result.method,
|
|
42
|
+
)
|
|
43
|
+
except Exception as exc:
|
|
44
|
+
errors.append(f"{backend}: {exc}")
|
|
45
|
+
raise AcceleratedIdentityUnavailable("; ".join(errors) or "no accelerated identity backend available")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _backend_order(preferred_backend: str | None) -> tuple[str, ...]:
|
|
49
|
+
if preferred_backend == "metal":
|
|
50
|
+
return ("metal", "cuda")
|
|
51
|
+
if preferred_backend == "cuda":
|
|
52
|
+
return ("cuda", "metal")
|
|
53
|
+
return ("cuda", "metal")
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import platform
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from ..exact_oracle import ExactLayerTask, LayerExactDiff
|
|
8
|
+
from .base import BackendCapabilities
|
|
9
|
+
from .exact import (
|
|
10
|
+
RectExactStats,
|
|
11
|
+
UnsupportedExactComponent,
|
|
12
|
+
_compare_rect_or_rect_set_task,
|
|
13
|
+
_fragment_from_rect,
|
|
14
|
+
_task_rect_pair,
|
|
15
|
+
_task_rect_sets,
|
|
16
|
+
)
|
|
17
|
+
from ..exact_oracle import _exact_layer_diff_task
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class MetalExactUnavailable(RuntimeError):
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class MetalManhattanRectExactComponentBackend:
|
|
25
|
+
"""Metal exact-component backend for bounded Manhattan rectangle tasks.
|
|
26
|
+
|
|
27
|
+
The comparator's exact-component contract is intentionally shared by CUDA
|
|
28
|
+
and Metal. This class dispatches compatible rectangle-pair and rectangle-set
|
|
29
|
+
tasks to the Apple Silicon Metal ctypes runtime, with optional gdstk fallback
|
|
30
|
+
for unsupported or unavailable cases.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(self, *, fallback_to_gdstk: bool = False) -> None:
|
|
34
|
+
self.fallback_to_gdstk = fallback_to_gdstk
|
|
35
|
+
|
|
36
|
+
def capabilities(self) -> BackendCapabilities:
|
|
37
|
+
return BackendCapabilities(name="metal-manhattan-rect-exact-component", deterministic=True, requires_gpu=True)
|
|
38
|
+
|
|
39
|
+
def compare_components(self, tasks: tuple[ExactLayerTask, ...]) -> tuple[LayerExactDiff, ...]:
|
|
40
|
+
if platform.system() != "Darwin" or platform.machine() not in {"arm64", "aarch64"}:
|
|
41
|
+
raise MetalExactUnavailable("Metal exact backend requires macOS on Apple Silicon")
|
|
42
|
+
from .metal_ctypes import MetalCtypesUnavailable, run_metal_rect_set_exact_fragments
|
|
43
|
+
|
|
44
|
+
diffs: list[LayerExactDiff | None] = [None] * len(tasks)
|
|
45
|
+
metal_set_items: list[tuple[int, ExactLayerTask, tuple[tuple[int, int, int, int], ...], tuple[tuple[int, int, int, int], ...]]] = []
|
|
46
|
+
fallback = 0
|
|
47
|
+
unsupported = 0
|
|
48
|
+
skipped = 0
|
|
49
|
+
rect_set = 0
|
|
50
|
+
metal_rect_set = 0
|
|
51
|
+
for index, task in enumerate(tasks):
|
|
52
|
+
try:
|
|
53
|
+
old_rect, new_rect = _task_rect_pair(task)
|
|
54
|
+
except UnsupportedExactComponent:
|
|
55
|
+
rect_sets = _task_rect_sets(task)
|
|
56
|
+
if rect_sets is not None:
|
|
57
|
+
metal_set_items.append((index, task, rect_sets[0], rect_sets[1]))
|
|
58
|
+
continue
|
|
59
|
+
try:
|
|
60
|
+
diffs[index] = _compare_rect_or_rect_set_task(task)
|
|
61
|
+
rect_set += 1
|
|
62
|
+
continue
|
|
63
|
+
except UnsupportedExactComponent:
|
|
64
|
+
pass
|
|
65
|
+
unsupported += 1
|
|
66
|
+
if not self.fallback_to_gdstk:
|
|
67
|
+
self.stats = RectExactStats(
|
|
68
|
+
0,
|
|
69
|
+
fallback,
|
|
70
|
+
unsupported,
|
|
71
|
+
skipped,
|
|
72
|
+
cpu_rect_set_tasks=rect_set,
|
|
73
|
+
metal_rect_set_tasks=metal_rect_set,
|
|
74
|
+
)
|
|
75
|
+
raise
|
|
76
|
+
diffs[index] = _exact_layer_diff_task(task)
|
|
77
|
+
fallback += 1
|
|
78
|
+
continue
|
|
79
|
+
if old_rect is not None and old_rect == new_rect:
|
|
80
|
+
diffs[index] = LayerExactDiff(layer=task.layer)
|
|
81
|
+
skipped += 1
|
|
82
|
+
continue
|
|
83
|
+
old_rects = () if old_rect is None else (old_rect,)
|
|
84
|
+
new_rects = () if new_rect is None else (new_rect,)
|
|
85
|
+
metal_set_items.append((index, task, old_rects, new_rects))
|
|
86
|
+
if metal_set_items:
|
|
87
|
+
max_rects = max(max(len(old_rects), len(new_rects)) for _index, _task, old_rects, new_rects in metal_set_items)
|
|
88
|
+
if max_rects > 16:
|
|
89
|
+
for index, task, _old_rects, _new_rects in metal_set_items:
|
|
90
|
+
try:
|
|
91
|
+
diffs[index] = _compare_rect_or_rect_set_task(task)
|
|
92
|
+
rect_set += 1
|
|
93
|
+
except UnsupportedExactComponent:
|
|
94
|
+
unsupported += 1
|
|
95
|
+
if not self.fallback_to_gdstk:
|
|
96
|
+
self.stats = RectExactStats(
|
|
97
|
+
0,
|
|
98
|
+
fallback,
|
|
99
|
+
unsupported,
|
|
100
|
+
skipped,
|
|
101
|
+
cpu_rect_set_tasks=rect_set,
|
|
102
|
+
metal_rect_set_tasks=metal_rect_set,
|
|
103
|
+
)
|
|
104
|
+
raise
|
|
105
|
+
diffs[index] = _exact_layer_diff_task(task)
|
|
106
|
+
fallback += 1
|
|
107
|
+
else:
|
|
108
|
+
old_rects_array = np.zeros((len(metal_set_items), max_rects, 4), dtype=np.int64)
|
|
109
|
+
new_rects_array = np.zeros((len(metal_set_items), max_rects, 4), dtype=np.int64)
|
|
110
|
+
old_counts = np.zeros((len(metal_set_items),), dtype=np.uint8)
|
|
111
|
+
new_counts = np.zeros((len(metal_set_items),), dtype=np.uint8)
|
|
112
|
+
for row, (_index, _task, old_rects, new_rects) in enumerate(metal_set_items):
|
|
113
|
+
old_counts[row] = len(old_rects)
|
|
114
|
+
new_counts[row] = len(new_rects)
|
|
115
|
+
for col, rect in enumerate(old_rects):
|
|
116
|
+
old_rects_array[row, col] = rect
|
|
117
|
+
for col, rect in enumerate(new_rects):
|
|
118
|
+
new_rects_array[row, col] = rect
|
|
119
|
+
try:
|
|
120
|
+
metal_rects, metal_counts, metal_areas = run_metal_rect_set_exact_fragments(
|
|
121
|
+
old_rects_array,
|
|
122
|
+
new_rects_array,
|
|
123
|
+
old_counts,
|
|
124
|
+
new_counts,
|
|
125
|
+
)
|
|
126
|
+
except MetalCtypesUnavailable as exc:
|
|
127
|
+
if not self.fallback_to_gdstk:
|
|
128
|
+
self.stats = RectExactStats(
|
|
129
|
+
0,
|
|
130
|
+
fallback,
|
|
131
|
+
unsupported,
|
|
132
|
+
skipped,
|
|
133
|
+
cpu_rect_set_tasks=rect_set,
|
|
134
|
+
metal_rect_set_tasks=metal_rect_set,
|
|
135
|
+
)
|
|
136
|
+
raise MetalExactUnavailable(str(exc)) from exc
|
|
137
|
+
metal_rects = metal_counts = metal_areas = None
|
|
138
|
+
if metal_rects is None or metal_counts is None or metal_areas is None:
|
|
139
|
+
for index, task, _old_rects, _new_rects in metal_set_items:
|
|
140
|
+
try:
|
|
141
|
+
diffs[index] = _compare_rect_or_rect_set_task(task)
|
|
142
|
+
rect_set += 1
|
|
143
|
+
except UnsupportedExactComponent:
|
|
144
|
+
unsupported += 1
|
|
145
|
+
if not self.fallback_to_gdstk:
|
|
146
|
+
self.stats = RectExactStats(
|
|
147
|
+
0,
|
|
148
|
+
fallback,
|
|
149
|
+
unsupported,
|
|
150
|
+
skipped,
|
|
151
|
+
cpu_rect_set_tasks=rect_set,
|
|
152
|
+
metal_rect_set_tasks=metal_rect_set,
|
|
153
|
+
)
|
|
154
|
+
raise
|
|
155
|
+
diffs[index] = _exact_layer_diff_task(task)
|
|
156
|
+
fallback += 1
|
|
157
|
+
else:
|
|
158
|
+
for row, (index, task, _old_rects, _new_rects) in enumerate(metal_set_items):
|
|
159
|
+
old_count = int(metal_counts[row, 0])
|
|
160
|
+
new_count = int(metal_counts[row, 1])
|
|
161
|
+
old_fragments = tuple(
|
|
162
|
+
_fragment_from_rect(tuple(int(v) for v in metal_rects[row, 0, piece]))
|
|
163
|
+
for piece in range(old_count)
|
|
164
|
+
)
|
|
165
|
+
new_fragments = tuple(
|
|
166
|
+
_fragment_from_rect(tuple(int(v) for v in metal_rects[row, 1, piece]))
|
|
167
|
+
for piece in range(new_count)
|
|
168
|
+
)
|
|
169
|
+
diff = LayerExactDiff(
|
|
170
|
+
layer=task.layer,
|
|
171
|
+
old_minus_new=old_fragments,
|
|
172
|
+
new_minus_old=new_fragments,
|
|
173
|
+
xor=old_fragments + new_fragments,
|
|
174
|
+
)
|
|
175
|
+
expected = np.asarray(
|
|
176
|
+
[
|
|
177
|
+
diff.old_minus_new_twice_area,
|
|
178
|
+
diff.new_minus_old_twice_area,
|
|
179
|
+
diff.xor_twice_area,
|
|
180
|
+
],
|
|
181
|
+
dtype=np.int64,
|
|
182
|
+
)
|
|
183
|
+
if not np.array_equal(metal_areas[row], expected):
|
|
184
|
+
raise UnsupportedExactComponent(
|
|
185
|
+
f"Metal rectangle set exact area mismatch for task {index}: "
|
|
186
|
+
f"metal={metal_areas[row].tolist()} expected={expected.tolist()}"
|
|
187
|
+
)
|
|
188
|
+
diffs[index] = diff
|
|
189
|
+
metal_rect_set += 1
|
|
190
|
+
self.stats = RectExactStats(
|
|
191
|
+
0,
|
|
192
|
+
fallback,
|
|
193
|
+
unsupported,
|
|
194
|
+
skipped,
|
|
195
|
+
cpu_rect_set_tasks=rect_set,
|
|
196
|
+
metal_rect_set_tasks=metal_rect_set,
|
|
197
|
+
)
|
|
198
|
+
return tuple(diff for diff in diffs if diff is not None)
|