gdsdiff 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. gdsdiff/__init__.py +148 -0
  2. gdsdiff/__main__.py +7 -0
  3. gdsdiff/_environment.py +12 -0
  4. gdsdiff/_version.py +34 -0
  5. gdsdiff/acceptance_evidence.py +530 -0
  6. gdsdiff/api.py +2955 -0
  7. gdsdiff/backends/__init__.py +26 -0
  8. gdsdiff/backends/base.py +77 -0
  9. gdsdiff/backends/boundary_loop_ctypes.py +191 -0
  10. gdsdiff/backends/cpu.py +20 -0
  11. gdsdiff/backends/cpu_ctypes.py +255 -0
  12. gdsdiff/backends/cuda.py +40 -0
  13. gdsdiff/backends/cuda_ctypes.py +1297 -0
  14. gdsdiff/backends/exact.py +424 -0
  15. gdsdiff/backends/geometry_ctypes.py +252 -0
  16. gdsdiff/backends/identity.py +53 -0
  17. gdsdiff/backends/metal.py +198 -0
  18. gdsdiff/backends/metal_ctypes.py +866 -0
  19. gdsdiff/backends/polygon_extract_ctypes.py +233 -0
  20. gdsdiff/backends/structural_ctypes.py +130 -0
  21. gdsdiff/boundary_loops.py +616 -0
  22. gdsdiff/cache.py +544 -0
  23. gdsdiff/canonicalize.py +176 -0
  24. gdsdiff/cli.py +352 -0
  25. gdsdiff/config.py +257 -0
  26. gdsdiff/cuda_setup.py +174 -0
  27. gdsdiff/design_history.py +65 -0
  28. gdsdiff/diagnostics.py +42 -0
  29. gdsdiff/diff_gds.py +66 -0
  30. gdsdiff/exact_diff_markdown.py +979 -0
  31. gdsdiff/exact_oracle.py +649 -0
  32. gdsdiff/extract.py +376 -0
  33. gdsdiff/gds_metadata.py +43 -0
  34. gdsdiff/geometry.py +112 -0
  35. gdsdiff/grid.py +143 -0
  36. gdsdiff/hierarchy.py +215 -0
  37. gdsdiff/hierarchy_extract.py +263 -0
  38. gdsdiff/inventory.py +153 -0
  39. gdsdiff/multiprocessing_support.py +39 -0
  40. gdsdiff/native.py +135 -0
  41. gdsdiff/native_cache.py +265 -0
  42. gdsdiff/native_src/cpu_prefilter.cpp +349 -0
  43. gdsdiff/native_src/gds_boundary_loops.cpp +505 -0
  44. gdsdiff/native_src/gds_geometry_scan.cpp +601 -0
  45. gdsdiff/native_src/gds_polygon_extract.cpp +594 -0
  46. gdsdiff/native_src/gds_structural_scan.cpp +335 -0
  47. gdsdiff/native_src/gdsdiff/CMakeLists.txt +74 -0
  48. gdsdiff/native_src/gdsdiff/core.cpp +191 -0
  49. gdsdiff/native_src/gdsdiff/core.hpp +96 -0
  50. gdsdiff/native_src/gdsdiff/cuda_assignment.cu +304 -0
  51. gdsdiff/native_src/gdsdiff/cuda_assignment.cuh +33 -0
  52. gdsdiff/native_src/gdsdiff/cuda_candidates.cu +133 -0
  53. gdsdiff/native_src/gdsdiff/cuda_candidates.cuh +17 -0
  54. gdsdiff/native_src/gdsdiff/cuda_ctypes.cu +4528 -0
  55. gdsdiff/native_src/gdsdiff/cuda_memory.cu +194 -0
  56. gdsdiff/native_src/gdsdiff/cuda_memory.cuh +34 -0
  57. gdsdiff/native_src/gdsdiff/metal_ctypes.mm +2791 -0
  58. gdsdiff/native_src/gdsdiff/python_bindings.cpp +21 -0
  59. gdsdiff/native_src/gdsdiff/test_core.cpp +93 -0
  60. gdsdiff/native_src/gdsdiff/test_cuda_assignment.cu +109 -0
  61. gdsdiff/native_src/gdsdiff/test_cuda_candidates.cu +94 -0
  62. gdsdiff/native_src/gdsdiff/test_cuda_memory.cu +97 -0
  63. gdsdiff/policy.py +305 -0
  64. gdsdiff/polygon_components.py +860 -0
  65. gdsdiff/profiles.py +152 -0
  66. gdsdiff/py.typed +1 -0
  67. gdsdiff/rect_coverage.py +384 -0
  68. gdsdiff/report.py +354 -0
  69. gdsdiff/schemas/gdsdiff_report-v1.schema.json +92 -0
  70. gdsdiff/semantics.py +75 -0
  71. gdsdiff/source_edge_diff.py +1120 -0
  72. gdsdiff/spatial.py +71 -0
  73. gdsdiff/structural_guard.py +161 -0
  74. gdsdiff/surplus_coverage.py +255 -0
  75. gdsdiff/synthetic_suite.py +1643 -0
  76. gdsdiff/templates.py +709 -0
  77. gdsdiff/tiling.py +153 -0
  78. gdsdiff/windowed_exact.py +270 -0
  79. gdsdiff/windows.py +184 -0
  80. gdsdiff-0.1.0.dist-info/METADATA +135 -0
  81. gdsdiff-0.1.0.dist-info/RECORD +85 -0
  82. gdsdiff-0.1.0.dist-info/WHEEL +5 -0
  83. gdsdiff-0.1.0.dist-info/entry_points.txt +4 -0
  84. gdsdiff-0.1.0.dist-info/licenses/LICENSE +674 -0
  85. gdsdiff-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,601 @@
