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.
Files changed (85) hide show
  1. gdsdiff/__init__.py +148 -0
  2. gdsdiff/__main__.py +7 -0
  3. gdsdiff/_environment.py +12 -0
  4. gdsdiff/_version.py +34 -0
  5. gdsdiff/acceptance_evidence.py +530 -0
  6. gdsdiff/api.py +2955 -0
  7. gdsdiff/backends/__init__.py +26 -0
  8. gdsdiff/backends/base.py +77 -0
  9. gdsdiff/backends/boundary_loop_ctypes.py +191 -0
  10. gdsdiff/backends/cpu.py +20 -0
  11. gdsdiff/backends/cpu_ctypes.py +255 -0
  12. gdsdiff/backends/cuda.py +40 -0
  13. gdsdiff/backends/cuda_ctypes.py +1297 -0
  14. gdsdiff/backends/exact.py +424 -0
  15. gdsdiff/backends/geometry_ctypes.py +252 -0
  16. gdsdiff/backends/identity.py +53 -0
  17. gdsdiff/backends/metal.py +198 -0
  18. gdsdiff/backends/metal_ctypes.py +866 -0
  19. gdsdiff/backends/polygon_extract_ctypes.py +233 -0
  20. gdsdiff/backends/structural_ctypes.py +130 -0
  21. gdsdiff/boundary_loops.py +616 -0
  22. gdsdiff/cache.py +544 -0
  23. gdsdiff/canonicalize.py +176 -0
  24. gdsdiff/cli.py +352 -0
  25. gdsdiff/config.py +257 -0
  26. gdsdiff/cuda_setup.py +174 -0
  27. gdsdiff/design_history.py +65 -0
  28. gdsdiff/diagnostics.py +42 -0
  29. gdsdiff/diff_gds.py +66 -0
  30. gdsdiff/exact_diff_markdown.py +979 -0
  31. gdsdiff/exact_oracle.py +649 -0
  32. gdsdiff/extract.py +376 -0
  33. gdsdiff/gds_metadata.py +43 -0
  34. gdsdiff/geometry.py +112 -0
  35. gdsdiff/grid.py +143 -0
  36. gdsdiff/hierarchy.py +215 -0
  37. gdsdiff/hierarchy_extract.py +263 -0
  38. gdsdiff/inventory.py +153 -0
  39. gdsdiff/multiprocessing_support.py +39 -0
  40. gdsdiff/native.py +135 -0
  41. gdsdiff/native_cache.py +265 -0
  42. gdsdiff/native_src/cpu_prefilter.cpp +349 -0
  43. gdsdiff/native_src/gds_boundary_loops.cpp +505 -0
  44. gdsdiff/native_src/gds_geometry_scan.cpp +601 -0
  45. gdsdiff/native_src/gds_polygon_extract.cpp +594 -0
  46. gdsdiff/native_src/gds_structural_scan.cpp +335 -0
  47. gdsdiff/native_src/gdsdiff/CMakeLists.txt +74 -0
  48. gdsdiff/native_src/gdsdiff/core.cpp +191 -0
  49. gdsdiff/native_src/gdsdiff/core.hpp +96 -0
  50. gdsdiff/native_src/gdsdiff/cuda_assignment.cu +304 -0
  51. gdsdiff/native_src/gdsdiff/cuda_assignment.cuh +33 -0
  52. gdsdiff/native_src/gdsdiff/cuda_candidates.cu +133 -0
  53. gdsdiff/native_src/gdsdiff/cuda_candidates.cuh +17 -0
  54. gdsdiff/native_src/gdsdiff/cuda_ctypes.cu +4528 -0
  55. gdsdiff/native_src/gdsdiff/cuda_memory.cu +194 -0
  56. gdsdiff/native_src/gdsdiff/cuda_memory.cuh +34 -0
  57. gdsdiff/native_src/gdsdiff/metal_ctypes.mm +2791 -0
  58. gdsdiff/native_src/gdsdiff/python_bindings.cpp +21 -0
  59. gdsdiff/native_src/gdsdiff/test_core.cpp +93 -0
  60. gdsdiff/native_src/gdsdiff/test_cuda_assignment.cu +109 -0
  61. gdsdiff/native_src/gdsdiff/test_cuda_candidates.cu +94 -0
  62. gdsdiff/native_src/gdsdiff/test_cuda_memory.cu +97 -0
  63. gdsdiff/policy.py +305 -0
  64. gdsdiff/polygon_components.py +860 -0
  65. gdsdiff/profiles.py +152 -0
  66. gdsdiff/py.typed +1 -0
  67. gdsdiff/rect_coverage.py +384 -0
  68. gdsdiff/report.py +354 -0
  69. gdsdiff/schemas/gdsdiff_report-v1.schema.json +92 -0
  70. gdsdiff/semantics.py +75 -0
  71. gdsdiff/source_edge_diff.py +1120 -0
  72. gdsdiff/spatial.py +71 -0
  73. gdsdiff/structural_guard.py +161 -0
  74. gdsdiff/surplus_coverage.py +255 -0
  75. gdsdiff/synthetic_suite.py +1643 -0
  76. gdsdiff/templates.py +709 -0
  77. gdsdiff/tiling.py +153 -0
  78. gdsdiff/windowed_exact.py +270 -0
  79. gdsdiff/windows.py +184 -0
  80. gdsdiff-0.1.0.dist-info/METADATA +135 -0
  81. gdsdiff-0.1.0.dist-info/RECORD +85 -0
  82. gdsdiff-0.1.0.dist-info/WHEEL +5 -0
  83. gdsdiff-0.1.0.dist-info/entry_points.txt +4 -0
  84. gdsdiff-0.1.0.dist-info/licenses/LICENSE +674 -0
  85. 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)