gdsdiff 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gdsdiff/__init__.py +148 -0
- gdsdiff/__main__.py +7 -0
- gdsdiff/_environment.py +12 -0
- gdsdiff/_version.py +34 -0
- gdsdiff/acceptance_evidence.py +530 -0
- gdsdiff/api.py +2955 -0
- gdsdiff/backends/__init__.py +26 -0
- gdsdiff/backends/base.py +77 -0
- gdsdiff/backends/boundary_loop_ctypes.py +191 -0
- gdsdiff/backends/cpu.py +20 -0
- gdsdiff/backends/cpu_ctypes.py +255 -0
- gdsdiff/backends/cuda.py +40 -0
- gdsdiff/backends/cuda_ctypes.py +1297 -0
- gdsdiff/backends/exact.py +424 -0
- gdsdiff/backends/geometry_ctypes.py +252 -0
- gdsdiff/backends/identity.py +53 -0
- gdsdiff/backends/metal.py +198 -0
- gdsdiff/backends/metal_ctypes.py +866 -0
- gdsdiff/backends/polygon_extract_ctypes.py +233 -0
- gdsdiff/backends/structural_ctypes.py +130 -0
- gdsdiff/boundary_loops.py +616 -0
- gdsdiff/cache.py +544 -0
- gdsdiff/canonicalize.py +176 -0
- gdsdiff/cli.py +352 -0
- gdsdiff/config.py +257 -0
- gdsdiff/cuda_setup.py +174 -0
- gdsdiff/design_history.py +65 -0
- gdsdiff/diagnostics.py +42 -0
- gdsdiff/diff_gds.py +66 -0
- gdsdiff/exact_diff_markdown.py +979 -0
- gdsdiff/exact_oracle.py +649 -0
- gdsdiff/extract.py +376 -0
- gdsdiff/gds_metadata.py +43 -0
- gdsdiff/geometry.py +112 -0
- gdsdiff/grid.py +143 -0
- gdsdiff/hierarchy.py +215 -0
- gdsdiff/hierarchy_extract.py +263 -0
- gdsdiff/inventory.py +153 -0
- gdsdiff/multiprocessing_support.py +39 -0
- gdsdiff/native.py +135 -0
- gdsdiff/native_cache.py +265 -0
- gdsdiff/native_src/cpu_prefilter.cpp +349 -0
- gdsdiff/native_src/gds_boundary_loops.cpp +505 -0
- gdsdiff/native_src/gds_geometry_scan.cpp +601 -0
- gdsdiff/native_src/gds_polygon_extract.cpp +594 -0
- gdsdiff/native_src/gds_structural_scan.cpp +335 -0
- gdsdiff/native_src/gdsdiff/CMakeLists.txt +74 -0
- gdsdiff/native_src/gdsdiff/core.cpp +191 -0
- gdsdiff/native_src/gdsdiff/core.hpp +96 -0
- gdsdiff/native_src/gdsdiff/cuda_assignment.cu +304 -0
- gdsdiff/native_src/gdsdiff/cuda_assignment.cuh +33 -0
- gdsdiff/native_src/gdsdiff/cuda_candidates.cu +133 -0
- gdsdiff/native_src/gdsdiff/cuda_candidates.cuh +17 -0
- gdsdiff/native_src/gdsdiff/cuda_ctypes.cu +4528 -0
- gdsdiff/native_src/gdsdiff/cuda_memory.cu +194 -0
- gdsdiff/native_src/gdsdiff/cuda_memory.cuh +34 -0
- gdsdiff/native_src/gdsdiff/metal_ctypes.mm +2791 -0
- gdsdiff/native_src/gdsdiff/python_bindings.cpp +21 -0
- gdsdiff/native_src/gdsdiff/test_core.cpp +93 -0
- gdsdiff/native_src/gdsdiff/test_cuda_assignment.cu +109 -0
- gdsdiff/native_src/gdsdiff/test_cuda_candidates.cu +94 -0
- gdsdiff/native_src/gdsdiff/test_cuda_memory.cu +97 -0
- gdsdiff/policy.py +305 -0
- gdsdiff/polygon_components.py +860 -0
- gdsdiff/profiles.py +152 -0
- gdsdiff/py.typed +1 -0
- gdsdiff/rect_coverage.py +384 -0
- gdsdiff/report.py +354 -0
- gdsdiff/schemas/gdsdiff_report-v1.schema.json +92 -0
- gdsdiff/semantics.py +75 -0
- gdsdiff/source_edge_diff.py +1120 -0
- gdsdiff/spatial.py +71 -0
- gdsdiff/structural_guard.py +161 -0
- gdsdiff/surplus_coverage.py +255 -0
- gdsdiff/synthetic_suite.py +1643 -0
- gdsdiff/templates.py +709 -0
- gdsdiff/tiling.py +153 -0
- gdsdiff/windowed_exact.py +270 -0
- gdsdiff/windows.py +184 -0
- gdsdiff-0.1.0.dist-info/METADATA +135 -0
- gdsdiff-0.1.0.dist-info/RECORD +85 -0
- gdsdiff-0.1.0.dist-info/WHEEL +5 -0
- gdsdiff-0.1.0.dist-info/entry_points.txt +4 -0
- gdsdiff-0.1.0.dist-info/licenses/LICENSE +674 -0
- gdsdiff-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
#include <algorithm>
|
|
2
|
+
#include <cstdint>
|
|
3
|
+
#include <cstdio>
|
|
4
|
+
#include <cstdlib>
|
|
5
|
+
#include <cstring>
|
|
6
|
+
#include <fstream>
|
|
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 Reference {
|
|
18
|
+
std::string child;
|
|
19
|
+
std::uint64_t multiplier = 1;
|
|
20
|
+
std::uint64_t raw_hash = 0;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
struct Cell {
|
|
24
|
+
std::uint64_t local_shape_count = 0;
|
|
25
|
+
std::uint64_t local_shape_hash = 1469598103934665603ull;
|
|
26
|
+
std::vector<Reference> references;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
struct ParseResult {
|
|
30
|
+
std::map<std::string, Cell> cells;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
std::uint16_t read_u16(const unsigned char* p) {
|
|
34
|
+
return static_cast<std::uint16_t>((static_cast<std::uint16_t>(p[0]) << 8) | p[1]);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
std::int16_t read_i16(const unsigned char* p) {
|
|
38
|
+
return static_cast<std::int16_t>(read_u16(p));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
std::string decode_string(const unsigned char* data, std::size_t size) {
|
|
42
|
+
while (size && data[size - 1] == 0) {
|
|
43
|
+
--size;
|
|
44
|
+
}
|
|
45
|
+
return std::string(reinterpret_cast<const char*>(data), size);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
std::uint64_t fnv1a_update(std::uint64_t hash, const unsigned char* data, std::size_t size) {
|
|
49
|
+
constexpr std::uint64_t prime = 1099511628211ull;
|
|
50
|
+
for (std::size_t i = 0; i < size; ++i) {
|
|
51
|
+
hash ^= static_cast<std::uint64_t>(data[i]);
|
|
52
|
+
hash *= prime;
|
|
53
|
+
}
|
|
54
|
+
return hash;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
std::uint64_t fnv1a_u64(std::uint64_t hash, std::uint64_t value) {
|
|
58
|
+
unsigned char bytes[8];
|
|
59
|
+
for (int i = 0; i < 8; ++i) {
|
|
60
|
+
bytes[i] = static_cast<unsigned char>((value >> (56 - i * 8)) & 0xff);
|
|
61
|
+
}
|
|
62
|
+
return fnv1a_update(hash, bytes, sizeof(bytes));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
std::vector<unsigned char> read_file(const char* path) {
|
|
66
|
+
std::ifstream in(path, std::ios::binary);
|
|
67
|
+
if (!in) {
|
|
68
|
+
throw std::runtime_error("unable to open GDS file");
|
|
69
|
+
}
|
|
70
|
+
in.seekg(0, std::ios::end);
|
|
71
|
+
const auto size = in.tellg();
|
|
72
|
+
if (size < 0) {
|
|
73
|
+
throw std::runtime_error("unable to determine GDS file size");
|
|
74
|
+
}
|
|
75
|
+
in.seekg(0, std::ios::beg);
|
|
76
|
+
std::vector<unsigned char> data(static_cast<std::size_t>(size));
|
|
77
|
+
if (!data.empty()) {
|
|
78
|
+
in.read(reinterpret_cast<char*>(data.data()), static_cast<std::streamsize>(data.size()));
|
|
79
|
+
if (!in) {
|
|
80
|
+
throw std::runtime_error("unable to read GDS file");
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return data;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
ParseResult parse_gds(const char* path, bool compute_hash) {
|
|
87
|
+
const auto data = read_file(path);
|
|
88
|
+
ParseResult result;
|
|
89
|
+
std::size_t offset = 0;
|
|
90
|
+
std::string current_name;
|
|
91
|
+
std::uint64_t local_shape_count = 0;
|
|
92
|
+
std::uint64_t local_shape_hash = 1469598103934665603ull;
|
|
93
|
+
std::vector<Reference> references;
|
|
94
|
+
int element_kind = -1;
|
|
95
|
+
std::size_t element_start = 0;
|
|
96
|
+
std::string pending_sname;
|
|
97
|
+
std::uint64_t pending_multiplier = 1;
|
|
98
|
+
|
|
99
|
+
while (offset + 4 <= data.size()) {
|
|
100
|
+
const std::uint16_t record_len = read_u16(data.data() + offset);
|
|
101
|
+
const int record_type = data[offset + 2];
|
|
102
|
+
if (record_len < 4 || offset + record_len > data.size()) {
|
|
103
|
+
throw std::runtime_error("invalid GDS record length");
|
|
104
|
+
}
|
|
105
|
+
const unsigned char* payload = data.data() + offset + 4;
|
|
106
|
+
const std::size_t payload_size = record_len - 4;
|
|
107
|
+
offset += record_len;
|
|
108
|
+
|
|
109
|
+
switch (record_type) {
|
|
110
|
+
case 0x05: // BGNSTR
|
|
111
|
+
current_name.clear();
|
|
112
|
+
local_shape_count = 0;
|
|
113
|
+
local_shape_hash = 1469598103934665603ull;
|
|
114
|
+
references.clear();
|
|
115
|
+
break;
|
|
116
|
+
case 0x06: // STRNAME
|
|
117
|
+
current_name = decode_string(payload, payload_size);
|
|
118
|
+
break;
|
|
119
|
+
case 0x08: // BOUNDARY
|
|
120
|
+
case 0x09: // PATH
|
|
121
|
+
element_kind = record_type;
|
|
122
|
+
element_start = offset - record_len;
|
|
123
|
+
break;
|
|
124
|
+
case 0x0A: // SREF
|
|
125
|
+
case 0x0B: // AREF
|
|
126
|
+
element_kind = record_type;
|
|
127
|
+
element_start = offset - record_len;
|
|
128
|
+
pending_sname.clear();
|
|
129
|
+
pending_multiplier = 1;
|
|
130
|
+
break;
|
|
131
|
+
case 0x12: // SNAME
|
|
132
|
+
if (element_kind == 0x0A || element_kind == 0x0B) {
|
|
133
|
+
pending_sname = decode_string(payload, payload_size);
|
|
134
|
+
}
|
|
135
|
+
break;
|
|
136
|
+
case 0x13: // COLROW
|
|
137
|
+
if (element_kind == 0x0B) {
|
|
138
|
+
if (payload_size < 4) {
|
|
139
|
+
throw std::runtime_error("invalid COLROW record");
|
|
140
|
+
}
|
|
141
|
+
const auto columns = read_i16(payload);
|
|
142
|
+
const auto rows = read_i16(payload + 2);
|
|
143
|
+
if (columns <= 0 || rows <= 0) {
|
|
144
|
+
throw std::runtime_error("invalid AREF repetition");
|
|
145
|
+
}
|
|
146
|
+
pending_multiplier = static_cast<std::uint64_t>(columns) * static_cast<std::uint64_t>(rows);
|
|
147
|
+
}
|
|
148
|
+
break;
|
|
149
|
+
case 0x11: // ENDEL
|
|
150
|
+
if (element_kind == 0x08 || element_kind == 0x09) {
|
|
151
|
+
++local_shape_count;
|
|
152
|
+
if (compute_hash) {
|
|
153
|
+
local_shape_hash = fnv1a_u64(local_shape_hash, static_cast<std::uint64_t>(record_type));
|
|
154
|
+
local_shape_hash = fnv1a_update(local_shape_hash, data.data() + element_start, offset - element_start);
|
|
155
|
+
}
|
|
156
|
+
} else if (element_kind == 0x0A || element_kind == 0x0B) {
|
|
157
|
+
if (pending_sname.empty()) {
|
|
158
|
+
throw std::runtime_error("reference missing SNAME");
|
|
159
|
+
}
|
|
160
|
+
const std::uint64_t raw_hash = compute_hash
|
|
161
|
+
? fnv1a_update(1469598103934665603ull, data.data() + element_start, offset - element_start)
|
|
162
|
+
: 0;
|
|
163
|
+
references.push_back(Reference{pending_sname, pending_multiplier, raw_hash});
|
|
164
|
+
}
|
|
165
|
+
element_kind = -1;
|
|
166
|
+
pending_sname.clear();
|
|
167
|
+
pending_multiplier = 1;
|
|
168
|
+
break;
|
|
169
|
+
case 0x07: // ENDSTR
|
|
170
|
+
if (current_name.empty()) {
|
|
171
|
+
throw std::runtime_error("cell missing STRNAME");
|
|
172
|
+
}
|
|
173
|
+
result.cells[current_name] = Cell{local_shape_count, local_shape_hash, references};
|
|
174
|
+
current_name.clear();
|
|
175
|
+
local_shape_count = 0;
|
|
176
|
+
local_shape_hash = 1469598103934665603ull;
|
|
177
|
+
references.clear();
|
|
178
|
+
break;
|
|
179
|
+
default:
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (offset != data.size()) {
|
|
184
|
+
throw std::runtime_error("trailing partial GDS record");
|
|
185
|
+
}
|
|
186
|
+
return result;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
std::string select_top(const std::map<std::string, Cell>& cells, const char* requested_top) {
|
|
190
|
+
if (requested_top && requested_top[0]) {
|
|
191
|
+
if (!cells.count(requested_top)) {
|
|
192
|
+
throw std::runtime_error("requested top cell not found");
|
|
193
|
+
}
|
|
194
|
+
return requested_top;
|
|
195
|
+
}
|
|
196
|
+
std::set<std::string> referenced;
|
|
197
|
+
for (const auto& item : cells) {
|
|
198
|
+
for (const auto& reference : item.second.references) {
|
|
199
|
+
referenced.insert(reference.child);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
std::vector<std::string> tops;
|
|
203
|
+
for (const auto& item : cells) {
|
|
204
|
+
if (!referenced.count(item.first)) {
|
|
205
|
+
tops.push_back(item.first);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (tops.size() != 1) {
|
|
209
|
+
throw std::runtime_error("expected exactly one top cell");
|
|
210
|
+
}
|
|
211
|
+
return tops.front();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
std::uint64_t flatten_count(
|
|
215
|
+
const std::string& name,
|
|
216
|
+
const std::map<std::string, Cell>& cells,
|
|
217
|
+
std::map<std::string, std::uint64_t>& memo,
|
|
218
|
+
std::set<std::string>& visiting,
|
|
219
|
+
std::set<std::string>& seen) {
|
|
220
|
+
if (memo.count(name)) {
|
|
221
|
+
seen.insert(name);
|
|
222
|
+
return memo.at(name);
|
|
223
|
+
}
|
|
224
|
+
if (visiting.count(name)) {
|
|
225
|
+
throw std::runtime_error("cell reference cycle detected");
|
|
226
|
+
}
|
|
227
|
+
const auto found = cells.find(name);
|
|
228
|
+
if (found == cells.end()) {
|
|
229
|
+
throw std::runtime_error("unresolved reference");
|
|
230
|
+
}
|
|
231
|
+
visiting.insert(name);
|
|
232
|
+
seen.insert(name);
|
|
233
|
+
std::uint64_t total = found->second.local_shape_count;
|
|
234
|
+
for (const auto& reference : found->second.references) {
|
|
235
|
+
const std::uint64_t child_count = flatten_count(reference.child, cells, memo, visiting, seen);
|
|
236
|
+
if (reference.multiplier != 0 && child_count > UINT64_MAX / reference.multiplier) {
|
|
237
|
+
throw std::overflow_error("flattened shape count overflow");
|
|
238
|
+
}
|
|
239
|
+
const std::uint64_t add = child_count * reference.multiplier;
|
|
240
|
+
if (total > UINT64_MAX - add) {
|
|
241
|
+
throw std::overflow_error("flattened shape count overflow");
|
|
242
|
+
}
|
|
243
|
+
total += add;
|
|
244
|
+
}
|
|
245
|
+
visiting.erase(name);
|
|
246
|
+
memo[name] = total;
|
|
247
|
+
return total;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
std::uint64_t structural_hash(
|
|
251
|
+
const std::string& name,
|
|
252
|
+
const std::map<std::string, Cell>& cells,
|
|
253
|
+
std::map<std::string, std::uint64_t>& memo,
|
|
254
|
+
std::set<std::string>& visiting) {
|
|
255
|
+
if (memo.count(name)) {
|
|
256
|
+
return memo.at(name);
|
|
257
|
+
}
|
|
258
|
+
if (visiting.count(name)) {
|
|
259
|
+
throw std::runtime_error("cell reference cycle detected");
|
|
260
|
+
}
|
|
261
|
+
const auto found = cells.find(name);
|
|
262
|
+
if (found == cells.end()) {
|
|
263
|
+
throw std::runtime_error("unresolved reference");
|
|
264
|
+
}
|
|
265
|
+
visiting.insert(name);
|
|
266
|
+
std::uint64_t hash = 1469598103934665603ull;
|
|
267
|
+
hash = fnv1a_u64(hash, found->second.local_shape_count);
|
|
268
|
+
hash = fnv1a_u64(hash, found->second.local_shape_hash);
|
|
269
|
+
hash = fnv1a_u64(hash, found->second.references.size());
|
|
270
|
+
for (const auto& reference : found->second.references) {
|
|
271
|
+
hash = fnv1a_u64(hash, reference.multiplier);
|
|
272
|
+
hash = fnv1a_u64(hash, reference.raw_hash);
|
|
273
|
+
hash = fnv1a_update(hash, reinterpret_cast<const unsigned char*>(reference.child.data()), reference.child.size());
|
|
274
|
+
hash = fnv1a_u64(hash, structural_hash(reference.child, cells, memo, visiting));
|
|
275
|
+
}
|
|
276
|
+
visiting.erase(name);
|
|
277
|
+
memo[name] = hash;
|
|
278
|
+
return hash;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
} // namespace
|
|
282
|
+
|
|
283
|
+
extern "C" {
|
|
284
|
+
|
|
285
|
+
struct GdsStructuralResult {
|
|
286
|
+
std::uint64_t flattened_shape_count;
|
|
287
|
+
std::uint64_t cell_count;
|
|
288
|
+
std::uint64_t reachable_cell_count;
|
|
289
|
+
std::uint64_t structural_hash;
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
const char* gds_structural_last_error() {
|
|
293
|
+
return g_last_error.c_str();
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
int gds_structural_summary_impl(const char* path, const char* top_name, GdsStructuralResult* out, bool compute_hash) {
|
|
297
|
+
try {
|
|
298
|
+
if (!path || !out) {
|
|
299
|
+
throw std::runtime_error("invalid arguments");
|
|
300
|
+
}
|
|
301
|
+
const auto parsed = parse_gds(path, compute_hash);
|
|
302
|
+
if (parsed.cells.empty()) {
|
|
303
|
+
throw std::runtime_error("no cells found");
|
|
304
|
+
}
|
|
305
|
+
const std::string top = select_top(parsed.cells, top_name);
|
|
306
|
+
std::map<std::string, std::uint64_t> memo;
|
|
307
|
+
std::set<std::string> visiting;
|
|
308
|
+
std::set<std::string> seen;
|
|
309
|
+
const std::uint64_t count = flatten_count(top, parsed.cells, memo, visiting, seen);
|
|
310
|
+
std::uint64_t hash = 0;
|
|
311
|
+
if (compute_hash) {
|
|
312
|
+
std::map<std::string, std::uint64_t> hash_memo;
|
|
313
|
+
std::set<std::string> hash_visiting;
|
|
314
|
+
hash = structural_hash(top, parsed.cells, hash_memo, hash_visiting);
|
|
315
|
+
}
|
|
316
|
+
out->flattened_shape_count = count;
|
|
317
|
+
out->cell_count = parsed.cells.size();
|
|
318
|
+
out->reachable_cell_count = seen.size();
|
|
319
|
+
out->structural_hash = hash;
|
|
320
|
+
return 0;
|
|
321
|
+
} catch (const std::exception& exc) {
|
|
322
|
+
g_last_error = exc.what();
|
|
323
|
+
return 1;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
int gds_structural_summary(const char* path, const char* top_name, GdsStructuralResult* out) {
|
|
328
|
+
return gds_structural_summary_impl(path, top_name, out, true);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
int gds_structural_summary_counts_only(const char* path, const char* top_name, GdsStructuralResult* out) {
|
|
332
|
+
return gds_structural_summary_impl(path, top_name, out, false);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
} // extern "C"
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
cmake_minimum_required(VERSION 3.24)
|
|
2
|
+
project(gdsdiff_native LANGUAGES CXX)
|
|
3
|
+
|
|
4
|
+
if(DEFINED GDS_GPU_COMPARE_BUILD_TESTS AND NOT DEFINED GDSDIFF_BUILD_TESTS)
|
|
5
|
+
set(GDSDIFF_BUILD_TESTS ${GDS_GPU_COMPARE_BUILD_TESTS})
|
|
6
|
+
endif()
|
|
7
|
+
if(DEFINED GDS_GPU_COMPARE_BUILD_PYTHON AND NOT DEFINED GDSDIFF_BUILD_PYTHON)
|
|
8
|
+
set(GDSDIFF_BUILD_PYTHON ${GDS_GPU_COMPARE_BUILD_PYTHON})
|
|
9
|
+
endif()
|
|
10
|
+
if(DEFINED GDS_GPU_COMPARE_ENABLE_CUDA AND NOT DEFINED GDSDIFF_ENABLE_CUDA)
|
|
11
|
+
set(GDSDIFF_ENABLE_CUDA ${GDS_GPU_COMPARE_ENABLE_CUDA})
|
|
12
|
+
endif()
|
|
13
|
+
if(DEFINED GDS_GPU_COMPARE_CUDA_ARCHITECTURES AND NOT DEFINED GDSDIFF_CUDA_ARCHITECTURES)
|
|
14
|
+
set(GDSDIFF_CUDA_ARCHITECTURES ${GDS_GPU_COMPARE_CUDA_ARCHITECTURES})
|
|
15
|
+
endif()
|
|
16
|
+
|
|
17
|
+
option(GDSDIFF_BUILD_TESTS "Build native tests" OFF)
|
|
18
|
+
option(GDSDIFF_BUILD_PYTHON "Build optional Python extensions" OFF)
|
|
19
|
+
option(GDSDIFF_ENABLE_CUDA "Build CUDA components" OFF)
|
|
20
|
+
|
|
21
|
+
add_library(gds_compare_core STATIC core.cpp)
|
|
22
|
+
target_compile_features(gds_compare_core PUBLIC cxx_std_20)
|
|
23
|
+
target_include_directories(gds_compare_core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
|
24
|
+
|
|
25
|
+
if(GDSDIFF_BUILD_TESTS)
|
|
26
|
+
add_executable(gds_compare_core_test test_core.cpp)
|
|
27
|
+
target_link_libraries(gds_compare_core_test PRIVATE gds_compare_core)
|
|
28
|
+
endif()
|
|
29
|
+
|
|
30
|
+
if(GDSDIFF_BUILD_PYTHON)
|
|
31
|
+
find_package(Python COMPONENTS Interpreter Development.Module REQUIRED)
|
|
32
|
+
find_package(pybind11 CONFIG REQUIRED)
|
|
33
|
+
pybind11_add_module(_gds_compare_core python_bindings.cpp)
|
|
34
|
+
target_link_libraries(_gds_compare_core PRIVATE gds_compare_core)
|
|
35
|
+
set_target_properties(_gds_compare_core PROPERTIES
|
|
36
|
+
CXX_VISIBILITY_PRESET hidden
|
|
37
|
+
VISIBILITY_INLINES_HIDDEN ON
|
|
38
|
+
)
|
|
39
|
+
endif()
|
|
40
|
+
|
|
41
|
+
if(GDSDIFF_ENABLE_CUDA)
|
|
42
|
+
enable_language(CUDA)
|
|
43
|
+
find_package(CUDAToolkit REQUIRED)
|
|
44
|
+
if(GDSDIFF_CUDA_ARCHITECTURES)
|
|
45
|
+
set(CMAKE_CUDA_ARCHITECTURES ${GDSDIFF_CUDA_ARCHITECTURES})
|
|
46
|
+
else()
|
|
47
|
+
set(CMAKE_CUDA_ARCHITECTURES native)
|
|
48
|
+
endif()
|
|
49
|
+
add_library(gds_compare_cuda_assignment STATIC cuda_assignment.cu)
|
|
50
|
+
target_link_libraries(gds_compare_cuda_assignment PRIVATE CUDA::cudart gds_compare_core)
|
|
51
|
+
target_include_directories(gds_compare_cuda_assignment PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
|
52
|
+
add_library(gds_compare_cuda_candidates STATIC cuda_candidates.cu)
|
|
53
|
+
target_link_libraries(gds_compare_cuda_candidates PRIVATE gds_compare_cuda_assignment CUDA::cudart gds_compare_core)
|
|
54
|
+
target_include_directories(gds_compare_cuda_candidates PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
|
55
|
+
add_library(gds_compare_cuda_memory STATIC cuda_memory.cu)
|
|
56
|
+
target_link_libraries(gds_compare_cuda_memory PRIVATE gds_compare_cuda_candidates CUDA::cudart gds_compare_core)
|
|
57
|
+
target_include_directories(gds_compare_cuda_memory PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
|
58
|
+
if(GDSDIFF_BUILD_TESTS)
|
|
59
|
+
add_executable(gds_compare_cuda_assignment_test test_cuda_assignment.cu)
|
|
60
|
+
target_link_libraries(gds_compare_cuda_assignment_test PRIVATE gds_compare_cuda_assignment CUDA::cudart)
|
|
61
|
+
add_executable(gds_compare_cuda_candidates_test test_cuda_candidates.cu)
|
|
62
|
+
target_link_libraries(gds_compare_cuda_candidates_test PRIVATE gds_compare_cuda_candidates CUDA::cudart)
|
|
63
|
+
add_executable(gds_compare_cuda_memory_test test_cuda_memory.cu)
|
|
64
|
+
target_link_libraries(gds_compare_cuda_memory_test PRIVATE gds_compare_cuda_memory CUDA::cudart)
|
|
65
|
+
endif()
|
|
66
|
+
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/cuda_bindings.cu)
|
|
67
|
+
find_package(Python COMPONENTS Interpreter Development.Module REQUIRED)
|
|
68
|
+
find_package(pybind11 CONFIG REQUIRED)
|
|
69
|
+
pybind11_add_module(_gds_compare_cuda cuda_bindings.cu)
|
|
70
|
+
target_link_libraries(_gds_compare_cuda PRIVATE CUDA::cudart gds_compare_core gds_compare_cuda_assignment gds_compare_cuda_candidates gds_compare_cuda_memory)
|
|
71
|
+
else()
|
|
72
|
+
message(STATUS "CUDA assignment kernels enabled; Python CUDA binding source is not present yet")
|
|
73
|
+
endif()
|
|
74
|
+
endif()
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
#include "core.hpp"
|
|
2
|
+
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
#include <array>
|
|
5
|
+
#include <limits>
|
|
6
|
+
#include <utility>
|
|
7
|
+
|
|
8
|
+
namespace gdsdiff {
|
|
9
|
+
namespace {
|
|
10
|
+
|
|
11
|
+
void append_u32_be(std::string& out, std::uint32_t value) {
|
|
12
|
+
for (int shift = 24; shift >= 0; shift -= 8) {
|
|
13
|
+
out.push_back(static_cast<char>((value >> shift) & 0xff));
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
void append_i64_be(std::string& out, std::int64_t value) {
|
|
18
|
+
const auto encoded = static_cast<std::uint64_t>(value);
|
|
19
|
+
for (int shift = 56; shift >= 0; shift -= 8) {
|
|
20
|
+
out.push_back(static_cast<char>((encoded >> shift) & 0xff));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
std::vector<Point> min_cyclic_rotation(const std::vector<Point>& points) {
|
|
25
|
+
if (points.empty()) {
|
|
26
|
+
return points;
|
|
27
|
+
}
|
|
28
|
+
std::vector<Point> best;
|
|
29
|
+
bool initialized = false;
|
|
30
|
+
for (std::size_t i = 0; i < points.size(); ++i) {
|
|
31
|
+
std::vector<Point> rotated;
|
|
32
|
+
rotated.reserve(points.size());
|
|
33
|
+
rotated.insert(rotated.end(), points.begin() + static_cast<std::ptrdiff_t>(i), points.end());
|
|
34
|
+
rotated.insert(rotated.end(), points.begin(), points.begin() + static_cast<std::ptrdiff_t>(i));
|
|
35
|
+
if (!initialized || rotated < best) {
|
|
36
|
+
best = std::move(rotated);
|
|
37
|
+
initialized = true;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return best;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
std::vector<Point> canonical_valid_points(const std::vector<Point>& points) {
|
|
44
|
+
auto forward = min_cyclic_rotation(points);
|
|
45
|
+
auto reversed = points;
|
|
46
|
+
std::reverse(reversed.begin(), reversed.end());
|
|
47
|
+
auto reverse = min_cyclic_rotation(reversed);
|
|
48
|
+
return std::min(forward, reverse);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
using PairKey = std::pair<TileKey, std::uint64_t>;
|
|
52
|
+
|
|
53
|
+
void add_side(
|
|
54
|
+
std::map<PairKey, std::uint8_t>& pair_side_masks,
|
|
55
|
+
std::map<std::tuple<LayerSpec, std::uint64_t, BBox>, std::uint8_t>& oversized_side_masks,
|
|
56
|
+
const PolygonRecord& polygon,
|
|
57
|
+
std::uint64_t canonical_id,
|
|
58
|
+
std::uint8_t side,
|
|
59
|
+
const TileConfig& config,
|
|
60
|
+
std::uint64_t& assignment_count) {
|
|
61
|
+
const BBox tile_bbox = bbox_tile_range(polygon.bbox, config.tile_size);
|
|
62
|
+
const std::int64_t span_x = tile_bbox.x1 - tile_bbox.x0 + 1;
|
|
63
|
+
const std::int64_t span_y = tile_bbox.y1 - tile_bbox.y0 + 1;
|
|
64
|
+
if (span_x <= 0 || span_y <= 0) {
|
|
65
|
+
throw std::overflow_error("invalid tile span");
|
|
66
|
+
}
|
|
67
|
+
const auto span_x_u = static_cast<std::uint64_t>(span_x);
|
|
68
|
+
const auto span_y_u = static_cast<std::uint64_t>(span_y);
|
|
69
|
+
if (span_x_u > std::numeric_limits<std::uint64_t>::max() / span_y_u) {
|
|
70
|
+
throw std::overflow_error("tile span overflow");
|
|
71
|
+
}
|
|
72
|
+
const std::uint64_t count = span_x_u * span_y_u;
|
|
73
|
+
if (count > config.max_tiles_per_polygon) {
|
|
74
|
+
auto key = std::make_tuple(polygon.layer, canonical_id, tile_bbox);
|
|
75
|
+
oversized_side_masks[key] = static_cast<std::uint8_t>(oversized_side_masks[key] | side);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
for (std::int64_t tx = tile_bbox.x0; tx <= tile_bbox.x1; ++tx) {
|
|
79
|
+
for (std::int64_t ty = tile_bbox.y0; ty <= tile_bbox.y1; ++ty) {
|
|
80
|
+
PairKey key{TileKey{polygon.layer, tx, ty}, canonical_id};
|
|
81
|
+
pair_side_masks[key] = static_cast<std::uint8_t>(pair_side_masks[key] | side);
|
|
82
|
+
++assignment_count;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
} // namespace
|
|
88
|
+
|
|
89
|
+
std::int64_t floor_div(std::int64_t value, std::int64_t divisor) {
|
|
90
|
+
if (divisor <= 0) {
|
|
91
|
+
throw std::invalid_argument("divisor must be positive");
|
|
92
|
+
}
|
|
93
|
+
std::int64_t quotient = value / divisor;
|
|
94
|
+
const std::int64_t remainder = value % divisor;
|
|
95
|
+
if (remainder != 0 && ((remainder < 0) != (divisor < 0))) {
|
|
96
|
+
--quotient;
|
|
97
|
+
}
|
|
98
|
+
return quotient;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
BBox bbox_tile_range(const BBox& bbox, std::int64_t tile_size) {
|
|
102
|
+
if (tile_size <= 0) {
|
|
103
|
+
throw std::invalid_argument("tile_size must be positive");
|
|
104
|
+
}
|
|
105
|
+
return BBox{
|
|
106
|
+
floor_div(bbox.x0, tile_size),
|
|
107
|
+
floor_div(bbox.y0, tile_size),
|
|
108
|
+
floor_div(bbox.x1, tile_size),
|
|
109
|
+
floor_div(bbox.y1, tile_size),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
std::string canonical_blob(const std::vector<Point>& points, bool valid) {
|
|
114
|
+
const std::vector<Point> encoded_points = valid ? canonical_valid_points(points) : points;
|
|
115
|
+
const std::uint32_t validity = valid ? kValidityValid : kValidityConservativeRaw;
|
|
116
|
+
if (encoded_points.size() > std::numeric_limits<std::uint32_t>::max()) {
|
|
117
|
+
throw std::overflow_error("polygon point count exceeds encoding limit");
|
|
118
|
+
}
|
|
119
|
+
std::string out;
|
|
120
|
+
out.reserve(12 + encoded_points.size() * 16);
|
|
121
|
+
append_u32_be(out, kEncodingVersion);
|
|
122
|
+
append_u32_be(out, validity);
|
|
123
|
+
append_u32_be(out, static_cast<std::uint32_t>(encoded_points.size()));
|
|
124
|
+
for (const Point& point : encoded_points) {
|
|
125
|
+
append_i64_be(out, point.x);
|
|
126
|
+
append_i64_be(out, point.y);
|
|
127
|
+
}
|
|
128
|
+
return out;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
std::vector<std::uint64_t> intern_canonical_blobs(const std::vector<std::string>& blobs) {
|
|
132
|
+
std::set<std::string> unique_set(blobs.begin(), blobs.end());
|
|
133
|
+
std::vector<std::string> unique(unique_set.begin(), unique_set.end());
|
|
134
|
+
std::map<std::string, std::uint64_t> id_by_blob;
|
|
135
|
+
for (std::size_t i = 0; i < unique.size(); ++i) {
|
|
136
|
+
id_by_blob.emplace(unique[i], static_cast<std::uint64_t>(i));
|
|
137
|
+
}
|
|
138
|
+
std::vector<std::uint64_t> ids;
|
|
139
|
+
ids.reserve(blobs.size());
|
|
140
|
+
for (const std::string& blob : blobs) {
|
|
141
|
+
ids.push_back(id_by_blob.at(blob));
|
|
142
|
+
}
|
|
143
|
+
return ids;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
TilePrefilterResult run_tile_prefilter(
|
|
147
|
+
const std::vector<PolygonRecord>& old_polygons,
|
|
148
|
+
const std::vector<std::uint64_t>& old_canonical_ids,
|
|
149
|
+
const std::vector<PolygonRecord>& new_polygons,
|
|
150
|
+
const std::vector<std::uint64_t>& new_canonical_ids,
|
|
151
|
+
const TileConfig& config) {
|
|
152
|
+
if (config.tile_size <= 0 || config.max_tiles_per_polygon == 0) {
|
|
153
|
+
throw std::invalid_argument("invalid tile config");
|
|
154
|
+
}
|
|
155
|
+
if (old_polygons.size() != old_canonical_ids.size() || new_polygons.size() != new_canonical_ids.size()) {
|
|
156
|
+
throw std::invalid_argument("canonical id vector length must match polygon count");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
std::map<PairKey, std::uint8_t> pair_side_masks;
|
|
160
|
+
std::map<std::tuple<LayerSpec, std::uint64_t, BBox>, std::uint8_t> oversized_side_masks;
|
|
161
|
+
TilePrefilterResult result;
|
|
162
|
+
|
|
163
|
+
for (std::size_t i = 0; i < old_polygons.size(); ++i) {
|
|
164
|
+
add_side(pair_side_masks, oversized_side_masks, old_polygons[i], old_canonical_ids[i], kOldSide, config, result.assignment_count);
|
|
165
|
+
}
|
|
166
|
+
for (std::size_t i = 0; i < new_polygons.size(); ++i) {
|
|
167
|
+
add_side(pair_side_masks, oversized_side_masks, new_polygons[i], new_canonical_ids[i], kNewSide, config, result.assignment_count);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
for (const auto& [key, side_mask] : pair_side_masks) {
|
|
171
|
+
if (side_mask != kBothSides) {
|
|
172
|
+
result.candidate_tiles.push_back(key.first);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
std::sort(result.candidate_tiles.begin(), result.candidate_tiles.end());
|
|
176
|
+
result.candidate_tiles.erase(std::unique(result.candidate_tiles.begin(), result.candidate_tiles.end()), result.candidate_tiles.end());
|
|
177
|
+
result.collapsed_pair_count = pair_side_masks.size();
|
|
178
|
+
|
|
179
|
+
for (const auto& [key, side_mask] : oversized_side_masks) {
|
|
180
|
+
if (side_mask == kBothSides) {
|
|
181
|
+
++result.oversized_matched_count;
|
|
182
|
+
} else {
|
|
183
|
+
result.oversized_candidates.push_back(
|
|
184
|
+
OversizedPolygon{std::get<0>(key), std::get<1>(key), side_mask, std::get<2>(key)});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
std::sort(result.oversized_candidates.begin(), result.oversized_candidates.end());
|
|
188
|
+
return result;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
} // namespace gdsdiff
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <cstdint>
|
|
4
|
+
#include <map>
|
|
5
|
+
#include <set>
|
|
6
|
+
#include <stdexcept>
|
|
7
|
+
#include <string>
|
|
8
|
+
#include <tuple>
|
|
9
|
+
#include <vector>
|
|
10
|
+
|
|
11
|
+
namespace gdsdiff {
|
|
12
|
+
|
|
13
|
+
constexpr std::uint32_t kEncodingVersion = 1;
|
|
14
|
+
constexpr std::uint32_t kValidityValid = 0;
|
|
15
|
+
constexpr std::uint32_t kValidityConservativeRaw = 1;
|
|
16
|
+
constexpr std::uint8_t kOldSide = 1;
|
|
17
|
+
constexpr std::uint8_t kNewSide = 2;
|
|
18
|
+
constexpr std::uint8_t kBothSides = kOldSide | kNewSide;
|
|
19
|
+
|
|
20
|
+
struct Point {
|
|
21
|
+
std::int64_t x = 0;
|
|
22
|
+
std::int64_t y = 0;
|
|
23
|
+
|
|
24
|
+
friend bool operator==(const Point&, const Point&) = default;
|
|
25
|
+
friend auto operator<=>(const Point&, const Point&) = default;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
struct LayerSpec {
|
|
29
|
+
std::int32_t layer = 0;
|
|
30
|
+
std::int32_t datatype = 0;
|
|
31
|
+
|
|
32
|
+
friend bool operator==(const LayerSpec&, const LayerSpec&) = default;
|
|
33
|
+
friend auto operator<=>(const LayerSpec&, const LayerSpec&) = default;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
struct BBox {
|
|
37
|
+
std::int64_t x0 = 0;
|
|
38
|
+
std::int64_t y0 = 0;
|
|
39
|
+
std::int64_t x1 = 0;
|
|
40
|
+
std::int64_t y1 = 0;
|
|
41
|
+
|
|
42
|
+
friend bool operator==(const BBox&, const BBox&) = default;
|
|
43
|
+
friend auto operator<=>(const BBox&, const BBox&) = default;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
struct PolygonRecord {
|
|
47
|
+
LayerSpec layer;
|
|
48
|
+
BBox bbox;
|
|
49
|
+
std::vector<Point> points;
|
|
50
|
+
bool valid = true;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
struct TileConfig {
|
|
54
|
+
std::int64_t tile_size = 1024;
|
|
55
|
+
std::uint64_t max_tiles_per_polygon = 4096;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
struct TileKey {
|
|
59
|
+
LayerSpec layer;
|
|
60
|
+
std::int64_t tx = 0;
|
|
61
|
+
std::int64_t ty = 0;
|
|
62
|
+
|
|
63
|
+
friend bool operator==(const TileKey&, const TileKey&) = default;
|
|
64
|
+
friend auto operator<=>(const TileKey&, const TileKey&) = default;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
struct OversizedPolygon {
|
|
68
|
+
LayerSpec layer;
|
|
69
|
+
std::uint64_t canonical_id = 0;
|
|
70
|
+
std::uint8_t side_mask = 0;
|
|
71
|
+
BBox tile_bbox;
|
|
72
|
+
|
|
73
|
+
friend bool operator==(const OversizedPolygon&, const OversizedPolygon&) = default;
|
|
74
|
+
friend auto operator<=>(const OversizedPolygon&, const OversizedPolygon&) = default;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
struct TilePrefilterResult {
|
|
78
|
+
std::vector<TileKey> candidate_tiles;
|
|
79
|
+
std::vector<OversizedPolygon> oversized_candidates;
|
|
80
|
+
std::uint64_t assignment_count = 0;
|
|
81
|
+
std::uint64_t collapsed_pair_count = 0;
|
|
82
|
+
std::uint64_t oversized_matched_count = 0;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
std::int64_t floor_div(std::int64_t value, std::int64_t divisor);
|
|
86
|
+
BBox bbox_tile_range(const BBox& bbox, std::int64_t tile_size);
|
|
87
|
+
std::string canonical_blob(const std::vector<Point>& points, bool valid);
|
|
88
|
+
std::vector<std::uint64_t> intern_canonical_blobs(const std::vector<std::string>& blobs);
|
|
89
|
+
TilePrefilterResult run_tile_prefilter(
|
|
90
|
+
const std::vector<PolygonRecord>& old_polygons,
|
|
91
|
+
const std::vector<std::uint64_t>& old_canonical_ids,
|
|
92
|
+
const std::vector<PolygonRecord>& new_polygons,
|
|
93
|
+
const std::vector<std::uint64_t>& new_canonical_ids,
|
|
94
|
+
const TileConfig& config);
|
|
95
|
+
|
|
96
|
+
} // namespace gdsdiff
|