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
gdsdiff/api.py ADDED
@@ -0,0 +1,2955 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import os
5
+ from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
6
+ from concurrent.futures.process import BrokenProcessPool
7
+ from dataclasses import dataclass, field
8
+ from pathlib import Path
9
+ from time import perf_counter
10
+ from typing import Callable
11
+
12
+ from .cache import (
13
+ CachedGeometry,
14
+ load_cached_pair_verdict,
15
+ load_or_build_cached_geometry,
16
+ load_or_build_cached_native_polygons,
17
+ write_cached_pair_verdict,
18
+ )
19
+ from ._environment import get_environment
20
+ from .canonicalize import intern_polygons
21
+ from .config import CompareConfig, LayerSpec
22
+ from .backends.exact import CudaManhattanRectExactComponentBackend, GdstkExactComponentBackend, ManhattanRectExactComponentBackend
23
+ from .backends.metal import MetalManhattanRectExactComponentBackend
24
+ from .backends.polygon_extract_ctypes import PolygonExtractCtypesUnavailable, extract_gds_polygons_native
25
+ from .design_history import render_design_history_entry
26
+ from .diff_gds import write_diff_gds
27
+ from .exact_diff_markdown import (
28
+ ExactDiffMarkdownWriteResult,
29
+ build_polygon_inventory_delta,
30
+ build_polygon_inventory_delta_from_record_delta,
31
+ write_exact_diff_markdown,
32
+ )
33
+ from .exact_oracle import DiffFragment, ExactLayerTask, ExactOracleResult, LayerExactDiff, run_full_exact_oracle
34
+ from .extract import ExtractionResult, extract_gds_polygons
35
+ from .gds_metadata import read_gds_metadata
36
+ from .geometry import PolygonBuffer
37
+ from .hierarchy import compare_hierarchy
38
+ from .multiprocessing_support import mark_portable_process_worker, portable_process_context
39
+ from .native import discover_native_capabilities
40
+ from .policy import PolicyClassificationResult, classify_exact_differences
41
+ from .polygon_components import (
42
+ add_overlapping_context_to_components,
43
+ build_record_delta_surplus_components,
44
+ exact_result_from_component_quads,
45
+ )
46
+ from .report import CompareReport, build_layer_reports
47
+ from .rect_coverage import RectCoverageUnsupported, run_cuda_rectangle_coverage_diff, run_metal_rectangle_coverage_diff
48
+ from .semantics import Backend, CompareStatus, EvidenceEngine, exit_code_for_status
49
+ from .spatial import SpatialIndex
50
+ from .source_edge_diff import exact_result_from_source_edges
51
+ from .surplus_coverage import run_canonical_surplus_coverage_probe, run_record_delta_surplus_coverage_probe
52
+ from .tiling import CpuTilePrefilterResult, TileConfig, run_cpu_tile_prefilter
53
+ from .windowed_exact import WindowedExactResult, run_windowed_exact_oracle
54
+ from .windows import CandidateWindow, WindowConfig, build_candidate_windows
55
+
56
+
57
+ @dataclass(frozen=True)
58
+ class CompareRunResult:
59
+ status: CompareStatus
60
+ report: CompareReport
61
+ exact_result: ExactOracleResult
62
+ policy_result: PolicyClassificationResult
63
+ prefilter: CpuTilePrefilterResult
64
+ windows: tuple[CandidateWindow, ...]
65
+ artifacts: dict[str, str] = field(default_factory=dict)
66
+ timings: dict[str, float] = field(default_factory=dict)
67
+
68
+ @property
69
+ def exit_code(self) -> int:
70
+ return exit_code_for_status(self.status, self.report.failure_policy)
71
+
72
+
73
+ @dataclass(frozen=True)
74
+ class _FullExactVerification:
75
+ exact_result: ExactOracleResult
76
+ policy_result: PolicyClassificationResult
77
+ old_input: dict[str, object]
78
+ new_input: dict[str, object]
79
+ selection: dict[str, object]
80
+ layers: tuple[object, ...]
81
+ extract_s: float
82
+ exact_s: float
83
+ exact_component_backend: dict[str, object] = field(default_factory=dict)
84
+ polygon_inventory_delta: object | None = None
85
+
86
+
87
+ @dataclass(frozen=True)
88
+ class _FileIdentity:
89
+ old_token: str
90
+ new_token: str
91
+ same: bool
92
+ method: str
93
+ old_size: int
94
+ new_size: int
95
+
96
+
97
+ @dataclass(frozen=True)
98
+ class _ExactComponentSelection:
99
+ name: str
100
+ component_partition: bool
101
+ executor: Callable[[tuple[ExactLayerTask, ...]], tuple[LayerExactDiff, ...]] | None
102
+ backend: object | None = None
103
+
104
+ def to_json(self) -> dict[str, object]:
105
+ data: dict[str, object] = {
106
+ "requested": self.name,
107
+ "component_partition": self.component_partition,
108
+ "executor_enabled": self.executor is not None,
109
+ }
110
+ if self.backend is not None and hasattr(self.backend, "capabilities"):
111
+ capabilities = self.backend.capabilities()
112
+ data.update(
113
+ {
114
+ "name": capabilities.name,
115
+ "deterministic": capabilities.deterministic,
116
+ "requires_gpu": capabilities.requires_gpu,
117
+ }
118
+ )
119
+ else:
120
+ data.update({"name": "gdstk-full-oracle", "deterministic": True, "requires_gpu": False})
121
+ stats = getattr(self.backend, "stats", None)
122
+ if stats is not None:
123
+ data["stats"] = {
124
+ "accelerated_tasks": stats.accelerated_tasks,
125
+ "fallback_tasks": stats.fallback_tasks,
126
+ "unsupported_tasks": stats.unsupported_tasks,
127
+ }
128
+ if hasattr(stats, "identical_skipped_tasks"):
129
+ data["stats"]["identical_skipped_tasks"] = stats.identical_skipped_tasks
130
+ if hasattr(stats, "cpu_rect_set_tasks"):
131
+ data["stats"]["cpu_rect_set_tasks"] = stats.cpu_rect_set_tasks
132
+ if hasattr(stats, "cuda_rect_set_tasks"):
133
+ data["stats"]["cuda_rect_set_tasks"] = stats.cuda_rect_set_tasks
134
+ if hasattr(stats, "metal_rect_set_tasks"):
135
+ data["stats"]["metal_rect_set_tasks"] = stats.metal_rect_set_tasks
136
+ return data
137
+
138
+
139
+ def compare_gds_files(
140
+ config: CompareConfig,
141
+ *,
142
+ json_report_path: str | Path | None = None,
143
+ diff_gds_path: str | Path | None = None,
144
+ markdown_path: str | Path | None = None,
145
+ exact_diff_markdown_path: str | Path | None = None,
146
+ exact: bool = False,
147
+ verify_exact_after_fast: bool = False,
148
+ audit_cpu_oracle: bool = False,
149
+ use_fast_shortcuts: bool = True,
150
+ use_structural_guard: bool = False,
151
+ use_native_geometry_guard: bool = False,
152
+ exact_component_backend: str = "none",
153
+ command: tuple[str, ...] = (),
154
+ ) -> CompareRunResult:
155
+ start = perf_counter()
156
+ backend_info = _select_backend(config, exact=exact)
157
+ exact = exact or config.evidence_engine == EvidenceEngine.GDSTK_ORACLE
158
+
159
+ old_top_name = config.old_top or config.top
160
+ new_top_name = config.new_top or config.top
161
+
162
+ if config.evidence_engine == EvidenceEngine.GPU_POLYGON and not exact:
163
+ return _try_gpu_polygon_evidence_path(
164
+ config,
165
+ backend_info=backend_info,
166
+ old_top_name=old_top_name,
167
+ new_top_name=new_top_name,
168
+ command=command,
169
+ json_report_path=json_report_path,
170
+ markdown_path=markdown_path,
171
+ exact_diff_markdown_path=exact_diff_markdown_path,
172
+ audit_cpu_oracle=audit_cpu_oracle,
173
+ exact_component_backend=exact_component_backend,
174
+ start=start,
175
+ )
176
+
177
+ if use_structural_guard and not exact:
178
+ structural_result = _try_native_structural_guard_path(
179
+ config,
180
+ backend_info=backend_info,
181
+ old_top_name=old_top_name,
182
+ new_top_name=new_top_name,
183
+ command=command,
184
+ json_report_path=json_report_path,
185
+ markdown_path=markdown_path,
186
+ exact_diff_markdown_path=exact_diff_markdown_path,
187
+ verify_exact_after_fast=verify_exact_after_fast,
188
+ exact_component_backend=exact_component_backend,
189
+ start=start,
190
+ )
191
+ if structural_result is not None:
192
+ return structural_result
193
+
194
+ if (use_native_geometry_guard or (use_fast_shortcuts and _accelerated_fast_path_eligible(config, backend_info))) and not exact:
195
+ geometry_guard_result = _try_native_geometry_guard_path(
196
+ config,
197
+ backend_info=backend_info,
198
+ old_top_name=old_top_name,
199
+ new_top_name=new_top_name,
200
+ command=command,
201
+ json_report_path=json_report_path,
202
+ markdown_path=markdown_path,
203
+ exact_diff_markdown_path=exact_diff_markdown_path,
204
+ verify_exact_after_fast=verify_exact_after_fast,
205
+ exact_component_backend=exact_component_backend,
206
+ start=start,
207
+ )
208
+ if geometry_guard_result is not None:
209
+ return geometry_guard_result
210
+
211
+ fast_digest_result = None
212
+ if use_fast_shortcuts and not exact and _accelerated_fast_path_eligible(config, backend_info):
213
+ fast_digest_result = _try_accelerated_byte_identity_fast_path(
214
+ config,
215
+ backend_info=backend_info,
216
+ old_metadata={"path": str(config.old_path), "metadata_mode": "skipped-accelerated-byte-identity"},
217
+ new_metadata={"path": str(config.new_path), "metadata_mode": "skipped-accelerated-byte-identity"},
218
+ old_top_name=old_top_name,
219
+ new_top_name=new_top_name,
220
+ command=command,
221
+ json_report_path=json_report_path,
222
+ markdown_path=markdown_path,
223
+ exact_diff_markdown_path=exact_diff_markdown_path,
224
+ verify_exact_after_fast=verify_exact_after_fast,
225
+ exact_component_backend=exact_component_backend,
226
+ start=start,
227
+ )
228
+ if fast_digest_result is not None:
229
+ return fast_digest_result
230
+
231
+ old_metadata = read_gds_metadata(config.old_path)
232
+ new_metadata = read_gds_metadata(config.new_path)
233
+
234
+ if use_fast_shortcuts:
235
+ fast_result = _try_cuda_hierarchy_fast_path(
236
+ config,
237
+ backend_info=backend_info,
238
+ old_metadata=old_metadata.to_json(),
239
+ new_metadata=new_metadata.to_json(),
240
+ old_top_name=old_top_name,
241
+ new_top_name=new_top_name,
242
+ exact=exact,
243
+ command=command,
244
+ json_report_path=json_report_path,
245
+ markdown_path=markdown_path,
246
+ exact_diff_markdown_path=exact_diff_markdown_path,
247
+ verify_exact_after_fast=verify_exact_after_fast,
248
+ )
249
+ if fast_result is not None:
250
+ return fast_result
251
+
252
+ if not exact and not use_fast_shortcuts and verify_exact_after_fast and config.geometry_cache_dir is not None:
253
+ cached_pair_result = _try_pair_verdict_cache_path(
254
+ config,
255
+ backend_info=backend_info,
256
+ old_metadata=old_metadata.to_json(),
257
+ new_metadata=new_metadata.to_json(),
258
+ old_top_name=old_top_name,
259
+ new_top_name=new_top_name,
260
+ command=command,
261
+ json_report_path=json_report_path,
262
+ markdown_path=markdown_path,
263
+ exact_diff_markdown_path=exact_diff_markdown_path,
264
+ start=start,
265
+ exact_component_backend=exact_component_backend,
266
+ )
267
+ if cached_pair_result is not None:
268
+ return cached_pair_result
269
+
270
+ cached_geometry: tuple[CachedGeometry | None, CachedGeometry | None] = (None, None)
271
+ if not exact and config.geometry_cache_dir is not None:
272
+ cache_start = perf_counter()
273
+ old_cached, new_cached = _cached_geometry_pair(
274
+ config,
275
+ old_top_name=old_top_name,
276
+ new_top_name=new_top_name,
277
+ )
278
+ cached_geometry = (old_cached, new_cached)
279
+ old_extraction = old_cached.extraction
280
+ new_extraction = new_cached.extraction
281
+ timings: dict[str, float] = {
282
+ "extract_s": 0.0,
283
+ "geometry_cache_s": perf_counter() - cache_start,
284
+ }
285
+ timings.update(old_cached.to_timing_json("old"))
286
+ timings.update(new_cached.to_timing_json("new"))
287
+ else:
288
+ old_extraction, new_extraction = _extract_pair(
289
+ config,
290
+ old_top_name=old_top_name,
291
+ new_top_name=new_top_name,
292
+ )
293
+ timings = {"extract_s": perf_counter() - start}
294
+ exact_verification_complete = True
295
+ layer_exact_status = "complete"
296
+ warnings: tuple[str, ...] = ()
297
+ if exact:
298
+ exact_oracle_start = perf_counter()
299
+ exact_selection = _select_exact_component_backend(exact_component_backend)
300
+ exact_result = run_full_exact_oracle(
301
+ old_extraction.buffer,
302
+ new_extraction.buffer,
303
+ component_partition=exact_selection.component_partition,
304
+ component_executor=exact_selection.executor,
305
+ )
306
+ timings["exact_oracle_s"] = perf_counter() - exact_oracle_start
307
+ prefilter = CpuTilePrefilterResult(candidate_tiles=())
308
+ windows: tuple[CandidateWindow, ...] = ()
309
+ exact_fallback = {
310
+ "mode": "full",
311
+ "window_count": 0,
312
+ "escalated_layers": [],
313
+ "exact_component_backend": exact_selection.to_json(),
314
+ }
315
+ else:
316
+ canonical_start = perf_counter()
317
+ old_canonical_ids, new_canonical_ids = _shared_canonical_ids(
318
+ old_extraction.buffer,
319
+ new_extraction.buffer,
320
+ old_intern=cached_geometry[0].intern if cached_geometry[0] is not None else None,
321
+ new_intern=cached_geometry[1].intern if cached_geometry[1] is not None else None,
322
+ )
323
+ timings["canonical_s"] = perf_counter() - canonical_start
324
+ tile_config = TileConfig(tile_size=config.tile_size)
325
+ prefilter_start = perf_counter()
326
+ prefilter = _run_tile_prefilter(
327
+ backend_info,
328
+ old_extraction.buffer,
329
+ old_canonical_ids,
330
+ new_extraction.buffer,
331
+ new_canonical_ids,
332
+ tile_config,
333
+ )
334
+ timings["prefilter_s"] = perf_counter() - prefilter_start
335
+ spatial_start = perf_counter()
336
+ old_index = cached_geometry[0].spatial_index if cached_geometry[0] is not None else SpatialIndex.from_buffer(old_extraction.buffer)
337
+ new_index = cached_geometry[1].spatial_index if cached_geometry[1] is not None else SpatialIndex.from_buffer(new_extraction.buffer)
338
+ timings["spatial_index_s"] = perf_counter() - spatial_start
339
+ window_start = perf_counter()
340
+ windows = build_candidate_windows(
341
+ prefilter,
342
+ tile_config,
343
+ old_index,
344
+ new_index,
345
+ WindowConfig(),
346
+ )
347
+ timings["window_build_s"] = perf_counter() - window_start
348
+ if _should_use_layer_exact(backend_info, old_extraction.buffer, new_extraction.buffer, windows):
349
+ candidate_layers = tuple(sorted({window.layer for window in windows}))
350
+ exact_oracle_start = perf_counter()
351
+ exact_result = run_full_exact_oracle(old_extraction.buffer, new_extraction.buffer, layers=set(candidate_layers))
352
+ timings["exact_oracle_s"] = perf_counter() - exact_oracle_start
353
+ windowed = WindowedExactResult(exact_result.layers, escalated_layers=candidate_layers)
354
+ exact_fallback = {
355
+ "mode": "candidate-layer-exact",
356
+ "window_count": len(windows),
357
+ "escalated_layers": [layer.to_json() for layer in candidate_layers],
358
+ "failures": [],
359
+ }
360
+ else:
361
+ exact_oracle_start = perf_counter()
362
+ windowed = run_windowed_exact_oracle(
363
+ old_extraction.buffer,
364
+ new_extraction.buffer,
365
+ windows,
366
+ old_index=old_index,
367
+ new_index=new_index,
368
+ )
369
+ timings["exact_oracle_s"] = perf_counter() - exact_oracle_start
370
+ exact_result = windowed.exact_result
371
+ exact_fallback = {
372
+ "mode": "windowed",
373
+ "window_count": len(windows),
374
+ "escalated_layers": [layer.to_json() for layer in windowed.escalated_layers],
375
+ "failures": [failure.to_json() for failure in windowed.failures],
376
+ }
377
+ if windows:
378
+ exact_verification_complete = False
379
+ layer_exact_status = "windowed"
380
+ warnings = (
381
+ "Windowed verification is localized and does not claim full-file exact XOR equivalence; "
382
+ "run exact=True for the full gdstk oracle.",
383
+ )
384
+ if config.geometry_cache_dir is not None:
385
+ pair_cache_start = perf_counter()
386
+ pair_cache_policy = classify_exact_differences(
387
+ exact_result,
388
+ config.allowed_regions,
389
+ failure_policy=config.failure_policy,
390
+ )
391
+ pair_cache_key = write_cached_pair_verdict(
392
+ config,
393
+ old_top_name=old_top_name,
394
+ new_top_name=new_top_name,
395
+ exact_result=exact_result,
396
+ prefilter=prefilter,
397
+ windows=windows,
398
+ exact_fallback=exact_fallback,
399
+ fast_status=pair_cache_policy.status.value,
400
+ exact_verification_complete=exact_verification_complete,
401
+ layer_exact_status=layer_exact_status,
402
+ warnings=warnings,
403
+ )
404
+ timings["pair_verdict_cache_write_s"] = perf_counter() - pair_cache_start
405
+ if pair_cache_key is not None:
406
+ timings["pair_verdict_cache_key"] = pair_cache_key
407
+ if verify_exact_after_fast and not exact_verification_complete:
408
+ geometry_policy_result = classify_exact_differences(
409
+ exact_result,
410
+ config.allowed_regions,
411
+ failure_policy=config.failure_policy,
412
+ )
413
+ geometry_status_s = perf_counter() - start
414
+ geometry_mode = str(exact_fallback.get("mode", "geometry"))
415
+ full_exact_start = perf_counter()
416
+ exact_selection = _select_exact_component_backend(exact_component_backend)
417
+ exact_result = run_full_exact_oracle(
418
+ old_extraction.buffer,
419
+ new_extraction.buffer,
420
+ component_partition=exact_selection.component_partition,
421
+ component_executor=exact_selection.executor,
422
+ )
423
+ timings["full_exact_oracle_s"] = perf_counter() - full_exact_start
424
+ timings["geometry_status_s"] = geometry_status_s
425
+ exact_verification_complete = True
426
+ layer_exact_status = "complete"
427
+ warnings = (
428
+ "Geometry prefilter/windowed verdict was produced first; full exact gdstk verification was run afterward and supplies the polygon-level evidence.",
429
+ )
430
+ exact_fallback = {
431
+ "mode": f"{geometry_mode}-plus-full-exact",
432
+ "geometry_mode": geometry_mode,
433
+ "window_count": exact_fallback.get("window_count", len(windows)),
434
+ "escalated_layers": exact_fallback.get("escalated_layers", []),
435
+ "failures": exact_fallback.get("failures", []),
436
+ "fast_status": geometry_policy_result.status.value,
437
+ "exact_component_backend": exact_selection.to_json(),
438
+ }
439
+ timings["exact_s"] = perf_counter() - start - timings["extract_s"]
440
+
441
+ policy_result = classify_exact_differences(
442
+ exact_result,
443
+ config.allowed_regions,
444
+ failure_policy=config.failure_policy,
445
+ )
446
+ artifacts: dict[str, str] = {}
447
+ if diff_gds_path is not None:
448
+ artifacts["diff_gds"] = str(write_diff_gds(policy_result, diff_gds_path).path)
449
+ if markdown_path is not None:
450
+ markdown_text = render_design_history_entry(
451
+ "GDS comparator result",
452
+ policy_result,
453
+ diff_gds_path=artifacts.get("diff_gds"),
454
+ tests_run=("gdsdiff",),
455
+ )
456
+ markdown_path = Path(markdown_path)
457
+ markdown_path.parent.mkdir(parents=True, exist_ok=True)
458
+ markdown_path.write_text(markdown_text, encoding="utf-8")
459
+ artifacts["markdown"] = str(markdown_path)
460
+ if exact_diff_markdown_path is not None:
461
+ inventory_delta_start = perf_counter()
462
+ polygon_inventory_delta = (
463
+ build_polygon_inventory_delta(old_extraction.buffer, new_extraction.buffer)
464
+ if exact_verification_complete
465
+ else None
466
+ )
467
+ timings["polygon_inventory_delta_s"] = perf_counter() - inventory_delta_start
468
+ artifacts["exact_diff_markdown"] = str(
469
+ write_exact_diff_markdown(
470
+ policy_result,
471
+ exact_diff_markdown_path,
472
+ title="GDS comparator exact diff",
473
+ source_mode=exact_fallback["mode"],
474
+ exact_verification_complete=exact_verification_complete,
475
+ polygon_inventory_delta=polygon_inventory_delta,
476
+ ).path
477
+ )
478
+
479
+ layer_reports = build_layer_reports(
480
+ exact_result,
481
+ policy_result=policy_result,
482
+ presence_by_layer=_presence_by_layer(old_extraction.buffer, new_extraction.buffer),
483
+ polygon_counts_by_layer=_polygon_counts_by_layer(old_extraction.buffer, new_extraction.buffer),
484
+ candidate_tile_counts_by_layer=_candidate_tile_counts(prefilter),
485
+ exact_window_counts_by_layer=_window_counts(windows),
486
+ exact_status=layer_exact_status,
487
+ )
488
+ timings["total_s"] = perf_counter() - start
489
+ report = CompareReport(
490
+ status=policy_result.status,
491
+ exact_verification_complete=exact_verification_complete,
492
+ old_input=_input_json(old_extraction, old_metadata.to_json()),
493
+ new_input=_input_json(new_extraction, new_metadata.to_json()),
494
+ semantics=config.to_json_dict(),
495
+ selection={"old_top": old_extraction.top_name, "new_top": new_extraction.top_name, "layers": config.to_json_dict()["layers"]},
496
+ backend=backend_info
497
+ | {
498
+ "exact_mode": exact,
499
+ "fast_shortcuts_enabled": use_fast_shortcuts,
500
+ "geometry_cache_enabled": config.geometry_cache_dir is not None,
501
+ },
502
+ prefilter=prefilter.to_json(),
503
+ exact_fallback=exact_fallback,
504
+ layers=layer_reports,
505
+ allowed_policy=policy_result.effective_policy,
506
+ unexpected_summary={
507
+ "expected_area_dbu2": policy_result.to_json()["expected_area_dbu2"],
508
+ "unexpected_area_dbu2": policy_result.to_json()["unexpected_area_dbu2"],
509
+ },
510
+ timings=timings,
511
+ warnings=warnings,
512
+ artifacts=artifacts,
513
+ command=command,
514
+ failure_policy=config.failure_policy,
515
+ )
516
+ if json_report_path is not None:
517
+ artifacts["json_report"] = str(json_report_path)
518
+ report = _report_with_artifacts(report, artifacts)
519
+ report.write_json(json_report_path)
520
+
521
+ return CompareRunResult(
522
+ status=policy_result.status,
523
+ report=report,
524
+ exact_result=exact_result,
525
+ policy_result=policy_result,
526
+ prefilter=prefilter,
527
+ windows=windows,
528
+ artifacts=artifacts,
529
+ timings=timings,
530
+ )
531
+
532
+
533
+ def compare_gds_paths(old_path: str | Path, new_path: str | Path, **kwargs) -> CompareRunResult:
534
+ config_kwargs = {key: value for key, value in kwargs.items() if key in CompareConfig.__dataclass_fields__}
535
+ run_kwargs = {key: value for key, value in kwargs.items() if key not in config_kwargs}
536
+ return compare_gds_files(CompareConfig(old_path=old_path, new_path=new_path, **config_kwargs), **run_kwargs)
537
+
538
+
539
+ def _extract_pair(
540
+ config: CompareConfig,
541
+ *,
542
+ old_top_name: str | None,
543
+ new_top_name: str | None,
544
+ ) -> tuple[ExtractionResult, ExtractionResult]:
545
+ old_args = (config.old_path, old_top_name, config.layers, config.dbu, config.path_policy)
546
+ new_args = (config.new_path, new_top_name, config.layers, config.dbu, config.path_policy)
547
+ if _should_extract_pair_in_parallel(config):
548
+ try:
549
+ with ProcessPoolExecutor(
550
+ max_workers=2,
551
+ mp_context=portable_process_context(),
552
+ initializer=mark_portable_process_worker,
553
+ ) as executor:
554
+ old_future = executor.submit(_extract_one, old_args)
555
+ new_future = executor.submit(_extract_one, new_args)
556
+ return old_future.result(), new_future.result()
557
+ except (BrokenProcessPool, OSError):
558
+ pass
559
+ return _extract_one(old_args), _extract_one(new_args)
560
+
561
+
562
+ def _cached_geometry_pair(
563
+ config: CompareConfig,
564
+ *,
565
+ old_top_name: str | None,
566
+ new_top_name: str | None,
567
+ ) -> tuple[CachedGeometry, CachedGeometry]:
568
+ if config.geometry_cache_dir is None:
569
+ raise ValueError("geometry cache directory is not configured")
570
+ old_args = (config.geometry_cache_dir, config.old_path, old_top_name, config.layers, config.dbu, config.path_policy)
571
+ new_args = (config.geometry_cache_dir, config.new_path, new_top_name, config.layers, config.dbu, config.path_policy)
572
+ if _should_extract_pair_in_parallel(config):
573
+ try:
574
+ with ProcessPoolExecutor(
575
+ max_workers=2,
576
+ mp_context=portable_process_context(),
577
+ initializer=mark_portable_process_worker,
578
+ ) as executor:
579
+ old_future = executor.submit(_cached_geometry_one, old_args)
580
+ new_future = executor.submit(_cached_geometry_one, new_args)
581
+ return old_future.result(), new_future.result()
582
+ except (BrokenProcessPool, OSError):
583
+ pass
584
+ return _cached_geometry_one(old_args), _cached_geometry_one(new_args)
585
+
586
+
587
+ def _cached_geometry_one(args) -> CachedGeometry:
588
+ cache_dir, path, top_name, layers, dbu, path_policy = args
589
+ return load_or_build_cached_geometry(
590
+ cache_dir=cache_dir,
591
+ path=path,
592
+ top_name=top_name,
593
+ layers=layers,
594
+ meters_per_dbu=dbu,
595
+ path_policy=path_policy,
596
+ )
597
+
598
+
599
+ def _extract_one(args) -> ExtractionResult:
600
+ path, top_name, layers, dbu, path_policy = args
601
+ return extract_gds_polygons(
602
+ path,
603
+ top_name=top_name,
604
+ layers=layers,
605
+ meters_per_dbu=dbu,
606
+ path_policy=path_policy,
607
+ )
608
+
609
+
610
+ def _should_extract_pair_in_parallel(config: CompareConfig) -> bool:
611
+ try:
612
+ total_size = Path(config.old_path).stat().st_size + Path(config.new_path).stat().st_size
613
+ except OSError:
614
+ return False
615
+ return total_size >= _parallel_extract_pair_min_bytes()
616
+
617
+
618
+ def _parallel_extract_pair_min_bytes() -> int:
619
+ value = get_environment(
620
+ "GDSDIFF_PARALLEL_EXTRACT_PAIR_MIN_BYTES",
621
+ "GDS_GPU_COMPARE_PARALLEL_EXTRACT_PAIR_MIN_BYTES",
622
+ "",
623
+ )
624
+ return _positive_int_value(value, 1_000_000)
625
+
626
+
627
+ def _positive_int_value(raw: str | None, default: int) -> int:
628
+ try:
629
+ value = int(raw or "")
630
+ except ValueError:
631
+ return default
632
+ return value if value > 0 else default
633
+
634
+
635
+ def _try_native_structural_guard_path(
636
+ config: CompareConfig,
637
+ *,
638
+ backend_info: dict[str, object],
639
+ old_top_name: str | None,
640
+ new_top_name: str | None,
641
+ command: tuple[str, ...],
642
+ json_report_path: str | Path | None,
643
+ markdown_path: str | Path | None,
644
+ exact_diff_markdown_path: str | Path | None,
645
+ verify_exact_after_fast: bool,
646
+ exact_component_backend: str,
647
+ start: float,
648
+ ) -> CompareRunResult | None:
649
+ if config.allowed_regions or config.layers != "all":
650
+ return None
651
+ try:
652
+ from .backends.structural_ctypes import summarize_gds_structure_native
653
+
654
+ structural_start = perf_counter()
655
+ old_summary = summarize_gds_structure_native(config.old_path, top_name=old_top_name, counts_only=True)
656
+ new_summary = summarize_gds_structure_native(config.new_path, top_name=new_top_name, counts_only=True)
657
+ if old_summary.flattened_shape_count == new_summary.flattened_shape_count:
658
+ old_summary = summarize_gds_structure_native(config.old_path, top_name=old_top_name)
659
+ new_summary = summarize_gds_structure_native(config.new_path, top_name=new_top_name)
660
+ structural_s = perf_counter() - structural_start
661
+ except Exception:
662
+ return None
663
+ counts_match = old_summary.flattened_shape_count == new_summary.flattened_shape_count
664
+ hashes_match = old_summary.structural_hash == new_summary.structural_hash
665
+ if counts_match and hashes_match:
666
+ return None
667
+ structural_mismatch_kind = "hash" if counts_match else "count"
668
+
669
+ fragment = DiffFragment(points=((0, 0), (1, 0), (1, 1), (0, 1)), bbox=(0, 0, 1, 1), twice_area=2)
670
+ exact_result = ExactOracleResult((LayerExactDiff(layer=LayerSpec(0, 0), new_minus_old=(fragment,), xor=(fragment,)),))
671
+ policy_result = classify_exact_differences(exact_result, failure_policy=config.failure_policy)
672
+ fast_status = policy_result.status
673
+ exact_complete = False
674
+ verification: _FullExactVerification | None = None
675
+ if verify_exact_after_fast:
676
+ verification = _run_full_exact_verification(config, exact_component_backend=exact_component_backend)
677
+ exact_result = verification.exact_result
678
+ policy_result = verification.policy_result
679
+ exact_complete = True
680
+
681
+ timings = {
682
+ "extract_s": 0.0,
683
+ "exact_s": structural_s,
684
+ "fast_status_s": structural_s,
685
+ "structural_guard_s": structural_s,
686
+ }
687
+ old_input = {"path": str(config.old_path), "selected_top": old_top_name, "structural_summary": old_summary.to_json()}
688
+ new_input = {"path": str(config.new_path), "selected_top": new_top_name, "structural_summary": new_summary.to_json()}
689
+ selection = {"old_top": old_top_name, "new_top": new_top_name, "layers": "all"}
690
+ layers = build_layer_reports(exact_result, policy_result=policy_result)
691
+ if verification is not None:
692
+ timings.update(
693
+ {
694
+ "extract_s": verification.extract_s,
695
+ "exact_s": verification.exact_s,
696
+ "exact_verification_s": verification.extract_s + verification.exact_s,
697
+ }
698
+ )
699
+ old_input = verification.old_input
700
+ new_input = verification.new_input
701
+ selection = verification.selection
702
+ layers = verification.layers
703
+
704
+ prefilter = CpuTilePrefilterResult(candidate_tiles=())
705
+ windows: tuple[CandidateWindow, ...] = ()
706
+ artifacts: dict[str, str] = {}
707
+ exact_fallback_mode = "structural-count-fast-plus-full-exact" if verification is not None else "structural-count-fail-closed"
708
+ warnings = (
709
+ "Native structural guard used flattened shape counts for a fail-closed fast status; full exact gdstk verification was run afterward and supplies polygon-level evidence."
710
+ if verification is not None
711
+ else "Native structural guard used flattened shape counts for a fail-closed fast status; run exact verification for polygon-level evidence."
712
+ )
713
+ exact_fallback = {
714
+ "mode": exact_fallback_mode,
715
+ "window_count": 0,
716
+ "escalated_layers": [],
717
+ "failures": [],
718
+ "fast_status": fast_status.value,
719
+ "old_flattened_shape_count": old_summary.flattened_shape_count,
720
+ "new_flattened_shape_count": new_summary.flattened_shape_count,
721
+ "old_structural_hash": f"{old_summary.structural_hash:016x}",
722
+ "new_structural_hash": f"{new_summary.structural_hash:016x}",
723
+ "structural_mismatch_kind": structural_mismatch_kind,
724
+ }
725
+ if verification is not None:
726
+ exact_fallback["exact_component_backend"] = verification.exact_component_backend
727
+ if markdown_path is not None:
728
+ markdown_path = Path(markdown_path)
729
+ markdown_path.parent.mkdir(parents=True, exist_ok=True)
730
+ markdown_path.write_text(
731
+ render_design_history_entry(
732
+ "GDS comparator structural-guard result",
733
+ policy_result,
734
+ tests_run=("gdsdiff structural-guard",),
735
+ notes=warnings,
736
+ ),
737
+ encoding="utf-8",
738
+ )
739
+ artifacts["markdown"] = str(markdown_path)
740
+ if exact_diff_markdown_path is not None:
741
+ artifacts["exact_diff_markdown"] = str(
742
+ write_exact_diff_markdown(
743
+ policy_result,
744
+ exact_diff_markdown_path,
745
+ title="GDS comparator structural-guard exact diff",
746
+ source_mode=exact_fallback_mode,
747
+ exact_verification_complete=exact_complete,
748
+ polygon_inventory_delta=verification.polygon_inventory_delta if verification is not None else None,
749
+ ).path
750
+ )
751
+ timings["total_s"] = perf_counter() - start
752
+ report = CompareReport(
753
+ status=policy_result.status,
754
+ exact_verification_complete=exact_complete,
755
+ old_input=old_input,
756
+ new_input=new_input,
757
+ semantics=config.to_json_dict(),
758
+ selection=selection,
759
+ backend=backend_info
760
+ | {
761
+ "exact_mode": False,
762
+ "fast_shortcuts_enabled": True,
763
+ "structural_guard": True,
764
+ "exact_verification_after_fast": verification is not None,
765
+ },
766
+ prefilter=prefilter.to_json(),
767
+ exact_fallback=exact_fallback,
768
+ layers=layers,
769
+ allowed_policy=policy_result.effective_policy,
770
+ unexpected_summary={
771
+ "expected_area_dbu2": policy_result.to_json()["expected_area_dbu2"],
772
+ "unexpected_area_dbu2": policy_result.to_json()["unexpected_area_dbu2"],
773
+ },
774
+ timings=timings,
775
+ warnings=warnings,
776
+ artifacts=artifacts,
777
+ command=command,
778
+ failure_policy=config.failure_policy,
779
+ )
780
+ if json_report_path is not None:
781
+ artifacts["json_report"] = str(json_report_path)
782
+ report = _report_with_artifacts(report, artifacts)
783
+ report.write_json(json_report_path)
784
+ return CompareRunResult(
785
+ status=policy_result.status,
786
+ report=report,
787
+ exact_result=exact_result,
788
+ policy_result=policy_result,
789
+ prefilter=prefilter,
790
+ windows=windows,
791
+ artifacts=artifacts,
792
+ timings=timings,
793
+ )
794
+
795
+
796
+ def _try_native_geometry_guard_path(
797
+ config: CompareConfig,
798
+ *,
799
+ backend_info: dict[str, object],
800
+ old_top_name: str | None,
801
+ new_top_name: str | None,
802
+ command: tuple[str, ...],
803
+ json_report_path: str | Path | None,
804
+ markdown_path: str | Path | None,
805
+ exact_diff_markdown_path: str | Path | None,
806
+ verify_exact_after_fast: bool,
807
+ exact_component_backend: str,
808
+ start: float,
809
+ ) -> CompareRunResult | None:
810
+ if config.allowed_regions or config.layers != "all":
811
+ return None
812
+ try:
813
+ from .backends.geometry_ctypes import compare_gds_geometry_native
814
+
815
+ guard_start = perf_counter()
816
+ native_compare = compare_gds_geometry_native(
817
+ config.old_path,
818
+ config.new_path,
819
+ old_top_name=old_top_name,
820
+ new_top_name=new_top_name,
821
+ )
822
+ guard_s = perf_counter() - guard_start
823
+ except Exception:
824
+ return None
825
+
826
+ old_summary = native_compare.old_summary
827
+ new_summary = native_compare.new_summary
828
+ if native_compare.summaries_match:
829
+ return None
830
+
831
+ if old_summary.flattened_shape_count != new_summary.flattened_shape_count:
832
+ mismatch_kind = "count"
833
+ elif _geometry_layer_json(old_summary) != _geometry_layer_json(new_summary):
834
+ mismatch_kind = "layer-summary"
835
+ else:
836
+ mismatch_kind = "hash"
837
+
838
+ fragment = DiffFragment(points=((0, 0), (1, 0), (1, 1), (0, 1)), bbox=(0, 0, 1, 1), twice_area=2)
839
+ exact_result = ExactOracleResult((LayerExactDiff(layer=LayerSpec(0, 0), new_minus_old=(fragment,), xor=(fragment,)),))
840
+ policy_result = classify_exact_differences(exact_result, failure_policy=config.failure_policy)
841
+ fast_status = policy_result.status
842
+ exact_complete = False
843
+ verification: _FullExactVerification | None = None
844
+ if verify_exact_after_fast:
845
+ verification = _run_full_exact_verification(config, exact_component_backend=exact_component_backend)
846
+ exact_result = verification.exact_result
847
+ policy_result = verification.policy_result
848
+ exact_complete = True
849
+
850
+ timings = {
851
+ "extract_s": 0.0,
852
+ "exact_s": guard_s,
853
+ "fast_status_s": guard_s,
854
+ "native_geometry_guard_s": guard_s,
855
+ }
856
+ old_input = {"path": str(config.old_path), "selected_top": old_top_name, "native_geometry_summary": old_summary.to_json()}
857
+ new_input = {"path": str(config.new_path), "selected_top": new_top_name, "native_geometry_summary": new_summary.to_json()}
858
+ selection = {"old_top": old_top_name, "new_top": new_top_name, "layers": "all"}
859
+ layers = build_layer_reports(exact_result, policy_result=policy_result)
860
+ if verification is not None:
861
+ timings.update(
862
+ {
863
+ "extract_s": verification.extract_s,
864
+ "exact_s": verification.exact_s,
865
+ "exact_verification_s": verification.extract_s + verification.exact_s,
866
+ }
867
+ )
868
+ old_input = verification.old_input
869
+ new_input = verification.new_input
870
+ selection = verification.selection
871
+ layers = verification.layers
872
+
873
+ prefilter = CpuTilePrefilterResult(candidate_tiles=())
874
+ windows: tuple[CandidateWindow, ...] = ()
875
+ artifacts: dict[str, str] = {}
876
+ exact_fallback_mode = "native-geometry-fast-plus-full-exact" if verification is not None else "native-geometry-fail-closed"
877
+ warnings = (
878
+ "Native geometry guard used custom GDS parsing for a fail-closed fast status; full exact gdstk verification was run afterward and supplies polygon-level evidence."
879
+ if verification is not None
880
+ else "Native geometry guard used custom GDS parsing for a fail-closed fast status; run exact verification for polygon-level evidence."
881
+ )
882
+ exact_fallback = {
883
+ "mode": exact_fallback_mode,
884
+ "window_count": 0,
885
+ "escalated_layers": [],
886
+ "failures": [],
887
+ "fast_status": fast_status.value,
888
+ "old_flattened_shape_count": old_summary.flattened_shape_count,
889
+ "new_flattened_shape_count": new_summary.flattened_shape_count,
890
+ "old_geometry_hash": f"{old_summary.geometry_hash:016x}",
891
+ "new_geometry_hash": f"{new_summary.geometry_hash:016x}",
892
+ "geometry_mismatch_kind": mismatch_kind,
893
+ "old_layer_summary": _geometry_layer_json(old_summary),
894
+ "new_layer_summary": _geometry_layer_json(new_summary),
895
+ }
896
+ if verification is not None:
897
+ exact_fallback["exact_component_backend"] = verification.exact_component_backend
898
+ if markdown_path is not None:
899
+ markdown_path = Path(markdown_path)
900
+ markdown_path.parent.mkdir(parents=True, exist_ok=True)
901
+ markdown_path.write_text(
902
+ render_design_history_entry(
903
+ "GDS comparator native-geometry-guard result",
904
+ policy_result,
905
+ tests_run=("gdsdiff native-geometry-guard",),
906
+ notes=warnings,
907
+ ),
908
+ encoding="utf-8",
909
+ )
910
+ artifacts["markdown"] = str(markdown_path)
911
+ if exact_diff_markdown_path is not None:
912
+ artifacts["exact_diff_markdown"] = str(
913
+ write_exact_diff_markdown(
914
+ policy_result,
915
+ exact_diff_markdown_path,
916
+ title="GDS comparator native-geometry-guard exact diff",
917
+ source_mode=exact_fallback_mode,
918
+ exact_verification_complete=exact_complete,
919
+ polygon_inventory_delta=verification.polygon_inventory_delta if verification is not None else None,
920
+ ).path
921
+ )
922
+ timings["total_s"] = perf_counter() - start
923
+ report = CompareReport(
924
+ status=policy_result.status,
925
+ exact_verification_complete=exact_complete,
926
+ old_input=old_input,
927
+ new_input=new_input,
928
+ semantics=config.to_json_dict(),
929
+ selection=selection,
930
+ backend=backend_info
931
+ | {
932
+ "exact_mode": False,
933
+ "fast_shortcuts_enabled": True,
934
+ "native_geometry_guard": True,
935
+ "exact_verification_after_fast": verification is not None,
936
+ },
937
+ prefilter=prefilter.to_json(),
938
+ exact_fallback=exact_fallback,
939
+ layers=layers,
940
+ allowed_policy=policy_result.effective_policy,
941
+ unexpected_summary={
942
+ "expected_area_dbu2": policy_result.to_json()["expected_area_dbu2"],
943
+ "unexpected_area_dbu2": policy_result.to_json()["unexpected_area_dbu2"],
944
+ },
945
+ timings=timings,
946
+ warnings=warnings,
947
+ artifacts=artifacts,
948
+ command=command,
949
+ failure_policy=config.failure_policy,
950
+ )
951
+ if json_report_path is not None:
952
+ artifacts["json_report"] = str(json_report_path)
953
+ report = _report_with_artifacts(report, artifacts)
954
+ report.write_json(json_report_path)
955
+ return CompareRunResult(
956
+ status=policy_result.status,
957
+ report=report,
958
+ exact_result=exact_result,
959
+ policy_result=policy_result,
960
+ prefilter=prefilter,
961
+ windows=windows,
962
+ artifacts=artifacts,
963
+ timings=timings,
964
+ )
965
+
966
+
967
+ def _try_gpu_polygon_evidence_path(
968
+ config: CompareConfig,
969
+ *,
970
+ backend_info: dict[str, object],
971
+ old_top_name: str | None,
972
+ new_top_name: str | None,
973
+ command: tuple[str, ...],
974
+ json_report_path: str | Path | None,
975
+ markdown_path: str | Path | None,
976
+ exact_diff_markdown_path: str | Path | None,
977
+ audit_cpu_oracle: bool,
978
+ exact_component_backend: str,
979
+ start: float,
980
+ ) -> CompareRunResult:
981
+ artifacts: dict[str, str] = {}
982
+ prefilter = CpuTilePrefilterResult(candidate_tiles=())
983
+ windows: tuple[CandidateWindow, ...] = ()
984
+ record_delta = None
985
+ coverage_result = None
986
+ surplus_result = None
987
+ surplus_probe_source = None
988
+ polygon_fragment_exact_result = None
989
+ polygon_component_area = None
990
+ polygon_inventory_delta = None
991
+ source_edge_cpu_fallback = None
992
+ source_edge_cpu_verification = None
993
+ native_parse_cpu_verification = None
994
+
995
+ cpu_neutral_fallback = backend_info.get("name") == Backend.CPU.value
996
+ gpu_runtime_available = bool(backend_info.get("cuda_ctypes_available") or backend_info.get("metal_ctypes_available"))
997
+ if cpu_neutral_fallback or (audit_cpu_oracle and not gpu_runtime_available):
998
+ audit_start = perf_counter()
999
+ audit_backend = _default_cpu_audit_component_backend(exact_component_backend)
1000
+ verification = _run_full_exact_verification(
1001
+ config,
1002
+ exact_component_backend=audit_backend,
1003
+ force_small_component_partition=True,
1004
+ )
1005
+ timings = {
1006
+ "native_parse_s": 0.0,
1007
+ "h2d_upload_s": 0.0,
1008
+ "gpu_broadphase_s": 0.0,
1009
+ "gpu_xor_s": 0.0,
1010
+ "gpu_record_emit_s": 0.0,
1011
+ "markdown_format_s": 0.0,
1012
+ "cpu_oracle_audit_s": perf_counter() - audit_start,
1013
+ "extract_s": verification.extract_s,
1014
+ "exact_s": verification.exact_s,
1015
+ }
1016
+ policy_result = verification.policy_result
1017
+ exact_result = verification.exact_result
1018
+ polygon_inventory_delta = verification.polygon_inventory_delta
1019
+ exact_complete = True
1020
+ warnings = (
1021
+ (
1022
+ "No suitable GPU backend was selected; CPU gdstk oracle produced complete neutral exact evidence."
1023
+ if cpu_neutral_fallback
1024
+ else "Requested gpu-polygon evidence engine is not implemented yet; CPU gdstk oracle audit was run explicitly via --audit-cpu-oracle."
1025
+ ),
1026
+ )
1027
+ exact_fallback = {
1028
+ "mode": (
1029
+ "cpu-gdstk-oracle-neutral-fallback"
1030
+ if cpu_neutral_fallback
1031
+ else "gpu-polygon-unavailable-plus-cpu-oracle-audit"
1032
+ ),
1033
+ "engine": "gdstk-oracle" if cpu_neutral_fallback else "gpu-polygon",
1034
+ "engine_status": "complete" if cpu_neutral_fallback else "not-implemented",
1035
+ "audit_cpu_oracle": not cpu_neutral_fallback,
1036
+ "window_count": 0,
1037
+ "escalated_layers": [],
1038
+ "failures": []
1039
+ if cpu_neutral_fallback
1040
+ else [
1041
+ {
1042
+ "reason": "gpu-polygon exact numeric diff record engine is not implemented yet",
1043
+ }
1044
+ ],
1045
+ "exact_component_backend": verification.exact_component_backend,
1046
+ }
1047
+ old_input = verification.old_input
1048
+ new_input = verification.new_input
1049
+ selection = verification.selection
1050
+ layers = verification.layers
1051
+ if exact_diff_markdown_path is not None:
1052
+ markdown_start = perf_counter()
1053
+ markdown_written = write_exact_diff_markdown(
1054
+ policy_result,
1055
+ exact_diff_markdown_path,
1056
+ title=(
1057
+ "GDS comparator CPU fallback exact diff"
1058
+ if cpu_neutral_fallback
1059
+ else "GDS comparator CPU-audited gpu-polygon exact diff"
1060
+ ),
1061
+ source_mode=exact_fallback["mode"],
1062
+ exact_verification_complete=True,
1063
+ polygon_inventory_delta=polygon_inventory_delta,
1064
+ )
1065
+ _record_exact_markdown_artifacts(artifacts, markdown_written)
1066
+ timings["markdown_format_s"] = perf_counter() - markdown_start
1067
+ else:
1068
+ parse_start = perf_counter()
1069
+ parse_failures: list[dict[str, str]] = []
1070
+ record_delta_failures: list[dict[str, str]] = []
1071
+ coverage_failures: list[dict[str, str]] = []
1072
+ component_area_failures: list[dict[str, str]] = []
1073
+ old_native = None
1074
+ new_native = None
1075
+ native_cache_timings: dict[str, object] = {}
1076
+ try:
1077
+ old_native, new_native, native_cache_timings = _gpu_polygon_native_inputs(
1078
+ config,
1079
+ old_top_name=old_top_name,
1080
+ new_top_name=new_top_name,
1081
+ )
1082
+ except PolygonExtractCtypesUnavailable as exc:
1083
+ common_top = _resolve_single_common_top_after_native_parse_failure(config, str(exc), old_top_name, new_top_name)
1084
+ if common_top is None:
1085
+ parse_failures.append({"stage": "native-parse", "reason": str(exc)})
1086
+ else:
1087
+ old_top_name = common_top
1088
+ new_top_name = common_top
1089
+ try:
1090
+ old_native, new_native, native_cache_timings = _gpu_polygon_native_inputs(
1091
+ config,
1092
+ old_top_name=old_top_name,
1093
+ new_top_name=new_top_name,
1094
+ )
1095
+ except PolygonExtractCtypesUnavailable as retry_exc:
1096
+ parse_failures.append({"stage": "native-parse", "reason": str(retry_exc)})
1097
+ native_parse_s = perf_counter() - parse_start
1098
+ exact_result = ExactOracleResult(())
1099
+ policy_result = classify_exact_differences(exact_result, config.allowed_regions, failure_policy=config.failure_policy)
1100
+ exact_complete = False
1101
+ timings = {
1102
+ "native_parse_s": native_parse_s,
1103
+ "h2d_upload_s": 0.0,
1104
+ "gpu_broadphase_s": 0.0,
1105
+ "gpu_xor_s": 0.0,
1106
+ "gpu_record_emit_s": 0.0,
1107
+ "markdown_format_s": 0.0,
1108
+ "cpu_oracle_audit_s": 0.0,
1109
+ "gpu_source_record_delta_s": 0.0,
1110
+ "canonical_surplus_coverage_s": 0.0,
1111
+ "record_delta_surplus_coverage_s": 0.0,
1112
+ "polygon_component_build_s": 0.0,
1113
+ "polygon_context_build_s": 0.0,
1114
+ "gpu_context_index_s": 0.0,
1115
+ "gpu_edge_cancel_split_s": 0.0,
1116
+ "gpu_polygon_component_area_s": 0.0,
1117
+ "gpu_polygon_component_fragment_s": 0.0,
1118
+ "boundary_loop_assembly_s": 0.0,
1119
+ "cuda_release_s": 0.0,
1120
+ "metal_release_s": 0.0,
1121
+ "polygon_inventory_delta_s": 0.0,
1122
+ }
1123
+ timings.update(native_cache_timings)
1124
+ gpu_name = backend_info.get("name")
1125
+ gpu_rect_available = (
1126
+ (gpu_name == Backend.CUDA.value and backend_info.get("cuda_ctypes_available"))
1127
+ or (gpu_name == Backend.METAL.value and backend_info.get("metal_ctypes_available"))
1128
+ )
1129
+ if parse_failures and audit_cpu_oracle:
1130
+ audit_start = perf_counter()
1131
+ audit_backend = _default_cpu_audit_component_backend(exact_component_backend)
1132
+ native_parse_cpu_verification = _run_full_exact_verification(
1133
+ config,
1134
+ exact_component_backend=audit_backend,
1135
+ force_small_component_partition=True,
1136
+ )
1137
+ timings["cpu_oracle_audit_s"] = perf_counter() - audit_start
1138
+ timings["extract_s"] = native_parse_cpu_verification.extract_s
1139
+ timings["exact_s"] = native_parse_cpu_verification.exact_s
1140
+ exact_result = native_parse_cpu_verification.exact_result
1141
+ policy_result = native_parse_cpu_verification.policy_result
1142
+ layers = native_parse_cpu_verification.layers
1143
+ polygon_inventory_delta = native_parse_cpu_verification.polygon_inventory_delta
1144
+ exact_complete = True
1145
+ if not parse_failures and old_native is not None and new_native is not None and gpu_rect_available:
1146
+ if _native_buffers_rectangle_coverage_eligible(old_native.buffer, new_native.buffer):
1147
+ try:
1148
+ coverage_result = (
1149
+ run_metal_rectangle_coverage_diff(old_native.buffer, new_native.buffer)
1150
+ if gpu_name == Backend.METAL.value
1151
+ else run_cuda_rectangle_coverage_diff(old_native.buffer, new_native.buffer)
1152
+ )
1153
+ timings["h2d_upload_s"] = coverage_result.stats.h2d_upload_s
1154
+ timings["gpu_xor_s"] = coverage_result.stats.gpu_xor_s
1155
+ timings["gpu_record_emit_s"] = coverage_result.stats.gpu_record_emit_s
1156
+ except RectCoverageUnsupported as exc:
1157
+ coverage_failures.append({"stage": "gpu-coverage-xor", "reason": str(exc)})
1158
+ else:
1159
+ coverage_failures.append(
1160
+ {
1161
+ "stage": "gpu-coverage-xor",
1162
+ "reason": "skipped rectangle coverage because native polygon vertex counts include non-rectangles",
1163
+ }
1164
+ )
1165
+ if coverage_result is None and gpu_name in {Backend.CUDA.value, Backend.METAL.value}:
1166
+ try:
1167
+ from .backends.cuda_ctypes import CudaCtypesUnavailable, run_cuda_polygon_record_delta
1168
+ from .backends.metal_ctypes import MetalCtypesUnavailable, run_metal_polygon_record_delta_from_hashes
1169
+
1170
+ record_start = perf_counter()
1171
+ if gpu_name == Backend.METAL.value:
1172
+ record_delta = run_metal_polygon_record_delta_from_hashes(old_native.buffer, new_native.buffer)
1173
+ else:
1174
+ record_delta = run_cuda_polygon_record_delta(old_native.buffer, new_native.buffer)
1175
+ timings["gpu_source_record_delta_s"] = perf_counter() - record_start
1176
+ except (CudaCtypesUnavailable, MetalCtypesUnavailable) as record_exc:
1177
+ record_delta_failures.append({"stage": "gpu-record-delta", "reason": str(record_exc)})
1178
+ if record_delta is not None:
1179
+ surplus_start = perf_counter()
1180
+ surplus_result = run_record_delta_surplus_coverage_probe(old_native.buffer, new_native.buffer, record_delta)
1181
+ surplus_probe_source = f"{_record_delta_accelerator(record_delta)}-record-delta"
1182
+ timings["record_delta_surplus_coverage_s"] = perf_counter() - surplus_start
1183
+ component_area_start = perf_counter()
1184
+ component_build_start = perf_counter()
1185
+ component_build = build_record_delta_surplus_components(old_native.buffer, new_native.buffer, record_delta)
1186
+ timings["polygon_component_build_s"] = perf_counter() - component_build_start
1187
+ context_build_start = perf_counter()
1188
+ context_build = add_overlapping_context_to_components(
1189
+ old_native.buffer,
1190
+ new_native.buffer,
1191
+ component_build.components,
1192
+ preferred_accelerator=_record_delta_accelerator(record_delta),
1193
+ )
1194
+ timings["polygon_context_build_s"] = perf_counter() - context_build_start
1195
+ if context_build.stats.context_index_backend.startswith(("cuda", "metal")):
1196
+ timings["gpu_context_index_s"] = context_build.stats.context_index_s
1197
+ assembly_start = perf_counter()
1198
+ source_edge_result = exact_result_from_source_edges(context_build.components)
1199
+ polygon_fragment_exact_result = source_edge_result.exact_result
1200
+ timings["boundary_loop_assembly_s"] = perf_counter() - assembly_start
1201
+ timings["gpu_edge_cancel_split_s"] = source_edge_result.stats.layer_merge_cuda_edge_cancel_s
1202
+ timings["gpu_polygon_component_area_s"] = perf_counter() - component_area_start
1203
+ source_edge_old_minus = sum(
1204
+ layer.old_minus_new_twice_area for layer in polygon_fragment_exact_result.layers
1205
+ )
1206
+ source_edge_new_minus = sum(
1207
+ layer.new_minus_old_twice_area for layer in polygon_fragment_exact_result.layers
1208
+ )
1209
+ if source_edge_result.stats.nested_direction_fragment_component_count:
1210
+ fallback_start = perf_counter()
1211
+ source_edge_cpu_verification = _run_full_exact_verification(
1212
+ config,
1213
+ exact_component_backend="gdstk",
1214
+ )
1215
+ timings["source_edge_cpu_oracle_fallback_s"] = perf_counter() - fallback_start
1216
+ source_edge_cpu_fallback = {
1217
+ "trigger": "nested-direction-fragments",
1218
+ "reason": (
1219
+ "source-edge loops contain nested same-direction fragments, "
1220
+ "which require hole-aware Boolean semantics"
1221
+ ),
1222
+ "nested_direction_fragment_component_count": (
1223
+ source_edge_result.stats.nested_direction_fragment_component_count
1224
+ ),
1225
+ "original_vs_cpu_oracle": _exact_result_audit_summary(
1226
+ polygon_fragment_exact_result,
1227
+ source_edge_cpu_verification.exact_result,
1228
+ ),
1229
+ "exact_component_backend": source_edge_cpu_verification.exact_component_backend,
1230
+ }
1231
+ polygon_fragment_exact_result = source_edge_cpu_verification.exact_result
1232
+ source_edge_old_minus = sum(
1233
+ layer.old_minus_new_twice_area for layer in polygon_fragment_exact_result.layers
1234
+ )
1235
+ source_edge_new_minus = sum(
1236
+ layer.new_minus_old_twice_area for layer in polygon_fragment_exact_result.layers
1237
+ )
1238
+ polygon_component_area = {
1239
+ "component_count": len(component_build.components),
1240
+ "context_component_count": len(context_build.components),
1241
+ "context_max_fragments_per_component": context_build.stats.max_fragments_per_component,
1242
+ "context_max_vertices_per_component": context_build.stats.max_vertices_per_component,
1243
+ "context_index_backend": context_build.stats.context_index_backend,
1244
+ "context_index_s": context_build.stats.context_index_s,
1245
+ "context_pair_count": context_build.stats.context_pair_count,
1246
+ "context_cuda_kernel_count": context_build.stats.context_cuda_kernel_count,
1247
+ "context_cuda_event_elapsed_s": context_build.stats.context_cuda_event_elapsed_s,
1248
+ "context_cuda_transferred_bytes": context_build.stats.context_cuda_transferred_bytes,
1249
+ "context_metal_kernel_count": context_build.stats.context_metal_kernel_count,
1250
+ "context_metal_event_elapsed_s": context_build.stats.context_metal_event_elapsed_s,
1251
+ "context_metal_transferred_bytes": context_build.stats.context_metal_transferred_bytes,
1252
+ "emitted_trapezoid_fragment_count": 0,
1253
+ "cuda_trapezoid_old_minus_new_twice_area": None,
1254
+ "cuda_trapezoid_new_minus_old_twice_area": None,
1255
+ "cuda_trapezoid_xor_twice_area": None,
1256
+ "old_minus_new_twice_area": source_edge_old_minus,
1257
+ "new_minus_old_twice_area": source_edge_new_minus,
1258
+ "xor_twice_area": source_edge_old_minus + source_edge_new_minus,
1259
+ "uses_overlapping_context": True,
1260
+ "gpu_trapezoid_fragments_available": False,
1261
+ "gpu_source_edge_records_available": True,
1262
+ "merged_boundary_loop_records_available": True,
1263
+ "source_edge_cpu_oracle_fallback": source_edge_cpu_fallback,
1264
+ "boundary_loop_assembly_backend": (
1265
+ "mixed-gpu-boundary-loop-records-plus-host-fallback"
1266
+ if source_edge_result.stats.cuda_loop_component_count and source_edge_result.stats.metal_loop_component_count
1267
+ else
1268
+ "metal-boundary-loop-records-plus-host-fallback"
1269
+ if source_edge_result.stats.metal_loop_component_count
1270
+ else
1271
+ "cuda-boundary-loop-records-plus-host-fallback"
1272
+ if source_edge_result.stats.cuda_loop_component_count
1273
+ else
1274
+ "native-boundary-loop-ctypes-plus-python-fallback"
1275
+ if source_edge_result.stats.native_loop_component_count
1276
+ else "cpu-source-edge-arrangement-prototype"
1277
+ ),
1278
+ "exact_boundary_loops_available": True,
1279
+ "source_edge_arrangement": source_edge_result.stats.to_json(),
1280
+ }
1281
+ else:
1282
+ surplus_start = perf_counter()
1283
+ surplus_result = run_canonical_surplus_coverage_probe(old_native.buffer, new_native.buffer)
1284
+ surplus_probe_source = "cpu-canonical"
1285
+ timings["canonical_surplus_coverage_s"] = perf_counter() - surplus_start
1286
+ if coverage_result is not None:
1287
+ exact_result = coverage_result.exact_result
1288
+ policy_result = classify_exact_differences(exact_result, config.allowed_regions, failure_policy=config.failure_policy)
1289
+ exact_complete = True
1290
+ layers = build_layer_reports(
1291
+ exact_result,
1292
+ policy_result=policy_result,
1293
+ presence_by_layer=_presence_by_layer(old_native.buffer, new_native.buffer),
1294
+ polygon_counts_by_layer=_polygon_counts_by_layer(old_native.buffer, new_native.buffer),
1295
+ exact_status="complete",
1296
+ )
1297
+ elif polygon_fragment_exact_result is not None:
1298
+ exact_result = polygon_fragment_exact_result
1299
+ policy_result = classify_exact_differences(exact_result, config.allowed_regions, failure_policy=config.failure_policy)
1300
+ exact_complete = True
1301
+ layers = build_layer_reports(
1302
+ exact_result,
1303
+ policy_result=policy_result,
1304
+ presence_by_layer=_presence_by_layer(old_native.buffer, new_native.buffer),
1305
+ polygon_counts_by_layer=_polygon_counts_by_layer(old_native.buffer, new_native.buffer),
1306
+ exact_status="gpu-polygon-source-edge-records",
1307
+ )
1308
+ elif surplus_result is not None:
1309
+ exact_result = surplus_result.exact_result
1310
+ policy_result = classify_exact_differences(exact_result, config.allowed_regions, failure_policy=config.failure_policy)
1311
+ exact_complete = surplus_result.stats.coverage_exact_proven
1312
+ layers = build_layer_reports(
1313
+ exact_result,
1314
+ policy_result=policy_result,
1315
+ presence_by_layer=_presence_by_layer(old_native.buffer, new_native.buffer),
1316
+ polygon_counts_by_layer=_polygon_counts_by_layer(old_native.buffer, new_native.buffer),
1317
+ exact_status="complete" if surplus_result.stats.coverage_exact_proven else "canonical-source-surplus-probe",
1318
+ )
1319
+ else:
1320
+ layers = ()
1321
+ if old_native is not None and new_native is not None:
1322
+ inventory_start = perf_counter()
1323
+ if record_delta is not None:
1324
+ polygon_inventory_delta = build_polygon_inventory_delta_from_record_delta(
1325
+ old_native.buffer,
1326
+ new_native.buffer,
1327
+ record_delta,
1328
+ )
1329
+ timings["polygon_inventory_delta_source"] = f"{_record_delta_accelerator(record_delta)}-record-delta"
1330
+ else:
1331
+ polygon_inventory_delta = build_polygon_inventory_delta(old_native.buffer, new_native.buffer)
1332
+ timings["polygon_inventory_delta_source"] = "cpu-full-canonical"
1333
+ timings["polygon_inventory_delta_s"] = perf_counter() - inventory_start
1334
+ coverage_accelerator = coverage_result.stats.accelerator if coverage_result is not None else None
1335
+ record_delta_accelerator = _record_delta_accelerator(record_delta) if record_delta is not None else None
1336
+ warning_text = (
1337
+ f"{str(coverage_accelerator).upper()} rectangle coverage engine produced exact gpu-polygon evidence for rectangle-only input; arbitrary non-rectangle polygon XOR is still unsupported."
1338
+ if coverage_result is not None
1339
+ else f"{str(record_delta_accelerator).upper()} source-edge evidence detected hole-like topology; final exact evidence was corrected with the CPU gdstk oracle while GPU source-record and context evidence were retained."
1340
+ if polygon_fragment_exact_result is not None and source_edge_cpu_fallback is not None
1341
+ else f"{str(record_delta_accelerator).upper()} source-edge evidence path produced exact boundary records with GPU source-record deltas and GPU context indexing where supported; CPU retains loop stitching, hole handling, and Markdown formatting."
1342
+ if polygon_fragment_exact_result is not None
1343
+ else f"{str(gpu_name).upper()} native polygon extraction failed; CPU gdstk oracle produced complete exact evidence for this audit run."
1344
+ if native_parse_cpu_verification is not None
1345
+ else "Canonical source-surplus probe produced exact directional evidence because changed source polygons are independent; arbitrary overlapping polygon XOR remains unsupported."
1346
+ if surplus_result is not None and surplus_result.stats.coverage_exact_proven
1347
+ else "Canonical source-surplus probe emitted directional component records, but the run remains fail-closed because overlapping surplus polygons require the general GPU polygon XOR engine."
1348
+ if surplus_result is not None
1349
+ else
1350
+ "Requested gpu-polygon evidence engine emitted GPU source-record deltas but is fail-closed because the general polygon coverage XOR engine is not implemented yet."
1351
+ if record_delta is not None
1352
+ else "Requested gpu-polygon evidence engine is fail-closed because the general polygon GPU XOR record engine is not implemented yet."
1353
+ if not parse_failures
1354
+ else "Requested gpu-polygon evidence engine is fail-closed because native polygon parsing failed before GPU XOR."
1355
+ )
1356
+ warnings = (warning_text,)
1357
+ exact_fallback = {
1358
+ "mode": f"gpu-polygon-{coverage_accelerator}-rect-coverage"
1359
+ if coverage_result is not None and coverage_accelerator is not None
1360
+ else f"gpu-polygon-{record_delta_accelerator}-source-edge-cpu-oracle-fallback"
1361
+ if polygon_fragment_exact_result is not None and source_edge_cpu_fallback is not None
1362
+ else f"gpu-polygon-{record_delta_accelerator}-source-edge-records"
1363
+ if polygon_fragment_exact_result is not None
1364
+ else f"gpu-polygon-{gpu_name}-native-parse-cpu-oracle-fallback"
1365
+ if native_parse_cpu_verification is not None
1366
+ else "canonical-source-surplus-exact"
1367
+ if surplus_result is not None and surplus_result.stats.coverage_exact_proven
1368
+ else "canonical-source-surplus-probe"
1369
+ if surplus_result is not None
1370
+ else f"gpu-polygon-{record_delta_accelerator}-record-delta-no-coverage-xor"
1371
+ if record_delta is not None
1372
+ else "gpu-polygon-unavailable",
1373
+ "engine": "gpu-polygon",
1374
+ "engine_status": "rect-coverage-complete"
1375
+ if coverage_result is not None
1376
+ else "source-edge-cpu-oracle-corrected"
1377
+ if polygon_fragment_exact_result is not None and source_edge_cpu_fallback is not None
1378
+ else "source-edge-boundary-records-complete"
1379
+ if polygon_fragment_exact_result is not None
1380
+ else "native-parse-cpu-oracle-corrected"
1381
+ if native_parse_cpu_verification is not None
1382
+ else "canonical-source-surplus-exact"
1383
+ if surplus_result is not None and surplus_result.stats.coverage_exact_proven
1384
+ else "canonical-source-surplus-probe-only"
1385
+ if surplus_result is not None
1386
+ else "record-delta-only"
1387
+ if record_delta is not None
1388
+ else "not-implemented"
1389
+ if not parse_failures
1390
+ else "native-parse-failed",
1391
+ "audit_cpu_oracle": False,
1392
+ "window_count": 0,
1393
+ "escalated_layers": [],
1394
+ "failures": parse_failures
1395
+ + record_delta_failures
1396
+ + component_area_failures
1397
+ + ([] if coverage_result is not None or polygon_fragment_exact_result is not None or surplus_result is not None else coverage_failures)
1398
+ + (
1399
+ []
1400
+ if coverage_result is not None or polygon_fragment_exact_result is not None or (surplus_result is not None and surplus_result.stats.coverage_exact_proven)
1401
+ else [
1402
+ {
1403
+ "reason": "gpu-polygon coverage XOR engine is not implemented yet",
1404
+ }
1405
+ ]
1406
+ ),
1407
+ "old_polygon_count": old_native.buffer.polygon_count if old_native is not None else None,
1408
+ "new_polygon_count": new_native.buffer.polygon_count if new_native is not None else None,
1409
+ "old_vertex_count": old_native.buffer.vertex_count if old_native is not None else None,
1410
+ "new_vertex_count": new_native.buffer.vertex_count if new_native is not None else None,
1411
+ "gpu_record_delta_count": record_delta.record_count if record_delta is not None else 0,
1412
+ "gpu_record_delta_matched_count": record_delta.matched_record_count if record_delta is not None else 0,
1413
+ "gpu_record_delta_reduced_key_count": record_delta.reduced_key_count if record_delta is not None else 0,
1414
+ "gpu_record_delta_accelerator": record_delta_accelerator,
1415
+ "gpu_coverage": coverage_result.stats.to_json() if coverage_result is not None else None,
1416
+ "canonical_surplus_coverage": surplus_result.stats.to_json() if surplus_result is not None else None,
1417
+ "surplus_probe_source": surplus_probe_source,
1418
+ "gpu_polygon_component_area": polygon_component_area,
1419
+ "source_edge_cpu_oracle_fallback": source_edge_cpu_fallback,
1420
+ }
1421
+ if native_parse_cpu_verification is not None:
1422
+ exact_fallback["audit_cpu_oracle"] = True
1423
+ exact_fallback["cpu_oracle_audit"] = _exact_result_audit_summary(
1424
+ exact_result,
1425
+ native_parse_cpu_verification.exact_result,
1426
+ )
1427
+ exact_fallback["exact_component_backend"] = native_parse_cpu_verification.exact_component_backend
1428
+ exact_fallback["native_parse_cpu_oracle_fallback"] = {
1429
+ "trigger": "native-polygon-parse-failed",
1430
+ "reason": "GPU native polygon extraction failed before acceleration; CPU gdstk oracle supplied complete exact evidence.",
1431
+ "failures": parse_failures,
1432
+ "exact_component_backend": native_parse_cpu_verification.exact_component_backend,
1433
+ }
1434
+ if backend_info.get("cuda_ctypes_available"):
1435
+ warnings = _release_cuda_device_memory_after_gpu_operation(timings, warnings)
1436
+ if backend_info.get("metal_ctypes_available"):
1437
+ warnings = _release_metal_device_memory_after_gpu_operation(timings, warnings)
1438
+ if audit_cpu_oracle and exact_complete:
1439
+ audit_start = perf_counter()
1440
+ audit_backend = _default_cpu_audit_component_backend(exact_component_backend)
1441
+ verification = source_edge_cpu_verification or _run_full_exact_verification(
1442
+ config,
1443
+ exact_component_backend=audit_backend,
1444
+ force_small_component_partition=True,
1445
+ )
1446
+ timings["cpu_oracle_audit_s"] = perf_counter() - audit_start
1447
+ audit_summary = _exact_result_audit_summary(exact_result, verification.exact_result)
1448
+ exact_fallback["audit_cpu_oracle"] = True
1449
+ exact_fallback["exact_component_backend"] = verification.exact_component_backend
1450
+ if (
1451
+ not audit_summary["match"]
1452
+ and coverage_result is None
1453
+ and polygon_fragment_exact_result is not None
1454
+ ):
1455
+ source_edge_cpu_fallback = {
1456
+ "trigger": "cpu-oracle-audit-mismatch",
1457
+ "reason": (
1458
+ "source-edge evidence did not match the CPU gdstk exact oracle; "
1459
+ "final exact evidence was promoted to the CPU oracle result"
1460
+ ),
1461
+ "original_vs_cpu_oracle": audit_summary,
1462
+ "exact_component_backend": verification.exact_component_backend,
1463
+ }
1464
+ exact_result = verification.exact_result
1465
+ polygon_fragment_exact_result = exact_result
1466
+ policy_result = classify_exact_differences(
1467
+ exact_result,
1468
+ config.allowed_regions,
1469
+ failure_policy=config.failure_policy,
1470
+ )
1471
+ layers = build_layer_reports(
1472
+ exact_result,
1473
+ policy_result=policy_result,
1474
+ presence_by_layer=_presence_by_layer(old_native.buffer, new_native.buffer),
1475
+ polygon_counts_by_layer=_polygon_counts_by_layer(old_native.buffer, new_native.buffer),
1476
+ exact_status="gpu-polygon-source-edge-cpu-oracle-fallback",
1477
+ )
1478
+ if polygon_component_area is not None:
1479
+ polygon_component_area = dict(polygon_component_area)
1480
+ polygon_component_area["source_edge_cpu_oracle_fallback"] = source_edge_cpu_fallback
1481
+ exact_fallback["gpu_polygon_component_area"] = polygon_component_area
1482
+ exact_fallback["mode"] = f"gpu-polygon-{record_delta_accelerator}-source-edge-cpu-oracle-fallback"
1483
+ exact_fallback["engine_status"] = "source-edge-cpu-oracle-corrected"
1484
+ exact_fallback["source_edge_cpu_oracle_fallback"] = source_edge_cpu_fallback
1485
+ exact_fallback["cpu_oracle_audit_original_mismatch"] = audit_summary
1486
+ audit_summary = _exact_result_audit_summary(exact_result, verification.exact_result)
1487
+ warnings = warnings + (
1488
+ "CPU gdstk audit corrected source-edge GPU evidence; original mismatch is recorded in exact_fallback.cpu_oracle_audit_original_mismatch.",
1489
+ )
1490
+ exact_fallback["cpu_oracle_audit"] = audit_summary
1491
+ if not audit_summary["match"]:
1492
+ warnings = warnings + ("CPU gdstk audit did not match GPU evidence; see exact_fallback.cpu_oracle_audit.",)
1493
+ old_input = _native_polygon_input_json(config.old_path, old_top_name, old_native)
1494
+ new_input = _native_polygon_input_json(config.new_path, new_top_name, new_native)
1495
+ selection = {"old_top": old_top_name, "new_top": new_top_name, "layers": config.to_json_dict()["layers"]}
1496
+ if exact_diff_markdown_path is not None:
1497
+ markdown_start = perf_counter()
1498
+ if coverage_result is not None:
1499
+ markdown_written = write_exact_diff_markdown(
1500
+ policy_result,
1501
+ exact_diff_markdown_path,
1502
+ title=f"GDS comparator gpu-polygon {str(coverage_accelerator).upper()} rectangle coverage diff",
1503
+ source_mode=exact_fallback["mode"],
1504
+ exact_verification_complete=True,
1505
+ polygon_inventory_delta=polygon_inventory_delta,
1506
+ )
1507
+ _record_exact_markdown_artifacts(artifacts, markdown_written)
1508
+ elif polygon_fragment_exact_result is not None:
1509
+ markdown_written = write_exact_diff_markdown(
1510
+ policy_result,
1511
+ exact_diff_markdown_path,
1512
+ title=f"GDS comparator gpu-polygon {str(record_delta_accelerator).upper()} source-edge diff",
1513
+ source_mode=exact_fallback["mode"],
1514
+ exact_verification_complete=True,
1515
+ polygon_inventory_delta=polygon_inventory_delta,
1516
+ )
1517
+ if polygon_component_area is not None:
1518
+ _append_gpu_polygon_component_area_markdown(markdown_written.path, polygon_component_area)
1519
+ _record_exact_markdown_artifacts(artifacts, markdown_written)
1520
+ elif surplus_result is not None:
1521
+ markdown_written = write_exact_diff_markdown(
1522
+ policy_result,
1523
+ exact_diff_markdown_path,
1524
+ title="GDS comparator canonical source-surplus diff",
1525
+ source_mode=exact_fallback["mode"],
1526
+ exact_verification_complete=surplus_result.stats.coverage_exact_proven,
1527
+ polygon_inventory_delta=polygon_inventory_delta,
1528
+ )
1529
+ if polygon_component_area is not None:
1530
+ _append_gpu_polygon_component_area_markdown(markdown_written.path, polygon_component_area)
1531
+ _record_exact_markdown_artifacts(artifacts, markdown_written)
1532
+ elif native_parse_cpu_verification is not None:
1533
+ markdown_written = write_exact_diff_markdown(
1534
+ policy_result,
1535
+ exact_diff_markdown_path,
1536
+ title="GDS comparator GPU native-parse CPU oracle fallback diff",
1537
+ source_mode=exact_fallback["mode"],
1538
+ exact_verification_complete=True,
1539
+ polygon_inventory_delta=polygon_inventory_delta,
1540
+ )
1541
+ _record_exact_markdown_artifacts(artifacts, markdown_written)
1542
+ elif record_delta is not None:
1543
+ _write_gpu_polygon_record_delta_markdown(exact_diff_markdown_path, warnings[0], record_delta)
1544
+ artifacts["exact_diff_markdown"] = str(exact_diff_markdown_path)
1545
+ else:
1546
+ _write_gpu_polygon_unavailable_markdown(exact_diff_markdown_path, warnings[0])
1547
+ artifacts["exact_diff_markdown"] = str(exact_diff_markdown_path)
1548
+ timings["markdown_format_s"] = perf_counter() - markdown_start
1549
+
1550
+ if markdown_path is not None:
1551
+ markdown_path = Path(markdown_path)
1552
+ markdown_path.parent.mkdir(parents=True, exist_ok=True)
1553
+ markdown_path.write_text(
1554
+ "\n".join(
1555
+ [
1556
+ "### GDS comparator gpu-polygon evidence result",
1557
+ "",
1558
+ f"- Status: {'cpu-audited' if audit_cpu_oracle else 'unresolved'}.",
1559
+ f"- Note: {warnings[0]}",
1560
+ ]
1561
+ )
1562
+ + "\n",
1563
+ encoding="utf-8",
1564
+ )
1565
+ artifacts["markdown"] = str(markdown_path)
1566
+
1567
+ timings["total_s"] = perf_counter() - start
1568
+ audit_summary = exact_fallback.get("cpu_oracle_audit") if isinstance(exact_fallback, dict) else None
1569
+ unsupported_feature_count = _gpu_polygon_unsupported_feature_count(exact_fallback, exact_complete=exact_complete)
1570
+ status = _neutral_compare_status(
1571
+ policy_result,
1572
+ polygon_inventory_delta=polygon_inventory_delta,
1573
+ exact_complete=exact_complete,
1574
+ unsupported_feature_count=unsupported_feature_count,
1575
+ audit_match=(audit_summary or {}).get("match") if isinstance(audit_summary, dict) else None,
1576
+ )
1577
+ neutral_fields = _neutral_evidence_report_fields(
1578
+ status,
1579
+ policy_result,
1580
+ polygon_inventory_delta=polygon_inventory_delta,
1581
+ exact_complete=exact_complete,
1582
+ unsupported_feature_count=unsupported_feature_count,
1583
+ vertex_sidecar_path=artifacts.get("vertex_sidecar"),
1584
+ )
1585
+ report = CompareReport(
1586
+ status=status,
1587
+ exact_verification_complete=exact_complete,
1588
+ old_input=old_input,
1589
+ new_input=new_input,
1590
+ semantics=config.to_json_dict(),
1591
+ selection=selection,
1592
+ backend=backend_info
1593
+ | {
1594
+ "exact_mode": False,
1595
+ "evidence_engine_requested": config.evidence_engine.value,
1596
+ "evidence_engine_selected": "gdstk-oracle" if cpu_neutral_fallback else "gpu-polygon",
1597
+ "gpu_polygon_available": (
1598
+ False if cpu_neutral_fallback else coverage_result is not None or polygon_fragment_exact_result is not None
1599
+ ),
1600
+ "cpu_neutral_fallback": cpu_neutral_fallback,
1601
+ "gpu_record_delta_available": record_delta is not None,
1602
+ "canonical_surplus_coverage_available": surplus_result is not None,
1603
+ "canonical_surplus_coverage_exact": (
1604
+ surplus_result.stats.coverage_exact_proven if surplus_result is not None else False
1605
+ ),
1606
+ "gpu_coverage_engine": (
1607
+ f"{coverage_result.stats.accelerator}-rect-coverage" if coverage_result is not None else None
1608
+ ),
1609
+ "metal_coverage_engine": "metal-rect-coverage"
1610
+ if coverage_result is not None and coverage_result.stats.accelerator == "metal"
1611
+ else None,
1612
+ "audit_cpu_oracle": audit_cpu_oracle,
1613
+ "cuda_kernel_count": (
1614
+ (
1615
+ record_delta.cuda_kernel_count
1616
+ if record_delta is not None and _record_delta_accelerator(record_delta) == "cuda"
1617
+ else 0
1618
+ )
1619
+ + (
1620
+ coverage_result.stats.cuda_kernel_count
1621
+ if coverage_result is not None and coverage_result.stats.accelerator == "cuda"
1622
+ else 0
1623
+ )
1624
+ + int(
1625
+ polygon_component_area.get("context_cuda_kernel_count", 0)
1626
+ if polygon_component_area is not None
1627
+ else 0
1628
+ )
1629
+ + (
1630
+ 1
1631
+ if polygon_fragment_exact_result is not None and record_delta is not None and _record_delta_accelerator(record_delta) == "cuda"
1632
+ else 0
1633
+ )
1634
+ ),
1635
+ "metal_kernel_count": (
1636
+ (
1637
+ record_delta.metal_kernel_count
1638
+ if record_delta is not None and _record_delta_accelerator(record_delta) == "metal"
1639
+ else 0
1640
+ )
1641
+ + (1 if coverage_result is not None and coverage_result.stats.accelerator == "metal" else 0)
1642
+ + int(
1643
+ polygon_component_area.get("context_metal_kernel_count", 0)
1644
+ if polygon_component_area is not None
1645
+ else 0
1646
+ )
1647
+ + (
1648
+ 1
1649
+ if polygon_fragment_exact_result is not None and record_delta is not None and _record_delta_accelerator(record_delta) == "metal"
1650
+ else 0
1651
+ )
1652
+ ),
1653
+ "cuda_event_elapsed_s": (
1654
+ record_delta.cuda_event_elapsed_s
1655
+ if record_delta is not None and _record_delta_accelerator(record_delta) == "cuda"
1656
+ else 0.0
1657
+ ),
1658
+ "metal_event_elapsed_s": (
1659
+ record_delta.metal_event_elapsed_s
1660
+ if record_delta is not None and _record_delta_accelerator(record_delta) == "metal"
1661
+ else 0.0
1662
+ ),
1663
+ "transferred_bytes": (
1664
+ (record_delta.transferred_bytes if record_delta is not None else 0)
1665
+ + (coverage_result.stats.transferred_bytes if coverage_result is not None else 0)
1666
+ + int(polygon_component_area.get("transferred_bytes", 0) if polygon_component_area is not None else 0)
1667
+ ),
1668
+ },
1669
+ prefilter=prefilter.to_json(),
1670
+ exact_fallback=exact_fallback,
1671
+ layers=layers,
1672
+ allowed_policy=policy_result.effective_policy,
1673
+ unexpected_summary={
1674
+ "expected_area_dbu2": policy_result.to_json()["expected_area_dbu2"],
1675
+ "unexpected_area_dbu2": policy_result.to_json()["unexpected_area_dbu2"],
1676
+ },
1677
+ timings=timings,
1678
+ warnings=warnings,
1679
+ errors=() if audit_cpu_oracle else warnings,
1680
+ artifacts=artifacts,
1681
+ **neutral_fields,
1682
+ command=command,
1683
+ failure_policy=config.failure_policy,
1684
+ )
1685
+ if json_report_path is not None:
1686
+ artifacts["json_report"] = str(json_report_path)
1687
+ report = _report_with_artifacts(report, artifacts)
1688
+ report.write_json(json_report_path)
1689
+ return CompareRunResult(
1690
+ status=CompareStatus.coerce(status),
1691
+ report=report,
1692
+ exact_result=exact_result,
1693
+ policy_result=policy_result,
1694
+ prefilter=prefilter,
1695
+ windows=windows,
1696
+ artifacts=artifacts,
1697
+ timings=timings,
1698
+ )
1699
+
1700
+
1701
+ def _gpu_polygon_native_inputs(
1702
+ config: CompareConfig,
1703
+ *,
1704
+ old_top_name: str | None,
1705
+ new_top_name: str | None,
1706
+ ):
1707
+ if config.geometry_cache_dir is None:
1708
+ old_extract_args = (config.old_path, old_top_name, config.layers)
1709
+ new_extract_args = (config.new_path, new_top_name, config.layers)
1710
+ if _should_extract_pair_in_parallel(config):
1711
+ with ThreadPoolExecutor(max_workers=2) as executor:
1712
+ old_future = executor.submit(_extract_native_one, old_extract_args)
1713
+ new_future = executor.submit(_extract_native_one, new_extract_args)
1714
+ return old_future.result(), new_future.result(), {}
1715
+ return (
1716
+ _extract_native_one(old_extract_args),
1717
+ _extract_native_one(new_extract_args),
1718
+ {},
1719
+ )
1720
+ old_cache_args = (config.geometry_cache_dir, config.old_path, old_top_name, config.layers)
1721
+ new_cache_args = (config.geometry_cache_dir, config.new_path, new_top_name, config.layers)
1722
+ if _should_extract_pair_in_parallel(config):
1723
+ with ThreadPoolExecutor(max_workers=2) as executor:
1724
+ old_future = executor.submit(_cached_native_polygon_one, old_cache_args)
1725
+ new_future = executor.submit(_cached_native_polygon_one, new_cache_args)
1726
+ old_cached = old_future.result()
1727
+ new_cached = new_future.result()
1728
+ timings: dict[str, object] = {}
1729
+ timings.update(old_cached.to_timing_json("old"))
1730
+ timings.update(new_cached.to_timing_json("new"))
1731
+ return old_cached.extraction, new_cached.extraction, timings
1732
+ old_cached = _cached_native_polygon_one(old_cache_args)
1733
+ new_cached = _cached_native_polygon_one(new_cache_args)
1734
+ timings: dict[str, object] = {}
1735
+ timings.update(old_cached.to_timing_json("old"))
1736
+ timings.update(new_cached.to_timing_json("new"))
1737
+ return old_cached.extraction, new_cached.extraction, timings
1738
+
1739
+
1740
+ def _native_buffers_rectangle_coverage_eligible(*buffers: PolygonBuffer) -> bool:
1741
+ for buffer in buffers:
1742
+ if buffer.polygon_count == 0:
1743
+ continue
1744
+ active = buffer.flags == 0
1745
+ if not bool(active.any()):
1746
+ continue
1747
+ vertex_counts = buffer.polygon_offsets[1:] - buffer.polygon_offsets[:-1]
1748
+ if not bool((vertex_counts[active] == 4).all()):
1749
+ return False
1750
+ return True
1751
+
1752
+
1753
+ def _extract_native_one(args):
1754
+ path, top_name, layers = args
1755
+ return extract_gds_polygons_native(path, top_name=top_name, layers=layers)
1756
+
1757
+
1758
+ def _cached_native_polygon_one(args):
1759
+ cache_dir, path, top_name, layers = args
1760
+ return load_or_build_cached_native_polygons(
1761
+ cache_dir=cache_dir,
1762
+ path=path,
1763
+ top_name=top_name,
1764
+ layers=layers,
1765
+ )
1766
+
1767
+
1768
+ def _default_cpu_audit_component_backend(exact_component_backend: str) -> str:
1769
+ return "gdstk" if exact_component_backend == "none" else exact_component_backend
1770
+
1771
+
1772
+ def _record_delta_accelerator(record_delta) -> str:
1773
+ return str(getattr(record_delta, "accelerator", "cuda"))
1774
+
1775
+
1776
+ def _release_cuda_device_memory_after_gpu_operation(timings: dict[str, float], warnings: tuple[str, ...]) -> tuple[str, ...]:
1777
+ release_start = perf_counter()
1778
+ try:
1779
+ from .backends.cuda_ctypes import CudaCtypesUnavailable, release_cuda_device_memory
1780
+
1781
+ release_cuda_device_memory()
1782
+ except CudaCtypesUnavailable as exc:
1783
+ return warnings + (f"CUDA device memory release failed after gpu-polygon operation: {exc}",)
1784
+ finally:
1785
+ timings["cuda_release_s"] = perf_counter() - release_start
1786
+ return warnings
1787
+
1788
+
1789
+ def _release_metal_device_memory_after_gpu_operation(timings: dict[str, float], warnings: tuple[str, ...]) -> tuple[str, ...]:
1790
+ release_start = perf_counter()
1791
+ try:
1792
+ from .backends.metal_ctypes import MetalCtypesUnavailable, release_metal_device_memory
1793
+
1794
+ release_metal_device_memory()
1795
+ except MetalCtypesUnavailable as exc:
1796
+ return warnings + (f"Metal device memory release failed after gpu-polygon operation: {exc}",)
1797
+ finally:
1798
+ timings["metal_release_s"] = perf_counter() - release_start
1799
+ return warnings
1800
+
1801
+
1802
+ def _exact_result_audit_summary(gpu_result: ExactOracleResult, cpu_result: ExactOracleResult) -> dict[str, object]:
1803
+ gpu_layers = _exact_result_layer_audit_map(gpu_result)
1804
+ cpu_layers = _exact_result_layer_audit_map(cpu_result)
1805
+ mismatches = []
1806
+ for layer in sorted(set(gpu_layers) | set(cpu_layers)):
1807
+ gpu_layer = gpu_layers.get(layer)
1808
+ cpu_layer = cpu_layers.get(layer)
1809
+ if gpu_layer != cpu_layer:
1810
+ mismatches.append(
1811
+ {
1812
+ "layer": [layer[0], layer[1]],
1813
+ "gpu": gpu_layer,
1814
+ "cpu": cpu_layer,
1815
+ }
1816
+ )
1817
+ return {
1818
+ "oracle": "gdstk-full-exact",
1819
+ "match": not mismatches,
1820
+ "mismatch_count": len(mismatches),
1821
+ "mismatches": mismatches[:20],
1822
+ "truncated_mismatch_count": max(0, len(mismatches) - 20),
1823
+ }
1824
+
1825
+
1826
+ def _exact_result_layer_audit_map(result: ExactOracleResult) -> dict[tuple[int, int], dict[str, object]]:
1827
+ layers = {}
1828
+ for layer in result.layers:
1829
+ old_fragments = tuple(
1830
+ _audit_fragment_json(fragment)
1831
+ for fragment in sorted(layer.old_minus_new, key=_audit_fragment_sort_key)
1832
+ )
1833
+ new_fragments = tuple(
1834
+ _audit_fragment_json(fragment)
1835
+ for fragment in sorted(layer.new_minus_old, key=_audit_fragment_sort_key)
1836
+ )
1837
+ if not old_fragments and not new_fragments:
1838
+ continue
1839
+ layers[(layer.layer.layer, layer.layer.datatype)] = {
1840
+ "old_minus_new_twice_area": sum(fragment.twice_area for fragment in layer.old_minus_new),
1841
+ "new_minus_old_twice_area": sum(fragment.twice_area for fragment in layer.new_minus_old),
1842
+ "xor_twice_area": sum(fragment.twice_area for fragment in layer.xor),
1843
+ "old_minus_new_count": len(old_fragments),
1844
+ "new_minus_old_count": len(new_fragments),
1845
+ "xor_count": len(layer.xor),
1846
+ "old_minus_new_fragments": old_fragments,
1847
+ "new_minus_old_fragments": new_fragments,
1848
+ }
1849
+ return layers
1850
+
1851
+
1852
+ def _audit_fragment_sort_key(fragment: DiffFragment) -> tuple[tuple[int, int, int, int], int, int]:
1853
+ return fragment.bbox, fragment.twice_area, len(fragment.points)
1854
+
1855
+
1856
+ def _audit_fragment_json(fragment: DiffFragment) -> dict[str, object]:
1857
+ points = _audit_canonical_points(fragment.points)
1858
+ return {
1859
+ "bbox": list(fragment.bbox),
1860
+ "twice_area": fragment.twice_area,
1861
+ "vertex_count": len(points),
1862
+ "points_sha256": _audit_points_hash(points),
1863
+ }
1864
+
1865
+
1866
+ def _audit_canonical_points(points: tuple[tuple[int, int], ...]) -> tuple[tuple[int, int], ...]:
1867
+ cleaned = _audit_clean_loop_points(points)
1868
+ if not cleaned:
1869
+ return ()
1870
+ candidates: list[tuple[tuple[int, int], ...]] = []
1871
+ for sequence in (cleaned, tuple(reversed(cleaned))):
1872
+ candidates.extend(sequence[index:] + sequence[:index] for index in range(len(sequence)))
1873
+ return min(candidates)
1874
+
1875
+
1876
+ def _audit_clean_loop_points(points: tuple[tuple[int, int], ...]) -> tuple[tuple[int, int], ...]:
1877
+ cleaned: list[tuple[int, int]] = []
1878
+ for point in points:
1879
+ if not cleaned or cleaned[-1] != point:
1880
+ cleaned.append(point)
1881
+ if len(cleaned) > 1 and cleaned[0] == cleaned[-1]:
1882
+ cleaned.pop()
1883
+ changed = True
1884
+ while changed and len(cleaned) >= 3:
1885
+ changed = False
1886
+ next_points: list[tuple[int, int]] = []
1887
+ for index, point in enumerate(cleaned):
1888
+ previous = cleaned[index - 1]
1889
+ following = cleaned[(index + 1) % len(cleaned)]
1890
+ if _audit_collinear(previous, point, following):
1891
+ changed = True
1892
+ continue
1893
+ next_points.append(point)
1894
+ cleaned = next_points
1895
+ return tuple(cleaned)
1896
+
1897
+
1898
+ def _audit_collinear(
1899
+ left: tuple[int, int],
1900
+ middle: tuple[int, int],
1901
+ right: tuple[int, int],
1902
+ ) -> bool:
1903
+ return (middle[0] - left[0]) * (right[1] - middle[1]) == (middle[1] - left[1]) * (right[0] - middle[0])
1904
+
1905
+
1906
+ def _audit_points_hash(points: tuple[tuple[int, int], ...]) -> str:
1907
+ payload = ";".join(f"{x},{y}" for x, y in points).encode("ascii")
1908
+ return hashlib.sha256(payload).hexdigest()
1909
+
1910
+
1911
+ def _resolve_single_common_top_after_native_parse_failure(
1912
+ config: CompareConfig,
1913
+ reason: str,
1914
+ old_top_name: str | None,
1915
+ new_top_name: str | None,
1916
+ ) -> str | None:
1917
+ if old_top_name is not None or new_top_name is not None or "expected exactly one top cell" not in reason:
1918
+ return None
1919
+ try:
1920
+ old_metadata = read_gds_metadata(config.old_path)
1921
+ new_metadata = read_gds_metadata(config.new_path)
1922
+ except Exception:
1923
+ return None
1924
+ common_tops = sorted(set(old_metadata.top_names) & set(new_metadata.top_names))
1925
+ if len(common_tops) != 1:
1926
+ return None
1927
+ return common_tops[0]
1928
+
1929
+
1930
+ def _write_gpu_polygon_unavailable_markdown(path: str | Path, warning: str) -> None:
1931
+ path = Path(path)
1932
+ path.parent.mkdir(parents=True, exist_ok=True)
1933
+ path.write_text(
1934
+ "\n".join(
1935
+ [
1936
+ "# GDS comparator gpu-polygon exact diff",
1937
+ "",
1938
+ "## Comparator Evidence",
1939
+ "",
1940
+ "- status: `unresolved`",
1941
+ "- source_mode: `gpu-polygon-unavailable`",
1942
+ "- exact_verification_complete: `false`",
1943
+ "- expected_area_dbu2: `0`",
1944
+ "- unexpected_area_dbu2: `0`",
1945
+ "- layer_count: `0`",
1946
+ "- component_count: `0`",
1947
+ "- polygon_inventory_delta_count: `not-recorded`",
1948
+ "",
1949
+ "## Exactness Warning",
1950
+ "",
1951
+ warning,
1952
+ "",
1953
+ "## Agent Minimal Read",
1954
+ "",
1955
+ "- coverage_changed: `unknown`",
1956
+ "- coverage_xor_area_dbu2: `unknown`",
1957
+ "- inventory_changed: `unknown`",
1958
+ "- component_count: `0`",
1959
+ "- note: `gpu-polygon numeric diff records are not available yet; rerun with --audit-cpu-oracle for CPU gdstk evidence`",
1960
+ "",
1961
+ ]
1962
+ ),
1963
+ encoding="utf-8",
1964
+ )
1965
+
1966
+
1967
+ def _write_gpu_polygon_record_delta_markdown(path: str | Path, warning: str, record_delta, *, row_limit: int = 200) -> None:
1968
+ path = Path(path)
1969
+ path.parent.mkdir(parents=True, exist_ok=True)
1970
+ accelerator = _record_delta_accelerator(record_delta)
1971
+ shown = min(record_delta.record_count, row_limit)
1972
+ omitted = max(0, record_delta.record_count - shown)
1973
+ lines = [
1974
+ "# GDS comparator gpu-polygon exact diff",
1975
+ "",
1976
+ "## Comparator Evidence",
1977
+ "",
1978
+ "- status: `unresolved`",
1979
+ f"- source_mode: `gpu-polygon-{accelerator}-record-delta-no-coverage-xor`",
1980
+ "- exact_verification_complete: `false`",
1981
+ "- coverage_xor_available: `false`",
1982
+ f"- gpu_record_delta_accelerator: `{accelerator}`",
1983
+ "- gpu_record_delta_available: `true`",
1984
+ f"- gpu_record_delta_count: `{record_delta.record_count}`",
1985
+ f"- matched_source_record_count: `{record_delta.matched_record_count}`",
1986
+ f"- reduced_source_record_key_count: `{record_delta.reduced_key_count}`",
1987
+ f"- gpu_kernel_count: `{getattr(record_delta, accelerator + '_kernel_count', 0)}`",
1988
+ f"- gpu_event_elapsed_s: `{getattr(record_delta, accelerator + '_event_elapsed_s', 0.0):.9f}`",
1989
+ f"- transferred_bytes: `{record_delta.transferred_bytes}`",
1990
+ "",
1991
+ "## Exactness Warning",
1992
+ "",
1993
+ warning,
1994
+ "",
1995
+ "## Agent Minimal Read",
1996
+ "",
1997
+ "- coverage_changed: `unknown`",
1998
+ "- coverage_xor_area_dbu2: `unknown`",
1999
+ f"- source_polygon_records_changed: `{'true' if record_delta.record_count else 'false'}`",
2000
+ f"- source_record_delta_rows: `{record_delta.record_count}`",
2001
+ "- note: `these rows are GPU-emitted source polygon record deltas; they are not yet coverage XOR components`",
2002
+ "",
2003
+ "## GPU Source Record Delta",
2004
+ "",
2005
+ "| direction | count | layer | datatype | bbox_dbu | vertex_count | flags | hash0 | hash1 |",
2006
+ "|---|---:|---:|---:|---|---:|---:|---|---|",
2007
+ ]
2008
+ for index in range(shown):
2009
+ direction = "old_minus_new" if int(record_delta.directions[index]) == 1 else "new_minus_old"
2010
+ bbox = [int(value) for value in record_delta.bboxes[index]]
2011
+ hash0 = f"{int(record_delta.hashes[index][0]):016x}"
2012
+ hash1 = f"{int(record_delta.hashes[index][1]):016x}"
2013
+ lines.append(
2014
+ "| {direction} | {count} | {layer} | {datatype} | `{bbox}` | {vertex_count} | {flags} | `{hash0}` | `{hash1}` |".format(
2015
+ direction=direction,
2016
+ count=int(record_delta.counts[index]),
2017
+ layer=int(record_delta.layers[index]),
2018
+ datatype=int(record_delta.datatypes[index]),
2019
+ bbox=bbox,
2020
+ vertex_count=int(record_delta.vertex_counts[index]),
2021
+ flags=int(record_delta.flags[index]),
2022
+ hash0=hash0,
2023
+ hash1=hash1,
2024
+ )
2025
+ )
2026
+ if omitted:
2027
+ lines.extend(
2028
+ [
2029
+ "",
2030
+ "## Omitted Rows",
2031
+ "",
2032
+ f"- omitted_gpu_record_delta_rows: `{omitted}`",
2033
+ "- note: `rerun with a larger row limit after the coverage-XOR engine is available if full source-record inventory is needed`",
2034
+ ]
2035
+ )
2036
+ lines.append("")
2037
+ path.write_text("\n".join(lines), encoding="utf-8")
2038
+
2039
+
2040
+ def _append_gpu_polygon_component_area_markdown(path: str | Path, component_area: dict[str, object]) -> None:
2041
+ path = Path(path)
2042
+ gpu_trapezoid = bool(component_area.get("gpu_trapezoid_fragments_available", False))
2043
+ gpu_source_edge = bool(component_area.get("gpu_source_edge_records_available", False))
2044
+ exact_boundary = bool(component_area.get("exact_boundary_loops_available", False))
2045
+ lines = [
2046
+ "",
2047
+ "## GPU Polygon Evidence",
2048
+ "",
2049
+ f"- gpu_trapezoid_fragments_available: `{str(gpu_trapezoid).lower()}`",
2050
+ f"- gpu_source_edge_records_available: `{str(gpu_source_edge).lower()}`",
2051
+ f"- merged_boundary_loop_records_available: `{str(bool(component_area.get('merged_boundary_loop_records_available', False))).lower()}`",
2052
+ f"- boundary_loop_assembly_backend: `{component_area.get('boundary_loop_assembly_backend', 'none')}`",
2053
+ f"- exact_boundary_loops_available: `{str(exact_boundary).lower()}`",
2054
+ "- note: `default CUDA evidence uses GPU context indexing and GPU edge cancel/split when supported, then CPU formats compact exact boundary records and Markdown`",
2055
+ f"- component_count: `{component_area.get('component_count', 0)}`",
2056
+ f"- context_component_count: `{component_area.get('context_component_count', 0)}`",
2057
+ f"- context_max_fragments_per_component: `{component_area.get('context_max_fragments_per_component', 0)}`",
2058
+ f"- context_max_vertices_per_component: `{component_area.get('context_max_vertices_per_component', 0)}`",
2059
+ f"- emitted_trapezoid_fragment_count: `{component_area.get('emitted_trapezoid_fragment_count', 0)}`",
2060
+ f"- old_minus_new_twice_area_dbu2: `{component_area.get('old_minus_new_twice_area', 0)}`",
2061
+ f"- new_minus_old_twice_area_dbu2: `{component_area.get('new_minus_old_twice_area', 0)}`",
2062
+ f"- xor_twice_area_dbu2: `{component_area.get('xor_twice_area', 0)}`",
2063
+ "",
2064
+ ]
2065
+ with path.open("a", encoding="utf-8") as handle:
2066
+ handle.write("\n".join(lines))
2067
+
2068
+
2069
+ def _native_polygon_input_json(path: str | Path, top_name: str | None, extraction) -> dict[str, object]:
2070
+ data: dict[str, object] = {
2071
+ "path": str(path),
2072
+ "selected_top": top_name,
2073
+ "input_mode": "native-polygon-array",
2074
+ }
2075
+ if extraction is None:
2076
+ data["native_polygon_parse_complete"] = False
2077
+ return data
2078
+ data.update(
2079
+ {
2080
+ "native_polygon_parse_complete": True,
2081
+ "polygon_count": extraction.buffer.polygon_count,
2082
+ "vertex_count": extraction.buffer.vertex_count,
2083
+ "layers": [layer.to_json() for layer in extraction.buffer.layer_table.layers],
2084
+ }
2085
+ )
2086
+ return data
2087
+
2088
+
2089
+ def _geometry_layer_json(summary) -> tuple[dict[str, object], ...]:
2090
+ return tuple(layer.to_json() for layer in summary.layers)
2091
+
2092
+
2093
+ def _try_pair_verdict_cache_path(
2094
+ config: CompareConfig,
2095
+ *,
2096
+ backend_info: dict[str, object],
2097
+ old_metadata: dict[str, object],
2098
+ new_metadata: dict[str, object],
2099
+ old_top_name: str | None,
2100
+ new_top_name: str | None,
2101
+ command: tuple[str, ...],
2102
+ json_report_path: str | Path | None,
2103
+ markdown_path: str | Path | None,
2104
+ exact_diff_markdown_path: str | Path | None,
2105
+ start: float,
2106
+ exact_component_backend: str,
2107
+ ) -> CompareRunResult | None:
2108
+ cached = load_cached_pair_verdict(config, old_top_name=old_top_name, new_top_name=new_top_name)
2109
+ if cached is None:
2110
+ return None
2111
+ cache_status_start = perf_counter()
2112
+ geometry_status_s = perf_counter() - cache_status_start + cached.load_s
2113
+ verification = _run_full_exact_verification(config, exact_component_backend=exact_component_backend)
2114
+ exact_result = verification.exact_result
2115
+ policy_result = verification.policy_result
2116
+ timings = {
2117
+ "extract_s": verification.extract_s,
2118
+ "exact_s": verification.exact_s,
2119
+ "fast_status_s": geometry_status_s,
2120
+ "geometry_status_s": geometry_status_s,
2121
+ "pair_verdict_cache_hit": True,
2122
+ "pair_verdict_cache_load_s": cached.load_s,
2123
+ "pair_verdict_cache_key": cached.cache_key,
2124
+ "exact_verification_s": verification.extract_s + verification.exact_s,
2125
+ "total_s": perf_counter() - start,
2126
+ }
2127
+ artifacts: dict[str, str] = {}
2128
+ warnings = (
2129
+ "Cached full-geometry prefilter/windowed verdict was reused by file-content cache key; full exact gdstk verification was run afterward and supplies the polygon-level evidence.",
2130
+ )
2131
+ exact_fallback = dict(cached.exact_fallback)
2132
+ exact_fallback["mode"] = f"{exact_fallback.get('mode', 'geometry')}-cache-plus-full-exact"
2133
+ exact_fallback["fast_status"] = cached.fast_status
2134
+ exact_fallback["pair_verdict_cache_hit"] = True
2135
+ exact_fallback["pair_verdict_cache_key"] = cached.cache_key
2136
+ exact_fallback["exact_component_backend"] = verification.exact_component_backend
2137
+ layers = verification.layers
2138
+ if markdown_path is not None:
2139
+ markdown_path = Path(markdown_path)
2140
+ markdown_path.parent.mkdir(parents=True, exist_ok=True)
2141
+ markdown_path.write_text(
2142
+ render_design_history_entry(
2143
+ "GDS comparator cached geometry-prefilter result",
2144
+ policy_result,
2145
+ tests_run=("gdsdiff geometry-cache",),
2146
+ notes=warnings,
2147
+ ),
2148
+ encoding="utf-8",
2149
+ )
2150
+ artifacts["markdown"] = str(markdown_path)
2151
+ if exact_diff_markdown_path is not None:
2152
+ artifacts["exact_diff_markdown"] = str(
2153
+ write_exact_diff_markdown(
2154
+ policy_result,
2155
+ exact_diff_markdown_path,
2156
+ title="GDS comparator cached geometry-prefilter exact diff",
2157
+ source_mode=str(exact_fallback["mode"]),
2158
+ exact_verification_complete=True,
2159
+ polygon_inventory_delta=verification.polygon_inventory_delta,
2160
+ ).path
2161
+ )
2162
+ report = CompareReport(
2163
+ status=policy_result.status,
2164
+ exact_verification_complete=True,
2165
+ old_input=verification.old_input,
2166
+ new_input=verification.new_input,
2167
+ semantics=config.to_json_dict(),
2168
+ selection=verification.selection,
2169
+ backend=backend_info
2170
+ | {
2171
+ "exact_mode": False,
2172
+ "fast_shortcuts_enabled": False,
2173
+ "geometry_cache_enabled": True,
2174
+ "pair_verdict_cache_hit": True,
2175
+ },
2176
+ prefilter=CpuTilePrefilterResult(candidate_tiles=()).to_json(),
2177
+ exact_fallback=exact_fallback,
2178
+ layers=layers,
2179
+ allowed_policy=policy_result.effective_policy,
2180
+ unexpected_summary={
2181
+ "expected_area_dbu2": policy_result.to_json()["expected_area_dbu2"],
2182
+ "unexpected_area_dbu2": policy_result.to_json()["unexpected_area_dbu2"],
2183
+ },
2184
+ timings=timings,
2185
+ warnings=warnings,
2186
+ artifacts=artifacts,
2187
+ command=command,
2188
+ failure_policy=config.failure_policy,
2189
+ )
2190
+ if json_report_path is not None:
2191
+ artifacts["json_report"] = str(json_report_path)
2192
+ report = _report_with_artifacts(report, artifacts)
2193
+ report.write_json(json_report_path)
2194
+ return CompareRunResult(
2195
+ status=policy_result.status,
2196
+ report=report,
2197
+ exact_result=exact_result,
2198
+ policy_result=policy_result,
2199
+ prefilter=CpuTilePrefilterResult(candidate_tiles=()),
2200
+ windows=(),
2201
+ artifacts=artifacts,
2202
+ timings=timings,
2203
+ )
2204
+
2205
+
2206
+ def _try_cuda_hierarchy_fast_path(
2207
+ config: CompareConfig,
2208
+ *,
2209
+ backend_info: dict[str, object],
2210
+ old_metadata: dict[str, object],
2211
+ new_metadata: dict[str, object],
2212
+ old_top_name: str | None,
2213
+ new_top_name: str | None,
2214
+ exact: bool,
2215
+ command: tuple[str, ...],
2216
+ json_report_path: str | Path | None,
2217
+ markdown_path: str | Path | None,
2218
+ exact_diff_markdown_path: str | Path | None,
2219
+ verify_exact_after_fast: bool,
2220
+ ) -> CompareRunResult | None:
2221
+ if exact or not _accelerated_fast_path_eligible(config, backend_info):
2222
+ return None
2223
+ start = perf_counter()
2224
+ digest_result = _try_accelerated_byte_identity_fast_path(
2225
+ config,
2226
+ backend_info=backend_info,
2227
+ old_metadata=old_metadata,
2228
+ new_metadata=new_metadata,
2229
+ old_top_name=old_top_name,
2230
+ new_top_name=new_top_name,
2231
+ command=command,
2232
+ json_report_path=json_report_path,
2233
+ markdown_path=markdown_path,
2234
+ exact_diff_markdown_path=exact_diff_markdown_path,
2235
+ verify_exact_after_fast=verify_exact_after_fast,
2236
+ exact_component_backend="none",
2237
+ start=start,
2238
+ )
2239
+ if digest_result is not None:
2240
+ return digest_result
2241
+ try:
2242
+ import gdstk
2243
+
2244
+ old_lib = gdstk.read_gds(str(config.old_path))
2245
+ new_lib = gdstk.read_gds(str(config.new_path))
2246
+ hierarchy = compare_hierarchy(old_lib, new_lib, old_top=old_top_name, new_top=new_top_name)
2247
+ except Exception:
2248
+ return None
2249
+ top = config.top or old_top_name or new_top_name
2250
+ warnings = (
2251
+ "CUDA hierarchy fast path used; geometry differences are detected by canonical hierarchy digest and reported fail-closed.",
2252
+ )
2253
+ if hierarchy.identical:
2254
+ exact_result = ExactOracleResult(())
2255
+ else:
2256
+ fragment = DiffFragment(points=((0, 0), (1, 0), (1, 1), (0, 1)), bbox=(0, 0, 1, 1), twice_area=2)
2257
+ exact_result = ExactOracleResult((LayerExactDiff(layer=LayerSpec(0, 0), new_minus_old=(fragment,), xor=(fragment,)),))
2258
+ policy_result = classify_exact_differences(exact_result, failure_policy=config.failure_policy)
2259
+ prefilter = CpuTilePrefilterResult(candidate_tiles=())
2260
+ windows: tuple[CandidateWindow, ...] = ()
2261
+ timings = {"extract_s": 0.0, "exact_s": perf_counter() - start, "total_s": perf_counter() - start}
2262
+ artifacts: dict[str, str] = {}
2263
+ layer_reports = build_layer_reports(exact_result, policy_result=policy_result)
2264
+ report = CompareReport(
2265
+ status=policy_result.status,
2266
+ exact_verification_complete=hierarchy.identical,
2267
+ old_input=old_metadata | {"selected_top": hierarchy.old_top, "extraction": {"mode": "hierarchy-fast"}},
2268
+ new_input=new_metadata | {"selected_top": hierarchy.new_top, "extraction": {"mode": "hierarchy-fast"}},
2269
+ semantics=config.to_json_dict(),
2270
+ selection={"old_top": hierarchy.old_top, "new_top": hierarchy.new_top, "layers": "all"},
2271
+ backend=backend_info | {"exact_mode": False, "hierarchy_fast_path": True},
2272
+ prefilter=prefilter.to_json(),
2273
+ exact_fallback={
2274
+ "mode": "hierarchy-digest",
2275
+ "window_count": len(hierarchy.changed_references),
2276
+ "escalated_layers": [],
2277
+ "failures": [],
2278
+ "old_digest": hierarchy.old_digest,
2279
+ "new_digest": hierarchy.new_digest,
2280
+ "matched_subtree_count": hierarchy.matched_subtree_count,
2281
+ "changed_reference_count": len(hierarchy.changed_references),
2282
+ },
2283
+ layers=layer_reports,
2284
+ allowed_policy=policy_result.effective_policy,
2285
+ unexpected_summary={
2286
+ "expected_area_dbu2": policy_result.to_json()["expected_area_dbu2"],
2287
+ "unexpected_area_dbu2": policy_result.to_json()["unexpected_area_dbu2"],
2288
+ },
2289
+ timings=timings,
2290
+ warnings=warnings,
2291
+ artifacts=artifacts,
2292
+ command=command,
2293
+ failure_policy=config.failure_policy,
2294
+ )
2295
+ if markdown_path is not None:
2296
+ markdown_path = Path(markdown_path)
2297
+ markdown_path.parent.mkdir(parents=True, exist_ok=True)
2298
+ markdown_path.write_text(
2299
+ render_design_history_entry(
2300
+ "GDS comparator hierarchy-fast result",
2301
+ policy_result,
2302
+ tests_run=("gdsdiff hierarchy-fast",),
2303
+ notes=warnings,
2304
+ ),
2305
+ encoding="utf-8",
2306
+ )
2307
+ artifacts["markdown"] = str(markdown_path)
2308
+ if exact_diff_markdown_path is not None:
2309
+ artifacts["exact_diff_markdown"] = str(
2310
+ write_exact_diff_markdown(
2311
+ policy_result,
2312
+ exact_diff_markdown_path,
2313
+ title="GDS comparator hierarchy-fast exact diff",
2314
+ source_mode="hierarchy-digest",
2315
+ exact_verification_complete=hierarchy.identical,
2316
+ ).path
2317
+ )
2318
+ if json_report_path is not None:
2319
+ artifacts["json_report"] = str(json_report_path)
2320
+ report = _report_with_artifacts(report, artifacts)
2321
+ report.write_json(json_report_path)
2322
+ return CompareRunResult(
2323
+ status=policy_result.status,
2324
+ report=report,
2325
+ exact_result=exact_result,
2326
+ policy_result=policy_result,
2327
+ prefilter=prefilter,
2328
+ windows=windows,
2329
+ artifacts=artifacts,
2330
+ timings=timings,
2331
+ )
2332
+
2333
+
2334
+ def _try_accelerated_byte_identity_fast_path(
2335
+ config: CompareConfig,
2336
+ *,
2337
+ backend_info: dict[str, object],
2338
+ old_metadata: dict[str, object],
2339
+ new_metadata: dict[str, object],
2340
+ old_top_name: str | None,
2341
+ new_top_name: str | None,
2342
+ command: tuple[str, ...],
2343
+ json_report_path: str | Path | None,
2344
+ markdown_path: str | Path | None,
2345
+ exact_diff_markdown_path: str | Path | None,
2346
+ verify_exact_after_fast: bool,
2347
+ exact_component_backend: str,
2348
+ start: float,
2349
+ ) -> CompareRunResult | None:
2350
+ identity = _file_identity(config.old_path, config.new_path, preferred_backend=str(backend_info.get("name", "")))
2351
+ if identity.same:
2352
+ exact_result = ExactOracleResult(())
2353
+ exact_complete = True
2354
+ else:
2355
+ fragment = DiffFragment(points=((0, 0), (1, 0), (1, 1), (0, 1)), bbox=(0, 0, 1, 1), twice_area=2)
2356
+ exact_result = ExactOracleResult((LayerExactDiff(layer=LayerSpec(0, 0), new_minus_old=(fragment,), xor=(fragment,)),))
2357
+ exact_complete = False
2358
+ policy_result = classify_exact_differences(exact_result, failure_policy=config.failure_policy)
2359
+ fast_status = policy_result.status
2360
+ fast_status_s = perf_counter() - start
2361
+ prefilter = CpuTilePrefilterResult(candidate_tiles=())
2362
+ windows: tuple[CandidateWindow, ...] = ()
2363
+ verification: _FullExactVerification | None = None
2364
+ if verify_exact_after_fast:
2365
+ verification = _run_full_exact_verification(config, exact_component_backend=exact_component_backend)
2366
+ exact_result = verification.exact_result
2367
+ policy_result = verification.policy_result
2368
+ exact_complete = True
2369
+ timings = {"extract_s": 0.0, "exact_s": fast_status_s, "fast_status_s": fast_status_s}
2370
+ if verification is not None:
2371
+ timings.update(
2372
+ {
2373
+ "extract_s": verification.extract_s,
2374
+ "exact_s": verification.exact_s,
2375
+ "exact_verification_s": verification.extract_s + verification.exact_s,
2376
+ }
2377
+ )
2378
+ timings["total_s"] = perf_counter() - start
2379
+ artifacts: dict[str, str] = {}
2380
+ warnings = _fast_path_warnings(verified=verification is not None)
2381
+ exact_fallback_mode = "file-digest-fast-plus-full-exact" if verification is not None else "file-digest-fail-closed"
2382
+ old_input = old_metadata | {"selected_top": old_top_name, "extraction": {"mode": "accelerated-byte-identity"}}
2383
+ new_input = new_metadata | {"selected_top": new_top_name, "extraction": {"mode": "accelerated-byte-identity"}}
2384
+ selection = {"old_top": old_top_name, "new_top": new_top_name, "layers": "all"}
2385
+ layers = build_layer_reports(exact_result, policy_result=policy_result)
2386
+ if verification is not None:
2387
+ old_input = verification.old_input
2388
+ new_input = verification.new_input
2389
+ selection = verification.selection
2390
+ layers = verification.layers
2391
+ exact_fallback = {
2392
+ "mode": exact_fallback_mode,
2393
+ "window_count": 0,
2394
+ "escalated_layers": [],
2395
+ "failures": [],
2396
+ "old_identity": identity.old_token,
2397
+ "new_identity": identity.new_token,
2398
+ "identity_method": identity.method,
2399
+ "old_size": identity.old_size,
2400
+ "new_size": identity.new_size,
2401
+ "fast_status": fast_status.value,
2402
+ }
2403
+ if verification is not None:
2404
+ exact_fallback["exact_component_backend"] = verification.exact_component_backend
2405
+ report = CompareReport(
2406
+ status=policy_result.status,
2407
+ exact_verification_complete=exact_complete,
2408
+ old_input=old_input,
2409
+ new_input=new_input,
2410
+ semantics=config.to_json_dict(),
2411
+ selection=selection,
2412
+ backend=backend_info
2413
+ | {
2414
+ "exact_mode": False,
2415
+ "file_digest_fast_path": True,
2416
+ "accelerated_byte_identity_fast_path": True,
2417
+ "exact_verification_after_fast": verification is not None,
2418
+ },
2419
+ prefilter=prefilter.to_json(),
2420
+ exact_fallback=exact_fallback,
2421
+ layers=layers,
2422
+ allowed_policy=policy_result.effective_policy,
2423
+ unexpected_summary={
2424
+ "expected_area_dbu2": policy_result.to_json()["expected_area_dbu2"],
2425
+ "unexpected_area_dbu2": policy_result.to_json()["unexpected_area_dbu2"],
2426
+ },
2427
+ timings=timings,
2428
+ warnings=warnings,
2429
+ artifacts=artifacts,
2430
+ command=command,
2431
+ failure_policy=config.failure_policy,
2432
+ )
2433
+ if markdown_path is not None:
2434
+ markdown_path = Path(markdown_path)
2435
+ markdown_path.parent.mkdir(parents=True, exist_ok=True)
2436
+ markdown_path.write_text(
2437
+ render_design_history_entry(
2438
+ "GDS comparator accelerated-byte-identity result",
2439
+ policy_result,
2440
+ tests_run=("gdsdiff accelerated-byte-identity",),
2441
+ notes=warnings,
2442
+ ),
2443
+ encoding="utf-8",
2444
+ )
2445
+ artifacts["markdown"] = str(markdown_path)
2446
+ if exact_diff_markdown_path is not None:
2447
+ artifacts["exact_diff_markdown"] = str(
2448
+ write_exact_diff_markdown(
2449
+ policy_result,
2450
+ exact_diff_markdown_path,
2451
+ title="GDS comparator accelerated-byte-identity exact diff",
2452
+ source_mode=exact_fallback_mode,
2453
+ exact_verification_complete=exact_complete,
2454
+ polygon_inventory_delta=verification.polygon_inventory_delta if verification is not None else None,
2455
+ ).path
2456
+ )
2457
+ if json_report_path is not None:
2458
+ artifacts["json_report"] = str(json_report_path)
2459
+ report = _report_with_artifacts(report, artifacts)
2460
+ report.write_json(json_report_path)
2461
+ return CompareRunResult(
2462
+ status=policy_result.status,
2463
+ report=report,
2464
+ exact_result=exact_result,
2465
+ policy_result=policy_result,
2466
+ prefilter=prefilter,
2467
+ windows=windows,
2468
+ artifacts=artifacts,
2469
+ timings=timings,
2470
+ )
2471
+
2472
+
2473
+ def _select_exact_component_backend(name: str) -> _ExactComponentSelection:
2474
+ normalized = str(name or "none").strip().lower()
2475
+ if normalized in {"none", "gdstk", "gdstk-full"}:
2476
+ return _ExactComponentSelection(name=normalized, component_partition=False, executor=None)
2477
+ if normalized in {"gdstk-component", "component-gdstk"}:
2478
+ backend = GdstkExactComponentBackend()
2479
+ return _ExactComponentSelection(
2480
+ name=normalized,
2481
+ component_partition=True,
2482
+ executor=backend.compare_components,
2483
+ backend=backend,
2484
+ )
2485
+ if normalized in {"rect", "manhattan-rect"}:
2486
+ backend = ManhattanRectExactComponentBackend(fallback_to_gdstk=False)
2487
+ return _ExactComponentSelection(
2488
+ name=normalized,
2489
+ component_partition=True,
2490
+ executor=backend.compare_components,
2491
+ backend=backend,
2492
+ )
2493
+ if normalized in {"rect-fallback", "manhattan-rect-fallback"}:
2494
+ backend = ManhattanRectExactComponentBackend(fallback_to_gdstk=True)
2495
+ return _ExactComponentSelection(
2496
+ name=normalized,
2497
+ component_partition=True,
2498
+ executor=backend.compare_components,
2499
+ backend=backend,
2500
+ )
2501
+ if normalized in {"cuda-rect", "cuda-manhattan-rect"}:
2502
+ backend = CudaManhattanRectExactComponentBackend(fallback_to_gdstk=False)
2503
+ return _ExactComponentSelection(
2504
+ name=normalized,
2505
+ component_partition=True,
2506
+ executor=backend.compare_components,
2507
+ backend=backend,
2508
+ )
2509
+ if normalized in {"cuda-rect-fallback", "cuda-manhattan-rect-fallback"}:
2510
+ backend = CudaManhattanRectExactComponentBackend(fallback_to_gdstk=True)
2511
+ return _ExactComponentSelection(
2512
+ name=normalized,
2513
+ component_partition=True,
2514
+ executor=backend.compare_components,
2515
+ backend=backend,
2516
+ )
2517
+ if normalized in {"metal-rect", "metal-manhattan-rect"}:
2518
+ backend = MetalManhattanRectExactComponentBackend(fallback_to_gdstk=False)
2519
+ return _ExactComponentSelection(
2520
+ name=normalized,
2521
+ component_partition=True,
2522
+ executor=backend.compare_components,
2523
+ backend=backend,
2524
+ )
2525
+ if normalized in {"metal-rect-fallback", "metal-manhattan-rect-fallback"}:
2526
+ backend = MetalManhattanRectExactComponentBackend(fallback_to_gdstk=True)
2527
+ return _ExactComponentSelection(
2528
+ name=normalized,
2529
+ component_partition=True,
2530
+ executor=backend.compare_components,
2531
+ backend=backend,
2532
+ )
2533
+ raise ValueError(f"unknown exact component backend: {name!r}")
2534
+
2535
+
2536
+ def _run_full_exact_verification(
2537
+ config: CompareConfig,
2538
+ *,
2539
+ exact_component_backend: str = "none",
2540
+ force_small_component_partition: bool = False,
2541
+ ) -> _FullExactVerification:
2542
+ start = perf_counter()
2543
+ old_metadata = read_gds_metadata(config.old_path)
2544
+ new_metadata = read_gds_metadata(config.new_path)
2545
+ old_top_name = config.old_top or config.top
2546
+ new_top_name = config.new_top or config.top
2547
+ if old_top_name is None and new_top_name is None:
2548
+ common_tops = sorted(set(old_metadata.top_names) & set(new_metadata.top_names))
2549
+ if len(common_tops) == 1:
2550
+ old_top_name = common_tops[0]
2551
+ new_top_name = common_tops[0]
2552
+ old_extraction, new_extraction = _extract_pair(
2553
+ config,
2554
+ old_top_name=old_top_name,
2555
+ new_top_name=new_top_name,
2556
+ )
2557
+ extract_s = perf_counter() - start
2558
+ exact_start = perf_counter()
2559
+ exact_selection = _select_exact_component_backend(exact_component_backend)
2560
+ exact_result = run_full_exact_oracle(
2561
+ old_extraction.buffer,
2562
+ new_extraction.buffer,
2563
+ component_partition=exact_selection.component_partition,
2564
+ component_executor=exact_selection.executor,
2565
+ force_small_component_partition=force_small_component_partition,
2566
+ )
2567
+ exact_s = perf_counter() - exact_start
2568
+ policy_result = classify_exact_differences(
2569
+ exact_result,
2570
+ config.allowed_regions,
2571
+ failure_policy=config.failure_policy,
2572
+ )
2573
+ layer_reports = build_layer_reports(
2574
+ exact_result,
2575
+ policy_result=policy_result,
2576
+ presence_by_layer=_presence_by_layer(old_extraction.buffer, new_extraction.buffer),
2577
+ polygon_counts_by_layer=_polygon_counts_by_layer(old_extraction.buffer, new_extraction.buffer),
2578
+ exact_status="complete",
2579
+ )
2580
+ return _FullExactVerification(
2581
+ exact_result=exact_result,
2582
+ policy_result=policy_result,
2583
+ old_input=_input_json(old_extraction, old_metadata.to_json()),
2584
+ new_input=_input_json(new_extraction, new_metadata.to_json()),
2585
+ selection={
2586
+ "old_top": old_extraction.top_name,
2587
+ "new_top": new_extraction.top_name,
2588
+ "layers": config.to_json_dict()["layers"],
2589
+ },
2590
+ layers=layer_reports,
2591
+ extract_s=extract_s,
2592
+ exact_s=exact_s,
2593
+ exact_component_backend=exact_selection.to_json(),
2594
+ polygon_inventory_delta=build_polygon_inventory_delta(old_extraction.buffer, new_extraction.buffer),
2595
+ )
2596
+
2597
+
2598
+ def _fast_path_warnings(*, verified: bool) -> tuple[str, ...]:
2599
+ if verified:
2600
+ return (
2601
+ "Accelerated byte-identity fast path produced the status verdict; full exact gdstk verification was run after the fast verdict and supplies the polygon-level evidence.",
2602
+ )
2603
+ return (
2604
+ "Accelerated byte-identity fast path used; differing files are reported fail-closed and require exact mode or verify_exact_after_fast for geometry evidence.",
2605
+ )
2606
+
2607
+
2608
+ def _accelerated_fast_path_eligible(config: CompareConfig, backend_info: dict[str, object]) -> bool:
2609
+ return (
2610
+ backend_info.get("name") in {Backend.CUDA.value, Backend.METAL.value}
2611
+ and not config.allowed_regions
2612
+ and config.layers == "all"
2613
+ )
2614
+
2615
+
2616
+ def _cuda_fast_path_eligible(config: CompareConfig, backend_info: dict[str, object]) -> bool:
2617
+ return _accelerated_fast_path_eligible(config, backend_info)
2618
+
2619
+
2620
+ def _try_cuda_file_digest_fast_path(*args, **kwargs) -> CompareRunResult | None:
2621
+ return _try_accelerated_byte_identity_fast_path(*args, **kwargs)
2622
+
2623
+
2624
+ def _file_sha256(path: Path) -> str:
2625
+ digest = hashlib.sha256()
2626
+ with Path(path).open("rb") as handle:
2627
+ for chunk in iter(lambda: handle.read(1024 * 1024), b""):
2628
+ digest.update(chunk)
2629
+ return digest.hexdigest()
2630
+
2631
+
2632
+ def _file_identity(old_path: Path, new_path: Path, *, preferred_backend: str | None = None) -> _FileIdentity:
2633
+ old_path = Path(old_path)
2634
+ new_path = Path(new_path)
2635
+ old_stat = old_path.stat()
2636
+ new_stat = new_path.stat()
2637
+ if old_stat.st_ino == new_stat.st_ino and old_stat.st_dev == new_stat.st_dev:
2638
+ token = f"inode:{old_stat.st_dev}:{old_stat.st_ino}:size:{old_stat.st_size}"
2639
+ return _FileIdentity(token, token, True, "inode", old_stat.st_size, new_stat.st_size)
2640
+ if old_stat.st_size != new_stat.st_size:
2641
+ return _FileIdentity(
2642
+ f"size:{old_stat.st_size}",
2643
+ f"size:{new_stat.st_size}",
2644
+ False,
2645
+ "size",
2646
+ old_stat.st_size,
2647
+ new_stat.st_size,
2648
+ )
2649
+ try:
2650
+ from .backends.identity import compare_file_identity_accelerated
2651
+
2652
+ cuda_result = compare_file_identity_accelerated(old_path, new_path, preferred_backend=preferred_backend)
2653
+ return _FileIdentity(
2654
+ f"{cuda_result.method}-size:{cuda_result.old_size}",
2655
+ f"{cuda_result.method}-size:{cuda_result.new_size}",
2656
+ cuda_result.same,
2657
+ cuda_result.method,
2658
+ cuda_result.old_size,
2659
+ cuda_result.new_size,
2660
+ )
2661
+ except Exception:
2662
+ pass
2663
+ old_digest = _file_sha256(old_path)
2664
+ new_digest = _file_sha256(new_path)
2665
+ return _FileIdentity(old_digest, new_digest, old_digest == new_digest, "sha256", old_stat.st_size, new_stat.st_size)
2666
+
2667
+
2668
+ def _select_backend(config: CompareConfig, *, exact: bool = False) -> dict[str, object]:
2669
+ requested = Backend.coerce(config.backend)
2670
+ capabilities = discover_native_capabilities()
2671
+ if (
2672
+ not exact
2673
+ and not capabilities.cuda_available
2674
+ and capabilities.nvcc_available
2675
+ and (requested == Backend.CUDA or (requested == Backend.AUTO and config.require_gpu))
2676
+ ):
2677
+ try:
2678
+ from .cuda_setup import prepare_cuda
2679
+
2680
+ prepare_cuda()
2681
+ capabilities = discover_native_capabilities()
2682
+ except Exception:
2683
+ pass
2684
+ base = {
2685
+ "requested": requested.value,
2686
+ "cuda_available": capabilities.cuda_available,
2687
+ "cuda_extension_available": capabilities.cuda_extension_available,
2688
+ "cuda_ctypes_available": capabilities.cuda_ctypes_available,
2689
+ "metal_available": capabilities.metal_available,
2690
+ "metal_ctypes_available": capabilities.metal_ctypes_available,
2691
+ "core_extension_available": capabilities.core_extension_available,
2692
+ "nvcc_available": capabilities.nvcc_available,
2693
+ }
2694
+ if requested == Backend.CPU:
2695
+ if config.require_gpu:
2696
+ raise ValueError("require_gpu cannot be satisfied by backend='cpu'")
2697
+ return base | {"name": Backend.CPU.value, "fallback_reason": None}
2698
+ if exact:
2699
+ return base | {"name": Backend.CPU.value, "fallback_reason": "exact mode uses the gdstk CPU oracle"}
2700
+ if requested == Backend.CUDA:
2701
+ if not capabilities.cuda_available:
2702
+ raise ValueError("backend='cuda' requested, but CUDA backend extension is not available")
2703
+ return base | {"name": Backend.CUDA.value, "fallback_reason": None}
2704
+ if requested == Backend.METAL:
2705
+ if not capabilities.metal_available:
2706
+ raise ValueError("backend='metal' requested, but Metal backend runtime is not available")
2707
+ return base | {"name": Backend.METAL.value, "fallback_reason": None}
2708
+ if requested == Backend.AUTO:
2709
+ if config.require_gpu:
2710
+ if capabilities.cuda_available:
2711
+ return base | {"name": Backend.CUDA.value, "fallback_reason": None}
2712
+ if capabilities.metal_available:
2713
+ return base | {"name": Backend.METAL.value, "fallback_reason": None}
2714
+ raise ValueError("require_gpu=True but no CUDA or Metal backend runtime is available")
2715
+ if capabilities.cuda_available:
2716
+ return base | {"name": Backend.CUDA.value, "fallback_reason": None}
2717
+ if capabilities.metal_available:
2718
+ return base | {"name": Backend.METAL.value, "fallback_reason": None}
2719
+ return base | {"name": Backend.CPU.value, "fallback_reason": "no CUDA or Metal backend runtime is available"}
2720
+ raise ValueError(f"backend {requested.value!r} is not implemented")
2721
+
2722
+
2723
+ def _run_tile_prefilter(
2724
+ backend_info: dict[str, object],
2725
+ old_buffer: PolygonBuffer,
2726
+ old_canonical_ids,
2727
+ new_buffer: PolygonBuffer,
2728
+ new_canonical_ids,
2729
+ tile_config: TileConfig,
2730
+ ) -> CpuTilePrefilterResult:
2731
+ if backend_info.get("name") == Backend.CUDA.value:
2732
+ from .backends.cuda_ctypes import CudaCtypesUnavailable, run_cuda_ctypes_tile_prefilter
2733
+
2734
+ try:
2735
+ return run_cuda_ctypes_tile_prefilter(old_buffer, old_canonical_ids, new_buffer, new_canonical_ids, tile_config)
2736
+ except CudaCtypesUnavailable:
2737
+ pass
2738
+ if backend_info.get("name") == Backend.METAL.value:
2739
+ from .backends.metal_ctypes import MetalCtypesUnavailable, run_metal_ctypes_tile_prefilter
2740
+
2741
+ try:
2742
+ return run_metal_ctypes_tile_prefilter(old_buffer, old_canonical_ids, new_buffer, new_canonical_ids, tile_config)
2743
+ except MetalCtypesUnavailable:
2744
+ pass
2745
+ try:
2746
+ from .backends.cpu_ctypes import run_cpu_ctypes_tile_prefilter
2747
+
2748
+ return run_cpu_ctypes_tile_prefilter(old_buffer, old_canonical_ids, new_buffer, new_canonical_ids, tile_config)
2749
+ except Exception:
2750
+ pass
2751
+ return run_cpu_tile_prefilter(old_buffer, old_canonical_ids, new_buffer, new_canonical_ids, tile_config)
2752
+
2753
+
2754
+ def _should_use_layer_exact(
2755
+ backend_info: dict[str, object],
2756
+ old_buffer: PolygonBuffer,
2757
+ new_buffer: PolygonBuffer,
2758
+ windows: tuple[CandidateWindow, ...],
2759
+ ) -> bool:
2760
+ if not windows:
2761
+ return False
2762
+ total_polygons = old_buffer.polygon_count + new_buffer.polygon_count
2763
+ if total_polygons == 0:
2764
+ return False
2765
+ estimated_window_polygons = sum(window.estimated_old_polygons + window.estimated_new_polygons for window in windows)
2766
+ if estimated_window_polygons > total_polygons * 2:
2767
+ return True
2768
+ if len(windows) > 64 and estimated_window_polygons > total_polygons:
2769
+ return True
2770
+ return False
2771
+
2772
+
2773
+ def _shared_canonical_ids(
2774
+ old_buffer: PolygonBuffer,
2775
+ new_buffer: PolygonBuffer,
2776
+ *,
2777
+ old_intern=None,
2778
+ new_intern=None,
2779
+ ) -> tuple[np.ndarray, np.ndarray]:
2780
+ import numpy as np
2781
+
2782
+ old_intern = old_intern if old_intern is not None else intern_polygons(old_buffer)
2783
+ new_intern = new_intern if new_intern is not None else intern_polygons(new_buffer)
2784
+ unique_blobs = tuple(sorted(set(old_intern.unique_blobs) | set(new_intern.unique_blobs)))
2785
+ id_by_blob = {blob: index for index, blob in enumerate(unique_blobs)}
2786
+ old_by_index = {record.polygon_index: id_by_blob[record.blob] for record in old_intern.records}
2787
+ new_by_index = {record.polygon_index: id_by_blob[record.blob] for record in new_intern.records}
2788
+ return (
2789
+ np.asarray([old_by_index[index] for index in range(old_buffer.polygon_count)], dtype=np.uint64),
2790
+ np.asarray([new_by_index[index] for index in range(new_buffer.polygon_count)], dtype=np.uint64),
2791
+ )
2792
+
2793
+
2794
+ def _input_json(extraction: ExtractionResult, metadata: dict[str, object]) -> dict[str, object]:
2795
+ data = dict(metadata)
2796
+ data.update({"selected_top": extraction.top_name, "extraction": extraction.stats.to_json()})
2797
+ return data
2798
+
2799
+
2800
+ def _presence_by_layer(old_buffer: PolygonBuffer, new_buffer: PolygonBuffer) -> dict[LayerSpec, tuple[bool, bool]]:
2801
+ old_layers = _layers_present(old_buffer)
2802
+ new_layers = _layers_present(new_buffer)
2803
+ return {layer: (layer in old_layers, layer in new_layers) for layer in sorted(old_layers | new_layers)}
2804
+
2805
+
2806
+ def _polygon_counts_by_layer(old_buffer: PolygonBuffer, new_buffer: PolygonBuffer) -> dict[LayerSpec, tuple[int, int]]:
2807
+ old_counts = _layer_polygon_counts(old_buffer)
2808
+ new_counts = _layer_polygon_counts(new_buffer)
2809
+ return {layer: (old_counts.get(layer, 0), new_counts.get(layer, 0)) for layer in sorted(set(old_counts) | set(new_counts))}
2810
+
2811
+
2812
+ def _layers_present(buffer: PolygonBuffer) -> set[LayerSpec]:
2813
+ return {buffer.layer_table.layers[int(layer_id)] for layer_id in buffer.layer_ids}
2814
+
2815
+
2816
+ def _layer_polygon_counts(buffer: PolygonBuffer) -> dict[LayerSpec, int]:
2817
+ counts: dict[LayerSpec, int] = {}
2818
+ for layer_id in buffer.layer_ids:
2819
+ layer = buffer.layer_table.layers[int(layer_id)]
2820
+ counts[layer] = counts.get(layer, 0) + 1
2821
+ return counts
2822
+
2823
+
2824
+ def _candidate_tile_counts(prefilter: CpuTilePrefilterResult) -> dict[LayerSpec, int]:
2825
+ counts: dict[LayerSpec, int] = {}
2826
+ for tile in prefilter.candidate_tiles:
2827
+ counts[tile.layer] = counts.get(tile.layer, 0) + 1
2828
+ for candidate in prefilter.oversized_candidates:
2829
+ counts[candidate.layer] = counts.get(candidate.layer, 0) + 1
2830
+ return counts
2831
+
2832
+
2833
+ def _window_counts(windows: tuple[CandidateWindow, ...]) -> dict[LayerSpec, int]:
2834
+ counts: dict[LayerSpec, int] = {}
2835
+ for window in windows:
2836
+ counts[window.layer] = counts.get(window.layer, 0) + 1
2837
+ return counts
2838
+
2839
+
2840
+ def _record_exact_markdown_artifacts(
2841
+ artifacts: dict[str, str],
2842
+ written: ExactDiffMarkdownWriteResult,
2843
+ ) -> None:
2844
+ artifacts["exact_diff_markdown"] = str(written.path)
2845
+ if written.vertex_sidecar_path is not None:
2846
+ artifacts["vertex_sidecar"] = str(written.vertex_sidecar_path)
2847
+
2848
+
2849
+ def _coverage_component_count(policy_result: PolicyClassificationResult) -> int:
2850
+ return sum(len(layer.components) for layer in policy_result.layers)
2851
+
2852
+
2853
+ def _coverage_twice_area(policy_result: PolicyClassificationResult) -> int:
2854
+ return sum(component.twice_area for layer in policy_result.layers for component in layer.components)
2855
+
2856
+
2857
+ def _neutral_compare_status(
2858
+ policy_result: PolicyClassificationResult,
2859
+ *,
2860
+ polygon_inventory_delta: object | None,
2861
+ exact_complete: bool,
2862
+ unsupported_feature_count: int = 0,
2863
+ audit_match: bool | None = None,
2864
+ ) -> CompareStatus:
2865
+ if audit_match is False:
2866
+ return CompareStatus.INTERNAL_MISMATCH
2867
+ if unsupported_feature_count:
2868
+ return CompareStatus.UNSUPPORTED_INPUT
2869
+ if not exact_complete:
2870
+ return CompareStatus.INCOMPLETE_EVIDENCE
2871
+ if _coverage_twice_area(policy_result) or _inventory_delta_count(polygon_inventory_delta):
2872
+ return CompareStatus.DIFFERENCES_FOUND
2873
+ return CompareStatus.IDENTICAL
2874
+
2875
+
2876
+ def _neutral_evidence_report_fields(
2877
+ status: CompareStatus,
2878
+ policy_result: PolicyClassificationResult,
2879
+ *,
2880
+ polygon_inventory_delta: object | None,
2881
+ exact_complete: bool,
2882
+ unsupported_feature_count: int,
2883
+ vertex_sidecar_path: str | None,
2884
+ ) -> dict[str, object]:
2885
+ coverage_area = _coverage_twice_area(policy_result)
2886
+ inventory_delta_count = _inventory_delta_count(polygon_inventory_delta)
2887
+ inventory_changed = inventory_delta_count > 0
2888
+ return {
2889
+ "evidence_status": status.value if status in {
2890
+ CompareStatus.IDENTICAL,
2891
+ CompareStatus.DIFFERENCES_FOUND,
2892
+ CompareStatus.UNSUPPORTED_INPUT,
2893
+ CompareStatus.INCOMPLETE_EVIDENCE,
2894
+ CompareStatus.INTERNAL_MISMATCH,
2895
+ } else (
2896
+ "identical"
2897
+ if status == CompareStatus.EQUIVALENT
2898
+ else "differences-found"
2899
+ if status in {CompareStatus.EXPECTED_DIFFERENCES_ONLY, CompareStatus.UNEXPECTED_DIFFERENCES}
2900
+ else "incomplete-evidence"
2901
+ ),
2902
+ "differences_found": bool(coverage_area or inventory_changed),
2903
+ "coverage_changed": bool(coverage_area),
2904
+ "inventory_changed": inventory_changed,
2905
+ "coverage_equal_but_inventory_changed": bool(exact_complete and coverage_area == 0 and inventory_changed),
2906
+ "coverage_component_count": _coverage_component_count(policy_result),
2907
+ "inventory_delta_count": inventory_delta_count,
2908
+ "vertex_sidecar_path": vertex_sidecar_path,
2909
+ "unsupported_feature_count": unsupported_feature_count,
2910
+ }
2911
+
2912
+
2913
+ def _inventory_delta_count(polygon_inventory_delta: object | None) -> int:
2914
+ return int(getattr(polygon_inventory_delta, "changed_count", 0) or 0)
2915
+
2916
+
2917
+ def _gpu_polygon_unsupported_feature_count(exact_fallback: dict[str, object], *, exact_complete: bool) -> int:
2918
+ if exact_complete:
2919
+ return 0
2920
+ failures = exact_fallback.get("failures", ())
2921
+ return len(failures) if isinstance(failures, list) else 0
2922
+
2923
+
2924
+ def _report_with_artifacts(report: CompareReport, artifacts: dict[str, str]) -> CompareReport:
2925
+ return CompareReport(
2926
+ status=report.status,
2927
+ exact_verification_complete=report.exact_verification_complete,
2928
+ old_input=report.old_input,
2929
+ new_input=report.new_input,
2930
+ semantics=report.semantics,
2931
+ selection=report.selection,
2932
+ backend=report.backend,
2933
+ prefilter=report.prefilter,
2934
+ exact_fallback=report.exact_fallback,
2935
+ layers=report.layers,
2936
+ allowed_policy=report.allowed_policy,
2937
+ unexpected_summary=report.unexpected_summary,
2938
+ timings=report.timings,
2939
+ memory=report.memory,
2940
+ warnings=report.warnings,
2941
+ errors=report.errors,
2942
+ artifacts=artifacts,
2943
+ evidence_status=report.evidence_status,
2944
+ differences_found=report.differences_found,
2945
+ coverage_changed=report.coverage_changed,
2946
+ inventory_changed=report.inventory_changed,
2947
+ coverage_equal_but_inventory_changed=report.coverage_equal_but_inventory_changed,
2948
+ coverage_component_count=report.coverage_component_count,
2949
+ inventory_delta_count=report.inventory_delta_count,
2950
+ vertex_sidecar_path=report.vertex_sidecar_path,
2951
+ unsupported_feature_count=report.unsupported_feature_count,
2952
+ command=report.command,
2953
+ failure_policy=report.failure_policy,
2954
+ tool_version=report.tool_version,
2955
+ )