1
+ #include <algorithm>
2
+ #include <cstdint>
3
+ #include <cstdlib>
4
+ #include <cstring>
5
+ #include <fstream>
6
+ #include <limits>
7
+ #include <map>
8
+ #include <set>
9
+ #include <stdexcept>
10
+ #include <string>
11
+ #include <vector>
12
+
13
+ namespace {
14
+
15
+ thread_local std::string g_last_error;
16
+
17
+ struct LayerKey {
18
+ std::int32_t layer = 0;
19
+ std::int32_t datatype = 0;
20
+
21
+ bool operator<(const LayerKey& other) const {
22
+ return layer < other.layer || (layer == other.layer && datatype < other.datatype);
23
+ }
24
+ };
25
+
26
+ struct BBox {
27
+ std::int64_t x0 = 0;
28
+ std::int64_t y0 = 0;
29
+ std::int64_t x1 = 0;
30
+ std::int64_t y1 = 0;
31
+ bool valid = false;
32
+ };
33
+
34
+ struct LayerSummary {
35
+ std::uint64_t count = 0;
36
+ BBox bbox;
37
+ };
38
+
39
+ struct Reference {
40
+ std::string child;
41
+ std::uint64_t columns = 1;
42
+ std::uint64_t rows = 1;
43
+ std::int64_t ox = 0;
44
+ std::int64_t oy = 0;
45
+ std::int64_t cx = 0;
46
+ std::int64_t cy = 0;
47
+ std::int64_t rx = 0;
48
+ std::int64_t ry = 0;
49
+ bool has_xy = false;
50
+ std::uint64_t raw_hash = 0;
51
+ };
52
+
53
+ struct Cell {
54
+ std::map<LayerKey, LayerSummary> layers;
55
+ std::vector<Reference> references;
56
+ std::uint64_t local_shape_count = 0;
57
+ std::uint64_t local_geometry_hash = 1469598103934665603ull;
58
+ };
59
+
60
+ struct FlattenedSummary {
61
+ std::map<LayerKey, LayerSummary> layers;
62
+ std::uint64_t shape_count = 0;
63
+ std::uint64_t geometry_hash = 1469598103934665603ull;
64
+ };
65
+
66
+ std::uint16_t read_u16(const unsigned char* p) {
67
+ return static_cast<std::uint16_t>((static_cast<std::uint16_t>(p[0]) << 8) | p[1]);
68
+ }
69
+
70
+ std::int16_t read_i16(const unsigned char* p) {
71
+ return static_cast<std::int16_t>(read_u16(p));
72
+ }
73
+
74
+ std::int32_t read_i32(const unsigned char* p) {
75
+ return (static_cast<std::int32_t>(p[0]) << 24) | (static_cast<std::int32_t>(p[1]) << 16)
76
+ | (static_cast<std::int32_t>(p[2]) << 8) | static_cast<std::int32_t>(p[3]);
77
+ }
78
+
79
+ std::string decode_string(const unsigned char* data, std::size_t size) {
80
+ while (size && data[size - 1] == 0) {
81
+ --size;
82
+ }
83
+ return std::string(reinterpret_cast<const char*>(data), size);
84
+ }
85
+
86
+ std::uint64_t fnv1a_update(std::uint64_t hash, const unsigned char* data, std::size_t size) {
87
+ constexpr std::uint64_t prime = 1099511628211ull;
88
+ for (std::size_t i = 0; i < size; ++i) {
89
+ hash ^= static_cast<std::uint64_t>(data[i]);
90
+ hash *= prime;
91
+ }
92
+ return hash;
93
+ }
94
+
95
+ std::uint64_t fnv1a_u64(std::uint64_t hash, std::uint64_t value) {
96
+ unsigned char bytes[8];
97
+ for (int i = 0; i < 8; ++i) {
98
+ bytes[i] = static_cast<unsigned char>((value >> (56 - i * 8)) & 0xff);
99
+ }
100
+ return fnv1a_update(hash, bytes, sizeof(bytes));
101
+ }
102
+
103
+ std::uint64_t fnv1a_i64(std::uint64_t hash, std::int64_t value) {
104
+ return fnv1a_u64(hash, static_cast<std::uint64_t>(value));
105
+ }
106
+
107
+ std::vector<unsigned char> read_file(const char* path) {
108
+ std::ifstream in(path, std::ios::binary | std::ios::ate);
109
+ if (!in) {
110
+ throw std::runtime_error("unable to open GDS file");
111
+ }
112
+ const auto size = in.tellg();
113
+ if (size < 0) {
114
+ throw std::runtime_error("unable to determine GDS file size");
115
+ }
116
+ std::vector<unsigned char> data(static_cast<std::size_t>(size));
117
+ in.seekg(0, std::ios::beg);
118
+ if (!data.empty()) {
119
+ in.read(reinterpret_cast<char*>(data.data()), static_cast<std::streamsize>(data.size()));
120
+ if (!in) {
121
+ throw std::runtime_error("unable to read GDS file");
122
+ }
123
+ }
124
+ return data;
125
+ }
126
+
127
+ void bbox_include(BBox& target, const BBox& source) {
128
+ if (!source.valid) {
129
+ return;
130
+ }
131
+ if (!target.valid) {
132
+ target = source;
133
+ return;
134
+ }
135
+ target.x0 = std::min(target.x0, source.x0);
136
+ target.y0 = std::min(target.y0, source.y0);
137
+ target.x1 = std::max(target.x1, source.x1);
138
+ target.y1 = std::max(target.y1, source.y1);
139
+ }
140
+
141
+ BBox shifted_bbox(const BBox& bbox, std::int64_t dx, std::int64_t dy) {
142
+ if (!bbox.valid) {
143
+ return bbox;
144
+ }
145
+ return BBox{bbox.x0 + dx, bbox.y0 + dy, bbox.x1 + dx, bbox.y1 + dy, true};
146
+ }
147
+
148
+ BBox bbox_from_xy(const std::vector<std::pair<std::int64_t, std::int64_t>>& xy) {
149
+ BBox bbox;
150
+ for (const auto& point : xy) {
151
+ BBox p{point.first, point.second, point.first, point.second, true};
152
+ bbox_include(bbox, p);
153
+ }
154
+ return bbox;
155
+ }
156
+
157
+ std::vector<std::pair<std::int64_t, std::int64_t>> parse_xy(const unsigned char* payload, std::size_t payload_size) {
158
+ if (payload_size % 8 != 0) {
159
+ throw std::runtime_error("invalid XY payload length");
160
+ }
161
+ std::vector<std::pair<std::int64_t, std::int64_t>> points;
162
+ points.reserve(payload_size / 8);
163
+ for (std::size_t i = 0; i < payload_size; i += 8) {
164
+ points.push_back({read_i32(payload + i), read_i32(payload + i + 4)});
165
+ }
166
+ return points;
167
+ }
168
+
169
+ void add_layer_summary(std::map<LayerKey, LayerSummary>& layers, LayerKey key, std::uint64_t count, const BBox& bbox) {
170
+ if (count == 0) {
171
+ return;
172
+ }
173
+ auto& summary = layers[key];
174
+ if (summary.count > std::numeric_limits<std::uint64_t>::max() - count) {
175
+ throw std::overflow_error("layer shape count overflow");
176
+ }
177
+ summary.count += count;
178
+ bbox_include(summary.bbox, bbox);
179
+ }
180
+
181
+ std::map<std::string, Cell> parse_gds(const char* path, bool detail) {
182
+ const auto data = read_file(path);
183
+ std::map<std::string, Cell> cells;
184
+ std::size_t offset = 0;
185
+ std::string current_name;
186
+ Cell current_cell;
187
+ int element_kind = -1;
188
+ std::size_t element_start = 0;
189
+ LayerKey pending_layer;
190
+ std::string pending_sname;
191
+ Reference pending_reference;
192
+
193
+ while (offset + 4 <= data.size()) {
194
+ const std::uint16_t record_len = read_u16(data.data() + offset);
195
+ const int record_type = data[offset + 2];
196
+ if (record_len < 4 || offset + record_len > data.size()) {
197
+ throw std::runtime_error("invalid GDS record length");
198
+ }
199
+ const unsigned char* payload = data.data() + offset + 4;
200
+ const std::size_t payload_size = record_len - 4;
201
+ offset += record_len;
202
+
203
+ switch (record_type) {
204
+ case 0x05: // BGNSTR
205
+ current_name.clear();
206
+ current_cell = Cell{};
207
+ break;
208
+ case 0x06: // STRNAME
209
+ current_name = decode_string(payload, payload_size);
210
+ break;
211
+ case 0x08: // BOUNDARY
212
+ case 0x09: // PATH
213
+ element_kind = record_type;
214
+ element_start = offset - record_len;
215
+ pending_layer = LayerKey{};
216
+ break;
217
+ case 0x0A: // SREF
218
+ case 0x0B: // AREF
219
+ element_kind = record_type;
220
+ element_start = offset - record_len;
221
+ pending_reference = Reference{};
222
+ pending_sname.clear();
223
+ break;
224
+ case 0x0D: // LAYER
225
+ if ((element_kind == 0x08 || element_kind == 0x09) && payload_size >= 2) {
226
+ pending_layer.layer = read_i16(payload);
227
+ }
228
+ break;
229
+ case 0x0E: // DATATYPE
230
+ if ((element_kind == 0x08 || element_kind == 0x09) && payload_size >= 2) {
231
+ pending_layer.datatype = read_i16(payload);
232
+ }
233
+ break;
234
+ case 0x12: // SNAME
235
+ if (element_kind == 0x0A || element_kind == 0x0B) {
236
+ pending_sname = decode_string(payload, payload_size);
237
+ }
238
+ break;
239
+ case 0x13: // COLROW
240
+ if (element_kind == 0x0B) {
241
+ if (payload_size < 4) {
242
+ throw std::runtime_error("invalid COLROW record");
243
+ }
244
+ const auto columns = read_i16(payload);
245
+ const auto rows = read_i16(payload + 2);
246
+ if (columns <= 0 || rows <= 0) {
247
+ throw std::runtime_error("invalid AREF repetition");
248
+ }
249
+ pending_reference.columns = static_cast<std::uint64_t>(columns);
250
+ pending_reference.rows = static_cast<std::uint64_t>(rows);
251
+ }
252
+ break;
253
+ case 0x10: { // XY
254
+ if (element_kind == 0x08 || element_kind == 0x09) {
255
+ ++current_cell.local_shape_count;
256
+ if (detail) {
257
+ const auto xy = parse_xy(payload, payload_size);
258
+ const BBox bbox = bbox_from_xy(xy);
259
+ add_layer_summary(current_cell.layers, pending_layer, 1, bbox);
260
+ current_cell.local_geometry_hash = fnv1a_u64(current_cell.local_geometry_hash, static_cast<std::uint64_t>(pending_layer.layer));
261
+ current_cell.local_geometry_hash = fnv1a_u64(current_cell.local_geometry_hash, static_cast<std::uint64_t>(pending_layer.datatype));
262
+ current_cell.local_geometry_hash = fnv1a_i64(current_cell.local_geometry_hash, bbox.x0);
263
+ current_cell.local_geometry_hash = fnv1a_i64(current_cell.local_geometry_hash, bbox.y0);
264
+ current_cell.local_geometry_hash = fnv1a_i64(current_cell.local_geometry_hash, bbox.x1);
265
+ current_cell.local_geometry_hash = fnv1a_i64(current_cell.local_geometry_hash, bbox.y1);
266
+ current_cell.local_geometry_hash = fnv1a_update(current_cell.local_geometry_hash, data.data() + element_start, offset - element_start);
267
+ }
268
+ } else if (element_kind == 0x0A || element_kind == 0x0B) {
269
+ if (detail) {
270
+ const auto xy = parse_xy(payload, payload_size);
271
+ if (!xy.empty()) {
272
+ pending_reference.ox = xy[0].first;
273
+ pending_reference.oy = xy[0].second;
274
+ pending_reference.cx = xy.size() > 1 ? xy[1].first : xy[0].first;
275
+ pending_reference.cy = xy.size() > 1 ? xy[1].second : xy[0].second;
276
+ pending_reference.rx = xy.size() > 2 ? xy[2].first : xy[0].first;
277
+ pending_reference.ry = xy.size() > 2 ? xy[2].second : xy[0].second;
278
+ pending_reference.has_xy = true;
279
+ }
280
+ }
281
+ }
282
+ break;
283
+ }
284
+ case 0x11: // ENDEL
285
+ if (element_kind == 0x0A || element_kind == 0x0B) {
286
+ if (pending_sname.empty()) {
287
+ throw std::runtime_error("reference missing SNAME");
288
+ }
289
+ pending_reference.child = pending_sname;
290
+ pending_reference.raw_hash = fnv1a_update(1469598103934665603ull, data.data() + element_start, offset - element_start);
291
+ current_cell.references.push_back(pending_reference);
292
+ }
293
+ element_kind = -1;
294
+ pending_sname.clear();
295
+ break;
296
+ case 0x07: // ENDSTR
297
+ if (current_name.empty()) {
298
+ throw std::runtime_error("cell missing STRNAME");
299
+ }
300
+ cells[current_name] = current_cell;
301
+ current_name.clear();
302
+ current_cell = Cell{};
303
+ break;
304
+ default:
305
+ break;
306
+ }
307
+ }
308
+ if (offset != data.size()) {
309
+ throw std::runtime_error("trailing partial GDS record");
310
+ }
311
+ return cells;
312
+ }
313
+
314
+ std::string select_top(const std::map<std::string, Cell>& cells, const char* requested_top) {
315
+ if (requested_top && requested_top[0]) {
316
+ if (!cells.count(requested_top)) {
317
+ throw std::runtime_error("requested top cell not found");
318
+ }
319
+ return requested_top;
320
+ }
321
+ std::set<std::string> referenced;
322
+ for (const auto& item : cells) {
323
+ for (const auto& reference : item.second.references) {
324
+ referenced.insert(reference.child);
325
+ }
326
+ }
327
+ std::vector<std::string> tops;
328
+ for (const auto& item : cells) {
329
+ if (!referenced.count(item.first)) {
330
+ tops.push_back(item.first);
331
+ }
332
+ }
333
+ if (tops.size() != 1) {
334
+ throw std::runtime_error("expected exactly one top cell");
335
+ }
336
+ return tops.front();
337
+ }
338
+
339
+ std::int64_t div_floor(std::int64_t numerator, std::uint64_t denominator) {
340
+ if (denominator == 0) {
341
+ throw std::runtime_error("zero denominator");
342
+ }
343
+ if (numerator >= 0) {
344
+ return numerator / static_cast<std::int64_t>(denominator);
345
+ }
346
+ return -static_cast<std::int64_t>((static_cast<std::uint64_t>(-numerator) + denominator - 1) / denominator);
347
+ }
348
+
349
+ FlattenedSummary flatten_cell(
350
+ const std::string& name,
351
+ const std::map<std::string, Cell>& cells,
352
+ std::map<std::string, FlattenedSummary>& memo,
353
+ std::set<std::string>& visiting) {
354
+ if (memo.count(name)) {
355
+ return memo.at(name);
356
+ }
357
+ if (visiting.count(name)) {
358
+ throw std::runtime_error("cell reference cycle detected");
359
+ }
360
+ const auto found = cells.find(name);
361
+ if (found == cells.end()) {
362
+ throw std::runtime_error("unresolved reference");
363
+ }
364
+ visiting.insert(name);
365
+ FlattenedSummary summary;
366
+ for (const auto& item : found->second.layers) {
367
+ add_layer_summary(summary.layers, item.first, item.second.count, item.second.bbox);
368
+ summary.shape_count += item.second.count;
369
+ }
370
+ if (found->second.layers.empty()) {
371
+ summary.shape_count = found->second.local_shape_count;
372
+ }
373
+ summary.geometry_hash = fnv1a_u64(summary.geometry_hash, found->second.local_shape_count);
374
+ summary.geometry_hash = fnv1a_u64(summary.geometry_hash, found->second.local_geometry_hash);
375
+ for (const auto& reference : found->second.references) {
376
+ const auto child = flatten_cell(reference.child, cells, memo, visiting);
377
+ const std::uint64_t columns = std::max<std::uint64_t>(1, reference.columns);
378
+ const std::uint64_t rows = std::max<std::uint64_t>(1, reference.rows);
379
+ const std::int64_t col_dx = div_floor(reference.cx - reference.ox, columns);
380
+ const std::int64_t col_dy = div_floor(reference.cy - reference.oy, columns);
381
+ const std::int64_t row_dx = div_floor(reference.rx - reference.ox, rows);
382
+ const std::int64_t row_dy = div_floor(reference.ry - reference.oy, rows);
383
+ for (std::uint64_t column = 0; column < columns; ++column) {
384
+ for (std::uint64_t row = 0; row < rows; ++row) {
385
+ const std::int64_t dx = reference.ox + static_cast<std::int64_t>(column) * col_dx + static_cast<std::int64_t>(row) * row_dx;
386
+ const std::int64_t dy = reference.oy + static_cast<std::int64_t>(column) * col_dy + static_cast<std::int64_t>(row) * row_dy;
387
+ for (const auto& item : child.layers) {
388
+ add_layer_summary(summary.layers, item.first, item.second.count, shifted_bbox(item.second.bbox, dx, dy));
389
+ }
390
+ if (summary.shape_count > std::numeric_limits<std::uint64_t>::max() - child.shape_count) {
391
+ throw std::overflow_error("flattened shape count overflow");
392
+ }
393
+ summary.shape_count += child.shape_count;
394
+ }
395
+ }
396
+ summary.geometry_hash = fnv1a_update(summary.geometry_hash, reinterpret_cast<const unsigned char*>(reference.child.data()), reference.child.size());
397
+ summary.geometry_hash = fnv1a_u64(summary.geometry_hash, reference.raw_hash);
398
+ summary.geometry_hash = fnv1a_u64(summary.geometry_hash, child.geometry_hash);
399
+ }
400
+ visiting.erase(name);
401
+ memo[name] = summary;
402
+ return summary;
403
+ }
404
+
405
+ } // namespace
406
+
407
+ extern "C" {
408
+
409
+ struct GdsGeometryLayerResult {
410
+ std::int32_t layer;
411
+ std::int32_t datatype;
412
+ std::uint64_t count;
413
+ std::int64_t x0;
414
+ std::int64_t y0;
415
+ std::int64_t x1;
416
+ std::int64_t y1;
417
+ };
418
+
419
+ struct GdsGeometrySummaryResult {
420
+ std::uint64_t flattened_shape_count;
421
+ std::uint64_t layer_count;
422
+ std::uint64_t geometry_hash;
423
+ GdsGeometryLayerResult* layers;
424
+ };
425
+
426
+ struct GdsGeometryCompareResult {
427
+ std::uint64_t old_flattened_shape_count;
428
+ std::uint64_t new_flattened_shape_count;
429
+ std::uint64_t old_layer_count;
430
+ std::uint64_t new_layer_count;
431
+ std::uint64_t old_geometry_hash;
432
+ std::uint64_t new_geometry_hash;
433
+ std::uint8_t summaries_match;
434
+ std::uint8_t detail_computed;
435
+ GdsGeometryLayerResult* old_layers;
436
+ GdsGeometryLayerResult* new_layers;
437
+ };
438
+
439
+ const char* gds_geometry_last_error() {
440
+ return g_last_error.c_str();
441
+ }
442
+
443
+ void gds_geometry_free_summary(GdsGeometrySummaryResult* result) {
444
+ if (!result) {
445
+ return;
446
+ }
447
+ std::free(result->layers);
448
+ result->layers = nullptr;
449
+ result->layer_count = 0;
450
+ result->flattened_shape_count = 0;
451
+ result->geometry_hash = 0;
452
+ }
453
+
454
+ void gds_geometry_free_compare(GdsGeometryCompareResult* result) {
455
+ if (!result) {
456
+ return;
457
+ }
458
+ std::free(result->old_layers);
459
+ std::free(result->new_layers);
460
+ result->old_layers = nullptr;
461
+ result->new_layers = nullptr;
462
+ result->old_layer_count = 0;
463
+ result->new_layer_count = 0;
464
+ }
465
+
466
+ void fill_layer_array(const std::map<LayerKey, LayerSummary>& layers, GdsGeometryLayerResult*& out_layers, std::uint64_t& out_count) {
467
+ out_count = layers.size();
468
+ out_layers = nullptr;
469
+ if (layers.empty()) {
470
+ return;
471
+ }
472
+ out_layers = static_cast<GdsGeometryLayerResult*>(std::malloc(sizeof(GdsGeometryLayerResult) * layers.size()));
473
+ if (!out_layers) {
474
+ throw std::bad_alloc();
475
+ }
476
+ std::size_t index = 0;
477
+ for (const auto& item : layers) {
478
+ out_layers[index++] = GdsGeometryLayerResult{
479
+ item.first.layer,
480
+ item.first.datatype,
481
+ item.second.count,
482
+ item.second.bbox.x0,
483
+ item.second.bbox.y0,
484
+ item.second.bbox.x1,
485
+ item.second.bbox.y1,
486
+ };
487
+ }
488
+ }
489
+
490
+ int gds_geometry_summary_impl(const char* path, const char* top_name, GdsGeometrySummaryResult* out, bool detail) {
491
+ try {
492
+ if (!path || !out) {
493
+ throw std::runtime_error("invalid arguments");
494
+ }
495
+ *out = GdsGeometrySummaryResult{};
496
+ const auto cells = parse_gds(path, detail);
497
+ if (cells.empty()) {
498
+ throw std::runtime_error("no cells found");
499
+ }
500
+ const auto top = select_top(cells, top_name);
501
+ std::map<std::string, FlattenedSummary> memo;
502
+ std::set<std::string> visiting;
503
+ const auto summary = flatten_cell(top, cells, memo, visiting);
504
+ out->flattened_shape_count = summary.shape_count;
505
+ out->layer_count = detail ? summary.layers.size() : 0;
506
+ out->geometry_hash = detail ? summary.geometry_hash : 0;
507
+ if (detail) {
508
+ fill_layer_array(summary.layers, out->layers, out->layer_count);
509
+ }
510
+ g_last_error.clear();
511
+ return 0;
512
+ } catch (const std::exception& exc) {
513
+ g_last_error = exc.what();
514
+ if (out) {
515
+ gds_geometry_free_summary(out);
516
+ }
517
+ return 1;
518
+ } catch (...) {
519
+ g_last_error = "unknown native geometry summary error";
520
+ if (out) {
521
+ gds_geometry_free_summary(out);
522
+ }
523
+ return 1;
524
+ }
525
+ }
526
+
527
+ int gds_geometry_summary(const char* path, const char* top_name, GdsGeometrySummaryResult* out) {
528
+ return gds_geometry_summary_impl(path, top_name, out, true);
529
+ }
530
+
531
+ int gds_geometry_summary_counts_only(const char* path, const char* top_name, GdsGeometrySummaryResult* out) {
532
+ return gds_geometry_summary_impl(path, top_name, out, false);
533
+ }
534
+
535
+ int gds_geometry_compare(
536
+ const char* old_path,
537
+ const char* new_path,
538
+ const char* old_top_name,
539
+ const char* new_top_name,
540
+ GdsGeometryCompareResult* out) {
541
+ try {
542
+ if (!old_path || !new_path || !out) {
543
+ throw std::runtime_error("invalid arguments");
544
+ }
545
+ *out = GdsGeometryCompareResult{};
546
+
547
+ const auto old_count_cells = parse_gds(old_path, false);
548
+ const auto new_count_cells = parse_gds(new_path, false);
549
+ const auto old_top = select_top(old_count_cells, old_top_name);
550
+ const auto new_top = select_top(new_count_cells, new_top_name);
551
+ std::map<std::string, FlattenedSummary> old_count_memo;
552
+ std::map<std::string, FlattenedSummary> new_count_memo;
553
+ std::set<std::string> old_count_visiting;
554
+ std::set<std::string> new_count_visiting;
555
+ const auto old_count_summary = flatten_cell(old_top, old_count_cells, old_count_memo, old_count_visiting);
556
+ const auto new_count_summary = flatten_cell(new_top, new_count_cells, new_count_memo, new_count_visiting);
557
+ out->old_flattened_shape_count = old_count_summary.shape_count;
558
+ out->new_flattened_shape_count = new_count_summary.shape_count;
559
+ if (old_count_summary.shape_count != new_count_summary.shape_count) {
560
+ out->summaries_match = 0;
561
+ out->detail_computed = 0;
562
+ g_last_error.clear();
563
+ return 0;
564
+ }
565
+
566
+ const auto old_detail_cells = parse_gds(old_path, true);
567
+ const auto new_detail_cells = parse_gds(new_path, true);
568
+ const auto old_detail_top = select_top(old_detail_cells, old_top_name);
569
+ const auto new_detail_top = select_top(new_detail_cells, new_top_name);
570
+ std::map<std::string, FlattenedSummary> old_detail_memo;
571
+ std::map<std::string, FlattenedSummary> new_detail_memo;
572
+ std::set<std::string> old_detail_visiting;
573
+ std::set<std::string> new_detail_visiting;
574
+ const auto old_detail = flatten_cell(old_detail_top, old_detail_cells, old_detail_memo, old_detail_visiting);
575
+ const auto new_detail = flatten_cell(new_detail_top, new_detail_cells, new_detail_memo, new_detail_visiting);
576
+ out->old_flattened_shape_count = old_detail.shape_count;
577
+ out->new_flattened_shape_count = new_detail.shape_count;
578
+ out->old_geometry_hash = old_detail.geometry_hash;
579
+ out->new_geometry_hash = new_detail.geometry_hash;
580
+ out->detail_computed = 1;
581
+ out->summaries_match = (old_detail.shape_count == new_detail.shape_count && old_detail.geometry_hash == new_detail.geometry_hash) ? 1 : 0;
582
+ fill_layer_array(old_detail.layers, out->old_layers, out->old_layer_count);
583
+ fill_layer_array(new_detail.layers, out->new_layers, out->new_layer_count);
584
+ g_last_error.clear();
585
+ return 0;
586
+ } catch (const std::exception& exc) {
587
+ g_last_error = exc.what();
588
+ if (out) {
589
+ gds_geometry_free_compare(out);
590
+ }
591
+ return 1;
592
+ } catch (...) {
593
+ g_last_error = "unknown native geometry compare error";
594
+ if (out) {
595
+ gds_geometry_free_compare(out);
596
+ }
597
+ return 1;
598
+ }
599
+ }
600
+
601
+ } // extern "C"