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,233 @@
1
+ from __future__ import annotations
2
+
3
+ import ctypes
4
+ import subprocess
5
+ from dataclasses import dataclass
6
+ from functools import lru_cache
7
+ from pathlib import Path
8
+
9
+ import numpy as np
10
+
11
+ from ..config import LayerSpec, parse_layer_selector
12
+ from ..geometry import LayerTable, PolygonBuffer
13
+ from ..native_cache import ensure_cached_native_library
14
+
15
+
16
+ class PolygonExtractCtypesUnavailable(RuntimeError):
17
+ pass
18
+
19
+
20
+ class _CPolygonExtractResult(ctypes.Structure):
21
+ _fields_ = [
22
+ ("polygon_count", ctypes.c_uint64),
23
+ ("vertex_count", ctypes.c_uint64),
24
+ ("vertices_xy", ctypes.POINTER(ctypes.c_int64)),
25
+ ("polygon_offsets", ctypes.POINTER(ctypes.c_uint64)),
26
+ ("layers", ctypes.POINTER(ctypes.c_int32)),
27
+ ("datatypes", ctypes.POINTER(ctypes.c_int32)),
28
+ ("bboxes", ctypes.POINTER(ctypes.c_int64)),
29
+ ("flags", ctypes.POINTER(ctypes.c_uint32)),
30
+ ("source_ids", ctypes.POINTER(ctypes.c_uint64)),
31
+ ("hashes", ctypes.POINTER(ctypes.c_uint64)),
32
+ ]
33
+
34
+
35
+ @dataclass(frozen=True)
36
+ class NativePolygonExtraction:
37
+ path: Path
38
+ top_name: str | None
39
+ buffer: PolygonBuffer
40
+ native_parse_s: float | None = None
41
+
42
+
43
+ def extract_gds_polygons_native(
44
+ path: str | Path,
45
+ *,
46
+ top_name: str | None = None,
47
+ layers: str | LayerSpec | tuple[str | tuple[int, int] | LayerSpec, ...] = "all",
48
+ ) -> NativePolygonExtraction:
49
+ path = Path(path)
50
+ library = _load_library()
51
+ result = _CPolygonExtractResult()
52
+ top_bytes = top_name.encode("utf-8") if top_name else None
53
+ status = library.gds_polygon_extract(str(path).encode("utf-8"), top_bytes, ctypes.byref(result))
54
+ if status != 0:
55
+ message = library.gds_polygon_extract_last_error()
56
+ raise PolygonExtractCtypesUnavailable(message.decode("utf-8") if message else "native polygon extraction failed")
57
+ try:
58
+ buffer = _buffer_from_result(result, layers=layers)
59
+ return NativePolygonExtraction(path=path, top_name=top_name, buffer=buffer)
60
+ finally:
61
+ library.gds_polygon_extract_free(ctypes.byref(result))
62
+
63
+
64
+ def polygon_extract_ctypes_available() -> bool:
65
+ try:
66
+ _load_library()
67
+ except PolygonExtractCtypesUnavailable:
68
+ return False
69
+ return True
70
+
71
+
72
+ def _buffer_from_result(
73
+ result: _CPolygonExtractResult,
74
+ *,
75
+ layers: str | LayerSpec | tuple[str | tuple[int, int] | LayerSpec, ...],
76
+ ) -> PolygonBuffer:
77
+ polygon_count = int(result.polygon_count)
78
+ vertex_count = int(result.vertex_count)
79
+ vertices = _copy_array(result.vertices_xy, vertex_count * 2, np.int64).reshape((vertex_count, 2))
80
+ offsets = _copy_array(result.polygon_offsets, polygon_count + 1, np.uint64)
81
+ raw_layers = _copy_array(result.layers, polygon_count, np.int32)
82
+ raw_datatypes = _copy_array(result.datatypes, polygon_count, np.int32)
83
+ bboxes = _copy_array(result.bboxes, polygon_count * 4, np.int64).reshape((polygon_count, 4))
84
+ flags = _copy_array(result.flags, polygon_count, np.uint32)
85
+ source_ids = _copy_array(result.source_ids, polygon_count, np.uint64)
86
+ selected = _selected_layers(layers)
87
+ hashes = _copy_array(result.hashes, polygon_count * 2, np.uint64).reshape((polygon_count, 2))
88
+ if selected is not None:
89
+ keep = _selected_mask(raw_layers, raw_datatypes, selected)
90
+ if not np.all(keep):
91
+ vertices, offsets, raw_layers, raw_datatypes, bboxes, flags, source_ids, hashes = _filter_polygons(
92
+ vertices,
93
+ offsets,
94
+ raw_layers,
95
+ raw_datatypes,
96
+ bboxes,
97
+ flags,
98
+ source_ids,
99
+ hashes,
100
+ keep,
101
+ )
102
+ table, layer_ids = _build_layer_table(raw_layers, raw_datatypes)
103
+ return PolygonBuffer(
104
+ vertices_xy=vertices.astype(np.int64, copy=False),
105
+ polygon_offsets=offsets.astype(np.uint64, copy=False),
106
+ layer_ids=layer_ids,
107
+ bboxes=bboxes.astype(np.int64, copy=False),
108
+ flags=flags.astype(np.uint32, copy=False),
109
+ source_ids=source_ids.astype(np.uint64, copy=False),
110
+ layer_table=table,
111
+ polygon_hashes=hashes.astype(np.uint64, copy=False),
112
+ )
113
+
114
+
115
+ def _selected_layers(layers) -> tuple[LayerSpec, ...] | None:
116
+ if layers == "all":
117
+ return None
118
+ parsed = parse_layer_selector(layers)
119
+ if parsed == "all":
120
+ return None
121
+ return parsed
122
+
123
+
124
+ def _selected_mask(raw_layers: np.ndarray, raw_datatypes: np.ndarray, selected: tuple[LayerSpec, ...]) -> np.ndarray:
125
+ keep = np.zeros(raw_layers.shape, dtype=bool)
126
+ for layer in selected:
127
+ keep |= (raw_layers == layer.layer) & (raw_datatypes == layer.datatype)
128
+ return keep
129
+
130
+
131
+ def _build_layer_table(raw_layers: np.ndarray, raw_datatypes: np.ndarray) -> tuple[LayerTable, np.ndarray]:
132
+ if len(raw_layers) == 0:
133
+ return LayerTable(()), np.empty((0,), dtype=np.uint32)
134
+ pairs = np.column_stack((raw_layers, raw_datatypes)).astype(np.int32, copy=False)
135
+ unique_pairs = np.unique(pairs, axis=0)
136
+ table = LayerTable(tuple(LayerSpec(int(layer), int(datatype)) for layer, datatype in unique_pairs))
137
+ layer_ids = np.empty(len(raw_layers), dtype=np.uint32)
138
+ for index, (layer, datatype) in enumerate(unique_pairs):
139
+ layer_ids[(raw_layers == layer) & (raw_datatypes == datatype)] = index
140
+ return table, layer_ids
141
+
142
+
143
+ def _filter_polygons(
144
+ vertices: np.ndarray,
145
+ offsets: np.ndarray,
146
+ raw_layers: np.ndarray,
147
+ raw_datatypes: np.ndarray,
148
+ bboxes: np.ndarray,
149
+ flags: np.ndarray,
150
+ source_ids: np.ndarray,
151
+ hashes: np.ndarray,
152
+ keep: np.ndarray,
153
+ ) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
154
+ kept_indices = np.nonzero(keep)[0]
155
+ new_vertices: list[np.ndarray] = []
156
+ new_offsets = [0]
157
+ for index in kept_indices:
158
+ start = int(offsets[index])
159
+ stop = int(offsets[index + 1])
160
+ part = vertices[start:stop]
161
+ new_vertices.append(part)
162
+ new_offsets.append(new_offsets[-1] + len(part))
163
+ return (
164
+ np.vstack(new_vertices).astype(np.int64, copy=False) if new_vertices else np.empty((0, 2), dtype=np.int64),
165
+ np.asarray(new_offsets, dtype=np.uint64),
166
+ raw_layers[keep].astype(np.int32, copy=False),
167
+ raw_datatypes[keep].astype(np.int32, copy=False),
168
+ bboxes[keep].astype(np.int64, copy=False),
169
+ flags[keep].astype(np.uint32, copy=False),
170
+ source_ids[keep].astype(np.uint64, copy=False),
171
+ hashes[keep].astype(np.uint64, copy=False),
172
+ )
173
+
174
+
175
+ def _copy_array(pointer, count: int, dtype) -> np.ndarray:
176
+ if count == 0:
177
+ return np.empty((0,), dtype=dtype)
178
+ if not pointer:
179
+ raise PolygonExtractCtypesUnavailable("native polygon extraction returned a null array")
180
+ ctype = np.ctypeslib.as_ctypes_type(dtype)
181
+ array = ctypes.cast(pointer, ctypes.POINTER(ctype * count)).contents
182
+ return np.frombuffer(array, dtype=dtype).copy()
183
+
184
+
185
+ @lru_cache(maxsize=1)
186
+ def _load_library() -> ctypes.CDLL:
187
+ path = _library_path()
188
+ library = ctypes.CDLL(str(path))
189
+ library.gds_polygon_extract.argtypes = [
190
+ ctypes.c_char_p,
191
+ ctypes.c_char_p,
192
+ ctypes.POINTER(_CPolygonExtractResult),
193
+ ]
194
+ library.gds_polygon_extract.restype = ctypes.c_int
195
+ library.gds_polygon_extract_free.argtypes = [ctypes.POINTER(_CPolygonExtractResult)]
196
+ library.gds_polygon_extract_free.restype = None
197
+ library.gds_polygon_extract_last_error.argtypes = []
198
+ library.gds_polygon_extract_last_error.restype = ctypes.c_char_p
199
+ return library
200
+
201
+
202
+ def _build_library(path: Path) -> None:
203
+ source = _source_path()
204
+ path.parent.mkdir(parents=True, exist_ok=True)
205
+ command = [
206
+ "g++",
207
+ "-std=c++17",
208
+ "-O3",
209
+ "-shared",
210
+ "-fPIC",
211
+ str(source),
212
+ "-o",
213
+ str(path),
214
+ ]
215
+ try:
216
+ completed = subprocess.run(command, check=False, text=True, capture_output=True)
217
+ except FileNotFoundError as exc:
218
+ raise PolygonExtractCtypesUnavailable("g++ is not available") from exc
219
+ if completed.returncode != 0:
220
+ raise PolygonExtractCtypesUnavailable((completed.stderr or completed.stdout).strip() or "g++ failed")
221
+
222
+
223
+ def _library_path() -> Path:
224
+ return ensure_cached_native_library(
225
+ "_gds_polygon_extract.so",
226
+ sources=(_source_path(),),
227
+ build_key=("g++", "-std=c++17", "-O3", "-shared", "-fPIC"),
228
+ builder=_build_library,
229
+ )
230
+
231
+
232
+ def _source_path() -> Path:
233
+ return Path(__file__).resolve().parents[1] / "native_src" / "gds_polygon_extract.cpp"
@@ -0,0 +1,130 @@
1
+ from __future__ import annotations
2
+
3
+ import ctypes
4
+ import subprocess
5
+ from dataclasses import dataclass
6
+ from functools import lru_cache
7
+ from pathlib import Path
8
+
9
+ from ..native_cache import ensure_cached_native_library
10
+
11
+
12
+ class StructuralCtypesUnavailable(RuntimeError):
13
+ pass
14
+
15
+
16
+ class _CStructuralResult(ctypes.Structure):
17
+ _fields_ = [
18
+ ("flattened_shape_count", ctypes.c_uint64),
19
+ ("cell_count", ctypes.c_uint64),
20
+ ("reachable_cell_count", ctypes.c_uint64),
21
+ ("structural_hash", ctypes.c_uint64),
22
+ ]
23
+
24
+
25
+ @dataclass(frozen=True)
26
+ class NativeStructuralSummary:
27
+ path: Path
28
+ flattened_shape_count: int
29
+ cell_count: int
30
+ reachable_cell_count: int
31
+ structural_hash: int
32
+
33
+ def to_json(self) -> dict[str, object]:
34
+ return {
35
+ "path": str(self.path),
36
+ "flattened_shape_count": self.flattened_shape_count,
37
+ "cell_count": self.cell_count,
38
+ "reachable_cell_count": self.reachable_cell_count,
39
+ "structural_hash": f"{self.structural_hash:016x}",
40
+ }
41
+
42
+
43
+ def summarize_gds_structure_native(
44
+ path: str | Path,
45
+ *,
46
+ top_name: str | None = None,
47
+ counts_only: bool = False,
48
+ ) -> NativeStructuralSummary:
49
+ library = _load_library()
50
+ result = _CStructuralResult()
51
+ top_bytes = top_name.encode("utf-8") if top_name else None
52
+ function = library.gds_structural_summary_counts_only if counts_only else library.gds_structural_summary
53
+ status = function(
54
+ str(path).encode("utf-8"),
55
+ top_bytes,
56
+ ctypes.byref(result),
57
+ )
58
+ if status != 0:
59
+ message = library.gds_structural_last_error()
60
+ raise StructuralCtypesUnavailable(message.decode("utf-8") if message else "native structural scan failed")
61
+ return NativeStructuralSummary(
62
+ path=Path(path),
63
+ flattened_shape_count=int(result.flattened_shape_count),
64
+ cell_count=int(result.cell_count),
65
+ reachable_cell_count=int(result.reachable_cell_count),
66
+ structural_hash=int(result.structural_hash),
67
+ )
68
+
69
+
70
+ def structural_ctypes_available() -> bool:
71
+ try:
72
+ _load_library()
73
+ except StructuralCtypesUnavailable:
74
+ return False
75
+ return True
76
+
77
+
78
+ @lru_cache(maxsize=1)
79
+ def _load_library() -> ctypes.CDLL:
80
+ path = _library_path()
81
+ library = ctypes.CDLL(str(path))
82
+ library.gds_structural_summary.argtypes = [
83
+ ctypes.c_char_p,
84
+ ctypes.c_char_p,
85
+ ctypes.POINTER(_CStructuralResult),
86
+ ]
87
+ library.gds_structural_summary.restype = ctypes.c_int
88
+ library.gds_structural_summary_counts_only.argtypes = [
89
+ ctypes.c_char_p,
90
+ ctypes.c_char_p,
91
+ ctypes.POINTER(_CStructuralResult),
92
+ ]
93
+ library.gds_structural_summary_counts_only.restype = ctypes.c_int
94
+ library.gds_structural_last_error.argtypes = []
95
+ library.gds_structural_last_error.restype = ctypes.c_char_p
96
+ return library
97
+
98
+
99
+ def _build_library(path: Path) -> None:
100
+ source = _source_path()
101
+ path.parent.mkdir(parents=True, exist_ok=True)
102
+ command = [
103
+ "g++",
104
+ "-std=c++17",
105
+ "-O3",
106
+ "-shared",
107
+ "-fPIC",
108
+ str(source),
109
+ "-o",
110
+ str(path),
111
+ ]
112
+ try:
113
+ completed = subprocess.run(command, check=False, text=True, capture_output=True)
114
+ except FileNotFoundError as exc:
115
+ raise StructuralCtypesUnavailable("g++ is not available") from exc
116
+ if completed.returncode != 0:
117
+ raise StructuralCtypesUnavailable((completed.stderr or completed.stdout).strip() or "g++ failed")
118
+
119
+
120
+ def _library_path() -> Path:
121
+ return ensure_cached_native_library(
122
+ "_gds_structural_scan.so",
123
+ sources=(_source_path(),),
124
+ build_key=("g++", "-std=c++17", "-O3", "-shared", "-fPIC"),
125
+ builder=_build_library,
126
+ )
127
+
128
+
129
+ def _source_path() -> Path:
130
+ return Path(__file__).resolve().parents[1] / "native_src" / "gds_structural_scan.cpp"