cpp-pd-code-simplify-interface 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.
@@ -0,0 +1,1690 @@
1
+ #include "pdcode_simplify/pdcode_simplify.hpp"
2
+
3
+ #include <algorithm>
4
+ #include <cctype>
5
+ #include <cstdlib>
6
+ #include <deque>
7
+ #include <limits>
8
+ #include <map>
9
+ #include <numeric>
10
+ #include <random>
11
+ #include <set>
12
+ #include <sstream>
13
+ #include <stdexcept>
14
+ #include <unordered_map>
15
+ #include <unordered_set>
16
+
17
+ namespace pdcode_simplify {
18
+ namespace {
19
+
20
+ constexpr int kBlockedWeight = 10000;
21
+
22
+ int positive_mod(int value, int modulus) {
23
+ int result = value % modulus;
24
+ return result < 0 ? result + modulus : result;
25
+ }
26
+
27
+ int endpoint_key(const Endpoint& endpoint) {
28
+ return endpoint.crossing * 4 + endpoint.strand;
29
+ }
30
+
31
+ Endpoint endpoint_from_key(int key) {
32
+ return Endpoint{key / 4, key % 4};
33
+ }
34
+
35
+ int max_label(const PDCode& code) {
36
+ int result = -1;
37
+ for (const Crossing& crossing : code) {
38
+ for (int label : crossing) {
39
+ result = std::max(result, label);
40
+ }
41
+ }
42
+ return result;
43
+ }
44
+
45
+ long long face_pair_key(int a, int b) {
46
+ if (a > b) {
47
+ std::swap(a, b);
48
+ }
49
+ return (static_cast<long long>(a) << 32) ^ static_cast<unsigned int>(b);
50
+ }
51
+
52
+ struct CrossingState {
53
+ std::array<Endpoint, 4> adjacent{};
54
+ bool directions[4][4]{};
55
+ int sign = 0;
56
+ };
57
+
58
+ using LabelMap = std::unordered_map<int, std::vector<Endpoint>>;
59
+
60
+ LabelMap build_label_map(const PDCode& code) {
61
+ LabelMap labels;
62
+ for (int c = 0; c < static_cast<int>(code.size()); ++c) {
63
+ for (int i = 0; i < 4; ++i) {
64
+ labels[code[c][i]].push_back(Endpoint{c, i});
65
+ }
66
+ }
67
+ for (const auto& item : labels) {
68
+ if (item.second.size() != 2) {
69
+ std::ostringstream message;
70
+ message << "PD label " << item.first << " appears " << item.second.size()
71
+ << " times; each label must appear exactly twice";
72
+ throw std::invalid_argument(message.str());
73
+ }
74
+ }
75
+ return labels;
76
+ }
77
+
78
+ Endpoint mate_endpoint(const PDCode& code, const LabelMap& labels, const Endpoint& endpoint) {
79
+ const int label = code.at(endpoint.crossing).at(endpoint.strand);
80
+ const std::vector<Endpoint>& endpoints = labels.at(label);
81
+ if (endpoints[0] == endpoint) {
82
+ return endpoints[1];
83
+ }
84
+ if (endpoints[1] == endpoint) {
85
+ return endpoints[0];
86
+ }
87
+ throw std::logic_error("Endpoint is not present in its label map entry");
88
+ }
89
+
90
+ void replace_label(PDCode& code, int old_label, int new_label) {
91
+ if (old_label == new_label) {
92
+ return;
93
+ }
94
+ for (Crossing& crossing : code) {
95
+ for (int& label : crossing) {
96
+ if (label == old_label) {
97
+ label = new_label;
98
+ }
99
+ }
100
+ }
101
+ }
102
+
103
+ void erase_crossings(PDCode& code, int first, int second = -1) {
104
+ if (second >= 0 && first > second) {
105
+ std::swap(first, second);
106
+ }
107
+ if (second >= 0) {
108
+ code.erase(code.begin() + second);
109
+ }
110
+ code.erase(code.begin() + first);
111
+ }
112
+
113
+ int label_occurrences_outside(const PDCode& code, int label, const std::set<int>& removed_crossings) {
114
+ int count = 0;
115
+ for (int crossing_index = 0; crossing_index < static_cast<int>(code.size()); ++crossing_index) {
116
+ if (removed_crossings.count(crossing_index) != 0) {
117
+ continue;
118
+ }
119
+ for (int strand = 0; strand < 4; ++strand) {
120
+ if (code[crossing_index][strand] == label) {
121
+ ++count;
122
+ }
123
+ }
124
+ }
125
+ return count;
126
+ }
127
+
128
+ int unique_label_count(const Crossing& crossing) {
129
+ return static_cast<int>(std::set<int>(crossing.begin(), crossing.end()).size());
130
+ }
131
+
132
+ std::vector<int> pd_value_set(const PDCode& code) {
133
+ std::set<int> values;
134
+ for (const Crossing& crossing : code) {
135
+ values.insert(crossing.begin(), crossing.end());
136
+ }
137
+ return std::vector<int>(values.begin(), values.end());
138
+ }
139
+
140
+ bool contains_value(const std::vector<int>& values, int value) {
141
+ return std::find(values.begin(), values.end(), value) != values.end();
142
+ }
143
+
144
+ void add_undirected_vector_edge(std::map<int, std::vector<int>>& graph, int a, int b) {
145
+ std::vector<int>& first = graph[a];
146
+ if (std::find(first.begin(), first.end(), b) == first.end()) {
147
+ first.push_back(b);
148
+ }
149
+ std::vector<int>& second = graph[b];
150
+ if (std::find(second.begin(), second.end(), a) == second.end()) {
151
+ second.push_back(a);
152
+ }
153
+ }
154
+
155
+ void add_undirected_set_edge(std::map<int, std::set<int>>& graph, int a, int b) {
156
+ graph[a].insert(b);
157
+ graph[b].insert(a);
158
+ }
159
+
160
+ std::map<int, std::vector<int>> pd_adjacency_vector(const PDCode& code) {
161
+ std::map<int, std::vector<int>> graph;
162
+ for (const Crossing& crossing : code) {
163
+ add_undirected_vector_edge(graph, crossing[0], crossing[2]);
164
+ add_undirected_vector_edge(graph, crossing[1], crossing[3]);
165
+ }
166
+ return graph;
167
+ }
168
+
169
+ PDCode renumber_r1_order(PDCode code) {
170
+ if (code.empty()) {
171
+ return code;
172
+ }
173
+
174
+ const std::vector<int> values = pd_value_set(code);
175
+ const std::map<int, std::vector<int>> graph = pd_adjacency_vector(code);
176
+ std::vector<int> visit_order;
177
+ for (int value : values) {
178
+ if (contains_value(visit_order, value)) {
179
+ continue;
180
+ }
181
+ if (graph.find(value) == graph.end()) {
182
+ throw std::runtime_error("Invalid PD graph during R1 renumbering");
183
+ }
184
+ visit_order.push_back(value);
185
+ while (true) {
186
+ const int current = visit_order.back();
187
+ std::vector<int> neighbors = graph.at(current);
188
+ std::sort(neighbors.begin(), neighbors.end());
189
+ bool advanced = false;
190
+ for (int next : neighbors) {
191
+ if (!contains_value(visit_order, next)) {
192
+ visit_order.push_back(next);
193
+ advanced = true;
194
+ break;
195
+ }
196
+ }
197
+ if (!advanced) {
198
+ break;
199
+ }
200
+ }
201
+ }
202
+
203
+ std::map<int, int> new_label;
204
+ for (int i = 0; i < static_cast<int>(visit_order.size()); ++i) {
205
+ new_label[visit_order[i]] = i;
206
+ }
207
+ for (Crossing& crossing : code) {
208
+ for (int& label : crossing) {
209
+ label = new_label.at(label);
210
+ }
211
+ }
212
+ return code;
213
+ }
214
+
215
+ PDCode erase_r1_moves(PDCode code, std::size_t& crossingless_components, int& moves) {
216
+ if (!code.empty()) {
217
+ build_label_map(code);
218
+ }
219
+
220
+ while (true) {
221
+ bool changed = false;
222
+ for (int crossing_index = 0; crossing_index < static_cast<int>(code.size()); ++crossing_index) {
223
+ if (unique_label_count(code[crossing_index]) > 3) {
224
+ continue;
225
+ }
226
+
227
+ const Crossing crossing = code[crossing_index];
228
+ const ComponentAnalysis after_removal =
229
+ analyze_components_after_removing_crossings(
230
+ code, std::vector<int>{crossing_index}, crossingless_components);
231
+ code.erase(code.begin() + crossing_index);
232
+
233
+ std::vector<int> singles;
234
+ for (int label : crossing) {
235
+ if (std::count(crossing.begin(), crossing.end(), label) == 1) {
236
+ singles.push_back(label);
237
+ }
238
+ }
239
+ if (singles.size() == 2) {
240
+ replace_label(code, singles[0], singles[1]);
241
+ }
242
+
243
+ crossingless_components = after_removal.crossingless_components;
244
+ ++moves;
245
+ changed = true;
246
+ break;
247
+ }
248
+
249
+ if (!changed) {
250
+ break;
251
+ }
252
+ }
253
+
254
+ return renumber_r1_order(code);
255
+ }
256
+
257
+ std::map<int, std::set<int>> base_pd_graph(const PDCode& code) {
258
+ std::map<int, std::set<int>> graph;
259
+ for (int i = 0; i < static_cast<int>(code.size()); ++i) {
260
+ const int crossing_node = -i - 1;
261
+ for (int label : code[i]) {
262
+ add_undirected_set_edge(graph, label, crossing_node);
263
+ }
264
+ }
265
+ return graph;
266
+ }
267
+
268
+ int graph_component_count(const PDCode& code) {
269
+ const std::map<int, std::set<int>> graph = base_pd_graph(code);
270
+ std::set<int> visited;
271
+ int count = 0;
272
+ for (const auto& item : graph) {
273
+ const int start = item.first;
274
+ if (visited.count(start) != 0) {
275
+ continue;
276
+ }
277
+ ++count;
278
+ std::vector<int> stack(1, start);
279
+ visited.insert(start);
280
+ while (!stack.empty()) {
281
+ const int node = stack.back();
282
+ stack.pop_back();
283
+ const auto found = graph.find(node);
284
+ if (found == graph.end()) {
285
+ continue;
286
+ }
287
+ for (int next : found->second) {
288
+ if (visited.insert(next).second) {
289
+ stack.push_back(next);
290
+ }
291
+ }
292
+ }
293
+ }
294
+ return count;
295
+ }
296
+
297
+ bool is_nugatory_crossing(const PDCode& code, int crossing_index) {
298
+ if (unique_label_count(code[crossing_index]) != 4) {
299
+ throw std::runtime_error("Nugatory check requires an R1-free PD code");
300
+ }
301
+ PDCode without = code;
302
+ without.erase(without.begin() + crossing_index);
303
+ return graph_component_count(without) > graph_component_count(code);
304
+ }
305
+
306
+ int find_nugatory_crossing(const PDCode& code) {
307
+ for (int i = 0; i < static_cast<int>(code.size()); ++i) {
308
+ if (is_nugatory_crossing(code, i)) {
309
+ return i;
310
+ }
311
+ }
312
+ return -1;
313
+ }
314
+
315
+ void add_pre_next_edge(std::map<int, int>& previous, std::map<int, int>& next, int a, int b) {
316
+ int previous_value = 0;
317
+ int next_value = 0;
318
+ if (std::abs(a - b) == 1) {
319
+ previous_value = a < b ? a : b;
320
+ next_value = a < b ? b : a;
321
+ } else {
322
+ previous_value = a < b ? b : a;
323
+ next_value = a < b ? a : b;
324
+ }
325
+ previous[next_value] = previous_value;
326
+ next[previous_value] = next_value;
327
+ }
328
+
329
+ std::pair<std::map<int, int>, std::map<int, int>> pre_next_maps(const PDCode& code) {
330
+ if (!code.empty()) {
331
+ build_label_map(code);
332
+ }
333
+
334
+ std::map<int, int> previous;
335
+ std::map<int, int> next;
336
+ for (const Crossing& crossing : code) {
337
+ if (unique_label_count(crossing) > 2) {
338
+ add_pre_next_edge(previous, next, crossing[0], crossing[2]);
339
+ add_pre_next_edge(previous, next, crossing[1], crossing[3]);
340
+ } else {
341
+ std::vector<int> values(crossing.begin(), crossing.end());
342
+ std::sort(values.begin(), values.end());
343
+ values.erase(std::unique(values.begin(), values.end()), values.end());
344
+ if (values.size() != 2) {
345
+ throw std::runtime_error("Invalid two-value crossing in pre/next maps");
346
+ }
347
+ previous[values[0]] = values[1];
348
+ next[values[0]] = values[1];
349
+ previous[values[1]] = values[0];
350
+ next[values[1]] = values[0];
351
+ }
352
+ }
353
+
354
+ for (int label : pd_value_set(code)) {
355
+ if (previous.count(label) == 0) {
356
+ if (next.count(label) == 0) {
357
+ throw std::runtime_error("Broken PD pre/next map");
358
+ }
359
+ previous[label] = next[label];
360
+ }
361
+ if (next.count(label) == 0) {
362
+ next[label] = previous[label];
363
+ }
364
+ }
365
+ return std::make_pair(previous, next);
366
+ }
367
+
368
+ PDCode replace_arc_value(PDCode code, int from, int to) {
369
+ replace_label(code, from, to);
370
+ return code;
371
+ }
372
+
373
+ PDCode renumber_full_dfs(PDCode code) {
374
+ if (code.empty()) {
375
+ return code;
376
+ }
377
+
378
+ const std::vector<int> values = pd_value_set(code);
379
+ std::map<int, std::set<int>> graph;
380
+ for (const Crossing& crossing : code) {
381
+ add_undirected_set_edge(graph, crossing[0], crossing[2]);
382
+ add_undirected_set_edge(graph, crossing[1], crossing[3]);
383
+ }
384
+
385
+ std::set<int> visited;
386
+ std::map<int, int> new_label;
387
+ for (int start : values) {
388
+ if (visited.count(start) != 0) {
389
+ continue;
390
+ }
391
+ std::vector<int> stack(1, start);
392
+ while (!stack.empty()) {
393
+ const int value = stack.back();
394
+ stack.pop_back();
395
+ if (visited.count(value) != 0) {
396
+ continue;
397
+ }
398
+ const auto found = graph.find(value);
399
+ if (found == graph.end()) {
400
+ throw std::runtime_error("Invalid PD graph during renumbering");
401
+ }
402
+ new_label[value] = static_cast<int>(visited.size());
403
+ visited.insert(value);
404
+ for (std::set<int>::const_reverse_iterator it = found->second.rbegin();
405
+ it != found->second.rend();
406
+ ++it) {
407
+ if (visited.count(*it) == 0) {
408
+ stack.push_back(*it);
409
+ }
410
+ }
411
+ }
412
+ }
413
+
414
+ if (new_label.size() != values.size()) {
415
+ throw std::runtime_error("PD renumbering failed");
416
+ }
417
+ for (Crossing& crossing : code) {
418
+ for (int& label : crossing) {
419
+ label = new_label.at(label);
420
+ }
421
+ }
422
+ return code;
423
+ }
424
+
425
+ PDCode erase_one_nugatory_crossing(
426
+ PDCode code,
427
+ int crossing_index,
428
+ std::size_t& crossingless_components,
429
+ int& moves) {
430
+ if (unique_label_count(code[crossing_index]) != 4) {
431
+ throw std::runtime_error("Nugatory erase requires an R1-free PD code");
432
+ }
433
+
434
+ const Crossing crossing = code[crossing_index];
435
+ const int ax = crossing[0];
436
+ const int bx = crossing[1];
437
+ const int cx = crossing[2];
438
+ const int dx = crossing[3];
439
+ const std::map<int, int> next = pre_next_maps(code).second;
440
+
441
+ std::vector<int> loop(1, ax);
442
+ const std::size_t guard = pd_value_set(code).size() + 1;
443
+ while (true) {
444
+ const auto found = next.find(loop.back());
445
+ if (found == next.end()) {
446
+ throw std::runtime_error("Broken loop while erasing nugatory crossing");
447
+ }
448
+ const int next_label = found->second;
449
+ loop.push_back(next_label);
450
+ if (next_label == ax) {
451
+ loop.pop_back();
452
+ break;
453
+ }
454
+ if (loop.size() > guard) {
455
+ throw std::runtime_error("Failed to close PD loop while erasing nugatory crossing");
456
+ }
457
+ }
458
+
459
+ const std::set<int> loop_set(loop.begin(), loop.end());
460
+ if (loop_set.count(ax) == 0 || loop_set.count(bx) == 0 ||
461
+ loop_set.count(cx) == 0 || loop_set.count(dx) == 0) {
462
+ throw std::runtime_error("Nugatory crossing arcs are not in one component");
463
+ }
464
+
465
+ const ComponentAnalysis after_removal =
466
+ analyze_components_after_removing_crossings(
467
+ code, std::vector<int>{crossing_index}, crossingless_components);
468
+
469
+ code.erase(code.begin() + crossing_index);
470
+ code = replace_arc_value(code, ax, cx);
471
+ code = replace_arc_value(code, dx, bx);
472
+ crossingless_components = after_removal.crossingless_components;
473
+ ++moves;
474
+ return renumber_full_dfs(code);
475
+ }
476
+
477
+ std::vector<std::vector<int>> raw_faces_from_pd_code(const PDCode& code) {
478
+ const LabelMap labels = build_label_map(code);
479
+ const int endpoint_count = static_cast<int>(code.size() * 4);
480
+ std::vector<char> present(endpoint_count, true);
481
+ int remaining = endpoint_count;
482
+ std::vector<std::vector<int>> faces;
483
+
484
+ while (remaining > 0) {
485
+ int first_key = -1;
486
+ for (int key = endpoint_count - 1; key >= 0; --key) {
487
+ if (present[key]) {
488
+ first_key = key;
489
+ break;
490
+ }
491
+ }
492
+ if (first_key < 0) {
493
+ break;
494
+ }
495
+
496
+ std::vector<int> face;
497
+ Endpoint first = endpoint_from_key(first_key);
498
+ Endpoint current = first;
499
+ present[first_key] = false;
500
+ --remaining;
501
+ face.push_back(first_key);
502
+
503
+ while (true) {
504
+ const Endpoint next_corner{
505
+ current.crossing,
506
+ (current.strand + 1) % 4};
507
+ const Endpoint next = mate_endpoint(code, labels, next_corner);
508
+ if (next == first) {
509
+ faces.push_back(std::move(face));
510
+ break;
511
+ }
512
+ const int next_key = endpoint_key(next);
513
+ if (present[next_key]) {
514
+ present[next_key] = false;
515
+ --remaining;
516
+ }
517
+ face.push_back(next_key);
518
+ current = next;
519
+ }
520
+ }
521
+
522
+ return faces;
523
+ }
524
+
525
+ struct Diagram {
526
+ PDCode code;
527
+ std::vector<CrossingState> crossings;
528
+
529
+ explicit Diagram(PDCode input) : code(std::move(input)), crossings(code.size()) {
530
+ build_adjacency();
531
+ auto starts = component_starts_from_pd();
532
+ orient_crossings(starts);
533
+ }
534
+
535
+ Endpoint opposite(const Endpoint& endpoint) const {
536
+ return crossings.at(endpoint.crossing).adjacent.at(endpoint.strand);
537
+ }
538
+
539
+ Endpoint next(const Endpoint& endpoint) const {
540
+ return crossings.at(endpoint.crossing).adjacent.at((endpoint.strand + 2) % 4);
541
+ }
542
+
543
+ Endpoint next_corner(const Endpoint& endpoint) const {
544
+ return crossings.at(endpoint.crossing).adjacent.at((endpoint.strand + 1) % 4);
545
+ }
546
+
547
+ Endpoint rotate_endpoint(const Endpoint& endpoint, int offset) const {
548
+ return Endpoint{endpoint.crossing, positive_mod(endpoint.strand + offset, 4)};
549
+ }
550
+
551
+ std::vector<Endpoint> crossing_entries() const {
552
+ std::vector<Endpoint> entries;
553
+ entries.reserve(crossings.size() * 2);
554
+ for (int c = 0; c < static_cast<int>(crossings.size()); ++c) {
555
+ if (crossings[c].sign == -1) {
556
+ entries.push_back(Endpoint{c, 0});
557
+ entries.push_back(Endpoint{c, 1});
558
+ } else if (crossings[c].sign == 1) {
559
+ entries.push_back(Endpoint{c, 0});
560
+ entries.push_back(Endpoint{c, 3});
561
+ } else {
562
+ throw std::logic_error("Crossing was not oriented");
563
+ }
564
+ }
565
+ return entries;
566
+ }
567
+
568
+ private:
569
+ void build_adjacency() {
570
+ std::map<int, std::vector<Endpoint>> gluings;
571
+ for (int c = 0; c < static_cast<int>(code.size()); ++c) {
572
+ for (int i = 0; i < 4; ++i) {
573
+ gluings[code[c][i]].push_back(Endpoint{c, i});
574
+ }
575
+ }
576
+
577
+ for (std::map<int, std::vector<Endpoint>>::const_iterator it = gluings.begin();
578
+ it != gluings.end();
579
+ ++it) {
580
+ const int label = it->first;
581
+ const std::vector<Endpoint>& endpoints = it->second;
582
+ if (endpoints.size() != 2) {
583
+ std::ostringstream message;
584
+ message << "PD label " << label << " appears " << endpoints.size()
585
+ << " times; each label must appear exactly twice";
586
+ throw std::invalid_argument(message.str());
587
+ }
588
+ const Endpoint a = endpoints[0];
589
+ const Endpoint b = endpoints[1];
590
+ crossings[a.crossing].adjacent[a.strand] = b;
591
+ crossings[b.crossing].adjacent[b.strand] = a;
592
+ }
593
+ }
594
+
595
+ std::vector<Endpoint> component_starts_from_pd() const {
596
+ std::set<int> labels;
597
+ std::map<int, std::vector<Endpoint>> gluings;
598
+ for (int c = 0; c < static_cast<int>(code.size()); ++c) {
599
+ for (int i = 0; i < 4; ++i) {
600
+ labels.insert(code[c][i]);
601
+ gluings[code[c][i]].push_back(Endpoint{c, i});
602
+ }
603
+ }
604
+
605
+ std::vector<Endpoint> starts;
606
+ while (!labels.empty()) {
607
+ const int m = *labels.begin();
608
+ labels.erase(labels.begin());
609
+ const auto& gluing = gluings.at(m);
610
+ const Endpoint first = gluing[0];
611
+ const Endpoint second = gluing[1];
612
+
613
+ Endpoint direction;
614
+ int next_label = m;
615
+
616
+ if (first.crossing == second.crossing) {
617
+ std::set<int> crossing_labels(code[first.crossing].begin(), code[first.crossing].end());
618
+ crossing_labels.erase(m);
619
+ if (crossing_labels.empty()) {
620
+ throw std::invalid_argument("A PD self-loop crossing must have another label");
621
+ }
622
+ next_label = *crossing_labels.begin();
623
+ direction = Endpoint{first.crossing, index_of_label(first.crossing, next_label)};
624
+ } else {
625
+ const int j1 = (first.strand + 2) % 4;
626
+ const int j2 = (second.strand + 2) % 4;
627
+ const int l1 = code[first.crossing][j1];
628
+ const int l2 = code[second.crossing][j2];
629
+ if (l1 < l2) {
630
+ next_label = l1;
631
+ direction = Endpoint{first.crossing, j1};
632
+ } else if (l2 < l1) {
633
+ next_label = l2;
634
+ direction = Endpoint{second.crossing, j2};
635
+ } else {
636
+ next_label = l1;
637
+ if (code[second.crossing][0] == l1 || code[first.crossing][0] == m) {
638
+ direction = Endpoint{first.crossing, j1};
639
+ } else {
640
+ direction = Endpoint{second.crossing, j2};
641
+ }
642
+ }
643
+ }
644
+
645
+ starts.push_back(direction);
646
+ while (next_label != m) {
647
+ auto removed = labels.erase(next_label);
648
+ if (removed == 0) {
649
+ throw std::invalid_argument("PD component traversal encountered a repeated label");
650
+ }
651
+ const auto& next_gluing = gluings.at(next_label);
652
+ const int index = next_gluing[0] == direction ? 0 : (next_gluing[1] == direction ? 1 : -1);
653
+ if (index == -1) {
654
+ throw std::invalid_argument("PD component traversal lost its current endpoint");
655
+ }
656
+ const Endpoint other = next_gluing[1 - index];
657
+ direction = Endpoint{other.crossing, (other.strand + 2) % 4};
658
+ next_label = code[direction.crossing][direction.strand];
659
+ }
660
+ }
661
+
662
+ return starts;
663
+ }
664
+
665
+ int index_of_label(int crossing, int label) const {
666
+ for (int i = 0; i < 4; ++i) {
667
+ if (code[crossing][i] == label) {
668
+ return i;
669
+ }
670
+ }
671
+ throw std::logic_error("Label was not present at the requested crossing");
672
+ }
673
+
674
+ void make_tail(int crossing, int strand) {
675
+ const int head = (strand + 2) % 4;
676
+ if (crossings[crossing].directions[head][strand]) {
677
+ throw std::invalid_argument("The same crossing strand was oriented twice");
678
+ }
679
+ crossings[crossing].directions[strand][head] = true;
680
+ }
681
+
682
+ void orient_crossings(std::vector<Endpoint> starts) {
683
+ std::set<int> remaining;
684
+ for (int c = 0; c < static_cast<int>(crossings.size()); ++c) {
685
+ for (int i = 0; i < 4; ++i) {
686
+ remaining.insert(endpoint_key(Endpoint{c, i}));
687
+ }
688
+ }
689
+
690
+ while (!remaining.empty()) {
691
+ Endpoint start;
692
+ if (!starts.empty()) {
693
+ start = starts.back();
694
+ starts.pop_back();
695
+ } else {
696
+ start = endpoint_from_key(*remaining.begin());
697
+ }
698
+
699
+ Endpoint current = start;
700
+ while (true) {
701
+ const Endpoint other = crossings[current.crossing].adjacent[current.strand];
702
+ make_tail(other.crossing, other.strand);
703
+ remaining.erase(endpoint_key(current));
704
+ remaining.erase(endpoint_key(other));
705
+ current = Endpoint{other.crossing, (other.strand + 2) % 4};
706
+ if (current == start) {
707
+ break;
708
+ }
709
+ }
710
+ }
711
+
712
+ for (int c = 0; c < static_cast<int>(crossings.size()); ++c) {
713
+ orient_crossing(c);
714
+ }
715
+ }
716
+
717
+ void orient_crossing(int crossing) {
718
+ if (crossings[crossing].directions[2][0]) {
719
+ rotate_crossing_180(crossing);
720
+ }
721
+
722
+ if (crossings[crossing].directions[3][1]) {
723
+ crossings[crossing].sign = 1;
724
+ } else if (crossings[crossing].directions[1][3]) {
725
+ crossings[crossing].sign = -1;
726
+ } else {
727
+ throw std::invalid_argument("Could not determine crossing sign from PD orientation");
728
+ }
729
+ }
730
+
731
+ void rotate_crossing_180(int crossing) {
732
+ auto old_adjacent = crossings[crossing].adjacent;
733
+ bool old_directions[4][4]{};
734
+ for (int a = 0; a < 4; ++a) {
735
+ for (int b = 0; b < 4; ++b) {
736
+ old_directions[a][b] = crossings[crossing].directions[a][b];
737
+ crossings[crossing].directions[a][b] = false;
738
+ }
739
+ }
740
+
741
+ for (int i = 0; i < 4; ++i) {
742
+ const Endpoint other = old_adjacent[(i + 2) % 4];
743
+ if (other.crossing != crossing) {
744
+ crossings[other.crossing].adjacent[other.strand] = Endpoint{crossing, i};
745
+ crossings[crossing].adjacent[i] = other;
746
+ } else {
747
+ crossings[crossing].adjacent[i] = Endpoint{crossing, positive_mod(other.strand - 2, 4)};
748
+ }
749
+ }
750
+
751
+ for (int a = 0; a < 4; ++a) {
752
+ for (int b = 0; b < 4; ++b) {
753
+ if (old_directions[a][b]) {
754
+ crossings[crossing].directions[(a + 2) % 4][(b + 2) % 4] = true;
755
+ }
756
+ }
757
+ }
758
+ }
759
+ };
760
+
761
+ struct GraphEdge {
762
+ int u = -1;
763
+ int v = -1;
764
+ int interface_u = -1;
765
+ int interface_v = -1;
766
+ int weight = 1;
767
+ };
768
+
769
+ struct DualGraph {
770
+ std::vector<int> edge_to_face;
771
+ std::vector<int> face_assignment_order;
772
+ std::vector<std::vector<int>> faces;
773
+ std::vector<GraphEdge> edges;
774
+ std::vector<std::vector<int>> adjacency;
775
+ std::unordered_map<long long, int> edge_by_faces;
776
+
777
+ explicit DualGraph(const Diagram& diagram) {
778
+ build_faces(diagram);
779
+ build_edges(diagram);
780
+ }
781
+
782
+ int edge_index(int a, int b) const {
783
+ const auto found = edge_by_faces.find(face_pair_key(a, b));
784
+ if (found == edge_by_faces.end()) {
785
+ return -1;
786
+ }
787
+ return found->second;
788
+ }
789
+
790
+ const GraphEdge* edge(int a, int b) const {
791
+ const int index = edge_index(a, b);
792
+ if (index < 0) {
793
+ return nullptr;
794
+ }
795
+ return &edges[index];
796
+ }
797
+
798
+ GraphEdge* mutable_edge(int a, int b) {
799
+ const int index = edge_index(a, b);
800
+ if (index < 0) {
801
+ return nullptr;
802
+ }
803
+ return &edges[index];
804
+ }
805
+
806
+ int interface_for_face(const GraphEdge& edge, int face) const {
807
+ if (edge.u == face) {
808
+ return edge.interface_u;
809
+ }
810
+ if (edge.v == face) {
811
+ return edge.interface_v;
812
+ }
813
+ throw std::logic_error("Face is not incident to the requested dual edge");
814
+ }
815
+
816
+ private:
817
+ void build_faces(const Diagram& diagram) {
818
+ const int endpoint_count = static_cast<int>(diagram.crossings.size() * 4);
819
+ edge_to_face.assign(endpoint_count, -1);
820
+ std::vector<char> present(endpoint_count, true);
821
+ int remaining = endpoint_count;
822
+
823
+ while (remaining > 0) {
824
+ int first_key = -1;
825
+ for (int key = endpoint_count - 1; key >= 0; --key) {
826
+ if (present[key]) {
827
+ first_key = key;
828
+ break;
829
+ }
830
+ }
831
+ if (first_key == -1) {
832
+ break;
833
+ }
834
+
835
+ const int face_index = static_cast<int>(faces.size());
836
+ std::vector<int> face;
837
+ Endpoint first = endpoint_from_key(first_key);
838
+ Endpoint current = first;
839
+ present[first_key] = false;
840
+ --remaining;
841
+ edge_to_face[first_key] = face_index;
842
+ face_assignment_order.push_back(first_key);
843
+ face.push_back(first_key);
844
+
845
+ while (true) {
846
+ Endpoint next = diagram.next_corner(current);
847
+ if (next == first) {
848
+ faces.push_back(std::move(face));
849
+ break;
850
+ }
851
+ const int next_key = endpoint_key(next);
852
+ edge_to_face[next_key] = face_index;
853
+ face_assignment_order.push_back(next_key);
854
+ if (present[next_key]) {
855
+ present[next_key] = false;
856
+ --remaining;
857
+ }
858
+ face.push_back(next_key);
859
+ current = next;
860
+ }
861
+ }
862
+ }
863
+
864
+ void build_edges(const Diagram& diagram) {
865
+ adjacency.assign(faces.size(), {});
866
+ for (int key : face_assignment_order) {
867
+ const Endpoint endpoint = endpoint_from_key(key);
868
+ const Endpoint opposite = diagram.opposite(endpoint);
869
+ const int opposite_key = endpoint_key(opposite);
870
+ const int face = edge_to_face[key];
871
+ const int neighbor = edge_to_face[opposite_key];
872
+ if (face >= neighbor) {
873
+ continue;
874
+ }
875
+
876
+ const long long pair_key = face_pair_key(face, neighbor);
877
+ const auto found = edge_by_faces.find(pair_key);
878
+ if (found == edge_by_faces.end()) {
879
+ GraphEdge edge;
880
+ edge.u = face;
881
+ edge.v = neighbor;
882
+ edge.interface_u = key;
883
+ edge.interface_v = opposite_key;
884
+ edge.weight = 1;
885
+ const int edge_index = static_cast<int>(edges.size());
886
+ edge_by_faces[pair_key] = edge_index;
887
+ edges.push_back(edge);
888
+ adjacency[face].push_back(edge_index);
889
+ adjacency[neighbor].push_back(edge_index);
890
+ } else {
891
+ GraphEdge& edge = edges[found->second];
892
+ if (edge.u == face) {
893
+ edge.interface_u = key;
894
+ edge.interface_v = opposite_key;
895
+ } else {
896
+ edge.interface_u = opposite_key;
897
+ edge.interface_v = key;
898
+ }
899
+ }
900
+ }
901
+ }
902
+ };
903
+
904
+ enum class Level {
905
+ Under,
906
+ Over
907
+ };
908
+
909
+ std::string level_to_string(Level level) {
910
+ return level == Level::Under ? "under" : "over";
911
+ }
912
+
913
+ Level opposite_level(Level level) {
914
+ return level == Level::Under ? Level::Over : Level::Under;
915
+ }
916
+
917
+ std::vector<std::vector<Endpoint>> possible_red_lines(const Diagram& diagram) {
918
+ std::vector<std::vector<Endpoint>> long_lines;
919
+ std::vector<Endpoint> entries = diagram.crossing_entries();
920
+
921
+ while (!entries.empty()) {
922
+ std::vector<Endpoint> red_line;
923
+ Endpoint endpoint = entries.back();
924
+ entries.pop_back();
925
+ red_line.push_back(endpoint);
926
+ std::unordered_set<int> crossings;
927
+ crossings.insert(endpoint.crossing);
928
+
929
+ while (true) {
930
+ endpoint = diagram.next(endpoint);
931
+ red_line.push_back(endpoint);
932
+ if (crossings.count(endpoint.crossing) != 0) {
933
+ break;
934
+ }
935
+ crossings.insert(endpoint.crossing);
936
+ }
937
+ long_lines.push_back(std::move(red_line));
938
+ }
939
+
940
+ std::vector<std::vector<Endpoint>> candidates;
941
+ for (const auto& line : long_lines) {
942
+ if (line.size() < 3) {
943
+ continue;
944
+ }
945
+ for (std::size_t i = 0; i < line.size() - 2; ++i) {
946
+ candidates.emplace_back(line.begin(), line.end() - static_cast<std::ptrdiff_t>(i));
947
+ }
948
+ }
949
+ return candidates;
950
+ }
951
+
952
+ std::vector<LinkComponentSummary> component_summaries(const Diagram& diagram) {
953
+ std::set<int> remaining_entries;
954
+ const std::vector<Endpoint> entries = diagram.crossing_entries();
955
+ for (const Endpoint& endpoint : entries) {
956
+ remaining_entries.insert(endpoint_key(endpoint));
957
+ }
958
+
959
+ std::vector<LinkComponentSummary> summaries;
960
+ while (!remaining_entries.empty()) {
961
+ Endpoint start = endpoint_from_key(*remaining_entries.rbegin());
962
+ Endpoint current = start;
963
+ std::set<int> crossing_set;
964
+
965
+ while (true) {
966
+ remaining_entries.erase(endpoint_key(current));
967
+ crossing_set.insert(current.crossing);
968
+ current = diagram.next(current);
969
+ if (current == start) {
970
+ break;
971
+ }
972
+ }
973
+
974
+ LinkComponentSummary summary;
975
+ summary.crossing_indices.assign(crossing_set.begin(), crossing_set.end());
976
+ summaries.push_back(std::move(summary));
977
+ }
978
+
979
+ return summaries;
980
+ }
981
+
982
+ std::set<int> normalized_removed_crossings(const PDCode& code, const std::vector<int>& removed_crossings) {
983
+ std::set<int> removed;
984
+ for (int crossing : removed_crossings) {
985
+ if (crossing < 0 || crossing >= static_cast<int>(code.size())) {
986
+ std::ostringstream message;
987
+ message << "Removed crossing index " << crossing << " is out of range";
988
+ throw std::invalid_argument(message.str());
989
+ }
990
+ removed.insert(crossing);
991
+ }
992
+ return removed;
993
+ }
994
+
995
+ void reset_weights(DualGraph& graph) {
996
+ for (auto& edge : graph.edges) {
997
+ edge.weight = 1;
998
+ }
999
+ }
1000
+
1001
+ void collect_simple_paths_dfs(
1002
+ const DualGraph& graph,
1003
+ int current,
1004
+ int target,
1005
+ int cutoff,
1006
+ int max_paths,
1007
+ std::vector<char>& visited,
1008
+ std::vector<int>& current_path,
1009
+ std::vector<std::vector<int>>& paths) {
1010
+ if (static_cast<int>(current_path.size()) - 1 >= cutoff) {
1011
+ return;
1012
+ }
1013
+
1014
+ for (int edge_index : graph.adjacency[current]) {
1015
+ const GraphEdge& edge = graph.edges[edge_index];
1016
+ const int next = edge.u == current ? edge.v : edge.u;
1017
+ if (visited[next]) {
1018
+ continue;
1019
+ }
1020
+
1021
+ current_path.push_back(next);
1022
+ visited[next] = true;
1023
+
1024
+ if (next == target) {
1025
+ int path_weight = 0;
1026
+ for (std::size_t i = 0; i + 1 < current_path.size(); ++i) {
1027
+ const GraphEdge* path_edge = graph.edge(current_path[i], current_path[i + 1]);
1028
+ if (path_edge == nullptr) {
1029
+ throw std::logic_error("Missing dual edge while weighing a path");
1030
+ }
1031
+ path_weight += path_edge->weight;
1032
+ if (path_weight >= cutoff) {
1033
+ break;
1034
+ }
1035
+ }
1036
+ if (path_weight < cutoff) {
1037
+ paths.push_back(current_path);
1038
+ }
1039
+ if (max_paths != -1 && static_cast<int>(paths.size()) > max_paths) {
1040
+ visited[next] = false;
1041
+ current_path.pop_back();
1042
+ return;
1043
+ }
1044
+ } else {
1045
+ collect_simple_paths_dfs(graph, next, target, cutoff, max_paths, visited, current_path, paths);
1046
+ if (max_paths != -1 && static_cast<int>(paths.size()) > max_paths) {
1047
+ visited[next] = false;
1048
+ current_path.pop_back();
1049
+ return;
1050
+ }
1051
+ }
1052
+
1053
+ visited[next] = false;
1054
+ current_path.pop_back();
1055
+ }
1056
+ }
1057
+
1058
+ std::vector<std::vector<int>> collect_simple_paths(
1059
+ const DualGraph& graph,
1060
+ int source,
1061
+ int target,
1062
+ int cutoff,
1063
+ int max_paths) {
1064
+ std::vector<std::vector<int>> paths;
1065
+ if (source == target || source < 0 || target < 0 ||
1066
+ source >= static_cast<int>(graph.faces.size()) ||
1067
+ target >= static_cast<int>(graph.faces.size()) ||
1068
+ cutoff <= 0) {
1069
+ return paths;
1070
+ }
1071
+
1072
+ std::vector<char> visited(graph.faces.size(), false);
1073
+ std::vector<int> current_path{source};
1074
+ visited[source] = true;
1075
+ collect_simple_paths_dfs(graph, source, target, cutoff, max_paths, visited, current_path, paths);
1076
+ return paths;
1077
+ }
1078
+
1079
+ bool contains_endpoint_key(const std::vector<int>& endpoints, int key) {
1080
+ return std::find(endpoints.begin(), endpoints.end(), key) != endpoints.end();
1081
+ }
1082
+
1083
+ bool do_check(
1084
+ const Diagram& diagram,
1085
+ const DualGraph& graph,
1086
+ const std::vector<Endpoint>& red_path,
1087
+ const std::vector<int>& green_path,
1088
+ Direction direction,
1089
+ SimplificationResult& result) {
1090
+ std::vector<int> green_left_cross;
1091
+ green_left_cross.reserve(green_path.size());
1092
+
1093
+ for (std::size_t i = 0; i + 1 < green_path.size(); ++i) {
1094
+ const int f1 = green_path[i];
1095
+ const int f2 = green_path[i + 1];
1096
+ const GraphEdge* edge = graph.edge(f1, f2);
1097
+ if (edge == nullptr) {
1098
+ return false;
1099
+ }
1100
+ const int face_for_interface = direction == Direction::Right ? f1 : f2;
1101
+ green_left_cross.push_back(graph.interface_for_face(*edge, face_for_interface));
1102
+ }
1103
+
1104
+ std::unordered_set<int> red_boundary_crossings;
1105
+ std::deque<int> to_check;
1106
+ std::unordered_set<int> queued;
1107
+ std::unordered_map<int, Level> check_result;
1108
+
1109
+ auto enqueue = [&](int key) {
1110
+ if (queued.insert(key).second) {
1111
+ to_check.push_back(key);
1112
+ }
1113
+ };
1114
+
1115
+ auto erase_queued = [&](int key) {
1116
+ auto found = queued.find(key);
1117
+ if (found != queued.end()) {
1118
+ queued.erase(found);
1119
+ auto it = std::find(to_check.begin(), to_check.end(), key);
1120
+ if (it != to_check.end()) {
1121
+ to_check.erase(it);
1122
+ }
1123
+ }
1124
+ };
1125
+
1126
+ for (std::size_t i = 0; i + 1 < red_path.size(); ++i) {
1127
+ const Endpoint red_endpoint = red_path[i];
1128
+ red_boundary_crossings.insert(red_endpoint.crossing);
1129
+ const int offset = direction == Direction::Right ? 3 : 1;
1130
+ const Endpoint cross_strand = diagram.rotate_endpoint(red_endpoint, offset);
1131
+ const int key = endpoint_key(cross_strand);
1132
+ enqueue(key);
1133
+ check_result[key] = (cross_strand.strand % 2 == 0) ? Level::Under : Level::Over;
1134
+ }
1135
+
1136
+ std::vector<GreenCrossing> green_crossings;
1137
+ std::unordered_map<int, int> green_index;
1138
+ for (int i = 0; i < static_cast<int>(green_path.size()); ++i) {
1139
+ green_index[green_path[i]] = i;
1140
+ }
1141
+
1142
+ bool good_path = true;
1143
+ while (!to_check.empty() && good_path) {
1144
+ const int start_key = to_check.back();
1145
+ to_check.pop_back();
1146
+ queued.erase(start_key);
1147
+ Endpoint cross_strand = endpoint_from_key(start_key);
1148
+
1149
+ while (true) {
1150
+ const int cross_key = endpoint_key(cross_strand);
1151
+ const Level current_level = check_result.at(cross_key);
1152
+ const Endpoint opposite = diagram.opposite(cross_strand);
1153
+ const int opposite_key = endpoint_key(opposite);
1154
+ const auto opposite_result = check_result.find(opposite_key);
1155
+ if (opposite_result != check_result.end() && opposite_result->second != current_level) {
1156
+ good_path = false;
1157
+ break;
1158
+ }
1159
+
1160
+ if (contains_endpoint_key(green_left_cross, cross_key)) {
1161
+ const int f1 = graph.edge_to_face[cross_key];
1162
+ const int f2 = graph.edge_to_face[opposite_key];
1163
+ const auto f1_index = green_index.find(f1);
1164
+ const auto f2_index = green_index.find(f2);
1165
+ if (f1_index == green_index.end() || f2_index == green_index.end()) {
1166
+ good_path = false;
1167
+ break;
1168
+ }
1169
+ const bool forward = f1_index->second < f2_index->second;
1170
+ GreenCrossing green_crossing;
1171
+ green_crossing.from_face = forward ? f1 : f2;
1172
+ green_crossing.to_face = forward ? f2 : f1;
1173
+ green_crossing.strand_level = level_to_string(opposite_level(current_level));
1174
+ green_crossings.push_back(std::move(green_crossing));
1175
+ break;
1176
+ }
1177
+
1178
+ check_result[opposite_key] = current_level;
1179
+ erase_queued(opposite_key);
1180
+
1181
+ if (red_boundary_crossings.count(opposite.crossing) != 0) {
1182
+ break;
1183
+ }
1184
+
1185
+ cross_strand = opposite;
1186
+ const Endpoint side1 = diagram.rotate_endpoint(cross_strand, 1);
1187
+ const Endpoint side2 = diagram.rotate_endpoint(cross_strand, 3);
1188
+ const int side1_key = endpoint_key(side1);
1189
+ const int side2_key = endpoint_key(side2);
1190
+
1191
+ if (cross_strand.strand % 2 == 1 && current_level == Level::Under) {
1192
+ auto first = check_result.find(side1_key);
1193
+ auto second = check_result.find(side2_key);
1194
+ if ((first != check_result.end() && first->second == Level::Over) ||
1195
+ (second != check_result.end() && second->second == Level::Over)) {
1196
+ good_path = false;
1197
+ break;
1198
+ }
1199
+ if (first == check_result.end()) {
1200
+ check_result[side1_key] = Level::Under;
1201
+ enqueue(side1_key);
1202
+ }
1203
+ if (second == check_result.end()) {
1204
+ check_result[side2_key] = Level::Under;
1205
+ enqueue(side2_key);
1206
+ }
1207
+ }
1208
+
1209
+ if (cross_strand.strand % 2 == 0 && current_level == Level::Over) {
1210
+ auto first = check_result.find(side1_key);
1211
+ auto second = check_result.find(side2_key);
1212
+ if ((first != check_result.end() && first->second == Level::Under) ||
1213
+ (second != check_result.end() && second->second == Level::Under)) {
1214
+ good_path = false;
1215
+ break;
1216
+ }
1217
+ if (first == check_result.end()) {
1218
+ check_result[side1_key] = Level::Over;
1219
+ enqueue(side1_key);
1220
+ }
1221
+ if (second == check_result.end()) {
1222
+ check_result[side2_key] = Level::Over;
1223
+ enqueue(side2_key);
1224
+ }
1225
+ }
1226
+
1227
+ const Endpoint across_same_crossing = diagram.rotate_endpoint(cross_strand, 2);
1228
+ const int across_key = endpoint_key(across_same_crossing);
1229
+ check_result[across_key] = current_level;
1230
+ cross_strand = across_same_crossing;
1231
+ }
1232
+ }
1233
+
1234
+ if (!good_path) {
1235
+ return false;
1236
+ }
1237
+
1238
+ result.found = true;
1239
+ result.direction = direction;
1240
+ result.red_path = red_path;
1241
+ result.green_path = green_path;
1242
+ result.green_crossings = std::move(green_crossings);
1243
+ return true;
1244
+ }
1245
+
1246
+ } // namespace
1247
+
1248
+ PDCode parse_pd_code(const std::string& text) {
1249
+ std::vector<int> numbers;
1250
+ for (std::size_t i = 0; i < text.size();) {
1251
+ if (text[i] == '-' || std::isdigit(static_cast<unsigned char>(text[i]))) {
1252
+ const std::size_t start = i;
1253
+ if (text[i] == '-') {
1254
+ ++i;
1255
+ if (i >= text.size() || !std::isdigit(static_cast<unsigned char>(text[i]))) {
1256
+ throw std::invalid_argument("A minus sign must be followed by digits");
1257
+ }
1258
+ }
1259
+ while (i < text.size() && std::isdigit(static_cast<unsigned char>(text[i]))) {
1260
+ ++i;
1261
+ }
1262
+ const std::string token = text.substr(start, i - start);
1263
+ numbers.push_back(std::stoi(token));
1264
+ } else {
1265
+ ++i;
1266
+ }
1267
+ }
1268
+
1269
+ if (numbers.empty()) {
1270
+ return {};
1271
+ }
1272
+ if (numbers.size() % 4 != 0) {
1273
+ throw std::invalid_argument("The input must contain a multiple of four integers");
1274
+ }
1275
+
1276
+ PDCode code;
1277
+ code.reserve(numbers.size() / 4);
1278
+ for (std::size_t i = 0; i < numbers.size(); i += 4) {
1279
+ code.push_back(Crossing{numbers[i], numbers[i + 1], numbers[i + 2], numbers[i + 3]});
1280
+ }
1281
+ return code;
1282
+ }
1283
+
1284
+ std::string format_pd_code(const PDCode& code) {
1285
+ std::ostringstream out;
1286
+ out << '[';
1287
+ for (std::size_t i = 0; i < code.size(); ++i) {
1288
+ if (i != 0) {
1289
+ out << ", ";
1290
+ }
1291
+ out << '(' << code[i][0] << ", " << code[i][1] << ", "
1292
+ << code[i][2] << ", " << code[i][3] << ')';
1293
+ }
1294
+ out << ']';
1295
+ return out.str();
1296
+ }
1297
+
1298
+ std::string format_endpoint(const Endpoint& endpoint) {
1299
+ std::ostringstream out;
1300
+ out << '(' << endpoint.crossing << ", " << endpoint.strand << ')';
1301
+ return out.str();
1302
+ }
1303
+
1304
+ std::string format_direction(Direction direction) {
1305
+ return direction == Direction::Left ? "left" : "right";
1306
+ }
1307
+
1308
+ ComponentAnalysis analyze_components(
1309
+ const PDCode& code,
1310
+ std::size_t known_crossingless_components) {
1311
+ ComponentAnalysis analysis;
1312
+ analysis.crossingless_components = known_crossingless_components;
1313
+ if (code.empty()) {
1314
+ return analysis;
1315
+ }
1316
+
1317
+ Diagram diagram(code);
1318
+ analysis.components = component_summaries(diagram);
1319
+ return analysis;
1320
+ }
1321
+
1322
+ ComponentAnalysis analyze_components_after_removing_crossings(
1323
+ const PDCode& code,
1324
+ const std::vector<int>& removed_crossings,
1325
+ std::size_t known_crossingless_components) {
1326
+ const std::set<int> removed = normalized_removed_crossings(code, removed_crossings);
1327
+ ComponentAnalysis original = analyze_components(code, known_crossingless_components);
1328
+ ComponentAnalysis reduced;
1329
+ reduced.crossingless_components = original.crossingless_components;
1330
+
1331
+ for (const LinkComponentSummary& component : original.components) {
1332
+ LinkComponentSummary remaining_component;
1333
+ for (int crossing : component.crossing_indices) {
1334
+ if (removed.count(crossing) == 0) {
1335
+ remaining_component.crossing_indices.push_back(crossing);
1336
+ }
1337
+ }
1338
+
1339
+ if (remaining_component.crossing_indices.empty()) {
1340
+ ++reduced.crossingless_components;
1341
+ } else {
1342
+ reduced.components.push_back(std::move(remaining_component));
1343
+ }
1344
+ }
1345
+
1346
+ return reduced;
1347
+ }
1348
+
1349
+ std::size_t count_crossingless_components_after_removing_crossings(
1350
+ const PDCode& code,
1351
+ const std::vector<int>& removed_crossings,
1352
+ std::size_t known_crossingless_components) {
1353
+ return analyze_components_after_removing_crossings(
1354
+ code, removed_crossings, known_crossingless_components).crossingless_components;
1355
+ }
1356
+
1357
+ bool apply_reverse_type_i(PDCode& code, std::mt19937& rng) {
1358
+ if (code.empty()) {
1359
+ return false;
1360
+ }
1361
+
1362
+ const LabelMap labels = build_label_map(code);
1363
+ std::uniform_int_distribution<int> endpoint_distribution(
1364
+ 0, static_cast<int>(code.size() * 4 - 1));
1365
+ const Endpoint first = endpoint_from_key(endpoint_distribution(rng));
1366
+ const Endpoint second = mate_endpoint(code, labels, first);
1367
+
1368
+ const int first_label = max_label(code) + 1;
1369
+ const int second_label = first_label + 1;
1370
+ const int loop_label = first_label + 2;
1371
+
1372
+ std::uniform_int_distribution<int> hand_distribution(0, 1);
1373
+ code[first.crossing][first.strand] = first_label;
1374
+ code[second.crossing][second.strand] = second_label;
1375
+ if (hand_distribution(rng) == 0) {
1376
+ code.push_back(Crossing{first_label, loop_label, loop_label, second_label});
1377
+ } else {
1378
+ code.push_back(Crossing{first_label, second_label, loop_label, loop_label});
1379
+ }
1380
+ return true;
1381
+ }
1382
+
1383
+ bool apply_reverse_type_ii(PDCode& code, std::mt19937& rng) {
1384
+ if (code.empty()) {
1385
+ return false;
1386
+ }
1387
+
1388
+ const LabelMap labels = build_label_map(code);
1389
+ const std::vector<std::vector<int>> faces = raw_faces_from_pd_code(code);
1390
+ std::vector<int> eligible_faces;
1391
+ for (int i = 0; i < static_cast<int>(faces.size()); ++i) {
1392
+ if (faces[i].size() > 1) {
1393
+ eligible_faces.push_back(i);
1394
+ }
1395
+ }
1396
+ if (eligible_faces.empty()) {
1397
+ return false;
1398
+ }
1399
+
1400
+ std::uniform_int_distribution<int> face_distribution(0, static_cast<int>(eligible_faces.size() - 1));
1401
+ for (int attempt = 0; attempt < 50; ++attempt) {
1402
+ const std::vector<int>& face = faces[eligible_faces[face_distribution(rng)]];
1403
+ std::uniform_int_distribution<int> corner_distribution(0, static_cast<int>(face.size() - 1));
1404
+ const int first_corner = corner_distribution(rng);
1405
+ int second_corner = corner_distribution(rng);
1406
+ if (first_corner == second_corner) {
1407
+ second_corner = (second_corner + 1) % static_cast<int>(face.size());
1408
+ }
1409
+
1410
+ const Endpoint c = endpoint_from_key(face[first_corner]);
1411
+ const Endpoint d = endpoint_from_key(face[second_corner]);
1412
+ const Endpoint c_opposite = mate_endpoint(code, labels, c);
1413
+ const Endpoint d_opposite = mate_endpoint(code, labels, d);
1414
+
1415
+ const std::set<int> touched{
1416
+ endpoint_key(c),
1417
+ endpoint_key(c_opposite),
1418
+ endpoint_key(d),
1419
+ endpoint_key(d_opposite)};
1420
+ if (touched.size() != 4) {
1421
+ continue;
1422
+ }
1423
+
1424
+ const int base = max_label(code) + 1;
1425
+ const int d_opposite_label = base;
1426
+ const int c_label = base + 1;
1427
+ const int shared_first = base + 2;
1428
+ const int shared_second = base + 3;
1429
+ const int c_opposite_label = base + 4;
1430
+ const int d_label = base + 5;
1431
+
1432
+ code[d_opposite.crossing][d_opposite.strand] = d_opposite_label;
1433
+ code[c.crossing][c.strand] = c_label;
1434
+ code[c_opposite.crossing][c_opposite.strand] = c_opposite_label;
1435
+ code[d.crossing][d.strand] = d_label;
1436
+
1437
+ code.push_back(Crossing{d_opposite_label, c_label, shared_first, shared_second});
1438
+ code.push_back(Crossing{shared_first, c_opposite_label, d_label, shared_second});
1439
+ return true;
1440
+ }
1441
+
1442
+ return false;
1443
+ }
1444
+
1445
+ bool apply_type_i_simplification(
1446
+ PDCode& code,
1447
+ std::size_t& crossingless_components,
1448
+ int& type_i_moves) {
1449
+ for (int crossing_index = 0; crossing_index < static_cast<int>(code.size()); ++crossing_index) {
1450
+ const Crossing crossing = code[crossing_index];
1451
+ for (int i = 0; i < 4; ++i) {
1452
+ if (crossing[i] != crossing[(i + 1) % 4]) {
1453
+ continue;
1454
+ }
1455
+
1456
+ const ComponentAnalysis after_removal =
1457
+ analyze_components_after_removing_crossings(
1458
+ code, std::vector<int>{crossing_index}, crossingless_components);
1459
+ const int keep_label = crossing[(i + 2) % 4];
1460
+ const int merge_label = crossing[(i + 3) % 4];
1461
+ const std::set<int> removed{crossing_index};
1462
+ if (keep_label == merge_label) {
1463
+ if (label_occurrences_outside(code, keep_label, removed) != 0) {
1464
+ continue;
1465
+ }
1466
+ } else if (label_occurrences_outside(code, keep_label, removed) != 1 ||
1467
+ label_occurrences_outside(code, merge_label, removed) != 1) {
1468
+ continue;
1469
+ }
1470
+
1471
+ crossingless_components = after_removal.crossingless_components;
1472
+ erase_crossings(code, crossing_index);
1473
+ replace_label(code, merge_label, keep_label);
1474
+ ++type_i_moves;
1475
+ return true;
1476
+ }
1477
+ }
1478
+ return false;
1479
+ }
1480
+
1481
+ bool apply_type_ii_simplification(
1482
+ PDCode& code,
1483
+ std::size_t& crossingless_components,
1484
+ int& type_ii_moves) {
1485
+ if (code.size() < 2) {
1486
+ return false;
1487
+ }
1488
+
1489
+ const LabelMap labels = build_label_map(code);
1490
+ for (int first_crossing = 0; first_crossing < static_cast<int>(code.size()); ++first_crossing) {
1491
+ for (int a = 0; a < 4; ++a) {
1492
+ const Endpoint first_mate =
1493
+ mate_endpoint(code, labels, Endpoint{first_crossing, a});
1494
+ const Endpoint second_mate =
1495
+ mate_endpoint(code, labels, Endpoint{first_crossing, (a + 1) % 4});
1496
+ const int second_crossing = first_mate.crossing;
1497
+ if (second_crossing == first_crossing || second_crossing != second_mate.crossing) {
1498
+ continue;
1499
+ }
1500
+
1501
+ const int b = first_mate.strand;
1502
+ const int c = second_mate.strand;
1503
+ if (positive_mod(b - 1, 4) != c || (a + b) % 2 != 0) {
1504
+ continue;
1505
+ }
1506
+
1507
+ const Crossing first = code[first_crossing];
1508
+ const Crossing second = code[second_crossing];
1509
+ const int keep_first = first[(a + 2) % 4];
1510
+ const int merge_first = second[(b + 2) % 4];
1511
+ const int keep_second = first[(a + 3) % 4];
1512
+ const int merge_second = second[(b + 1) % 4];
1513
+ const std::set<int> boundary_labels{
1514
+ keep_first,
1515
+ merge_first,
1516
+ keep_second,
1517
+ merge_second};
1518
+ const std::set<int> removed_crossings{first_crossing, second_crossing};
1519
+ if (boundary_labels.size() != 4 ||
1520
+ label_occurrences_outside(code, keep_first, removed_crossings) != 1 ||
1521
+ label_occurrences_outside(code, merge_first, removed_crossings) != 1 ||
1522
+ label_occurrences_outside(code, keep_second, removed_crossings) != 1 ||
1523
+ label_occurrences_outside(code, merge_second, removed_crossings) != 1) {
1524
+ continue;
1525
+ }
1526
+
1527
+ const ComponentAnalysis after_removal =
1528
+ analyze_components_after_removing_crossings(
1529
+ code,
1530
+ std::vector<int>{first_crossing, second_crossing},
1531
+ crossingless_components);
1532
+ crossingless_components = after_removal.crossingless_components;
1533
+
1534
+ erase_crossings(code, first_crossing, second_crossing);
1535
+ replace_label(code, merge_first, keep_first);
1536
+ replace_label(code, merge_second, keep_second);
1537
+ ++type_ii_moves;
1538
+ return true;
1539
+ }
1540
+ }
1541
+
1542
+ return false;
1543
+ }
1544
+
1545
+ RandomInflationResult randomly_increase_crossings(
1546
+ const PDCode& code,
1547
+ const RandomInflationOptions& options) {
1548
+ if (options.moves < 0) {
1549
+ throw std::invalid_argument("Random inflation move count cannot be negative");
1550
+ }
1551
+ if (options.type_ii_percentage < 0 || options.type_ii_percentage > 100) {
1552
+ throw std::invalid_argument("Type-II percentage must be between 0 and 100");
1553
+ }
1554
+
1555
+ RandomInflationResult result;
1556
+ result.code = code;
1557
+ result.seed = options.seed;
1558
+ std::mt19937 rng(options.seed);
1559
+ std::uniform_int_distribution<int> percent_distribution(0, 99);
1560
+
1561
+ for (int move = 0; move < options.moves; ++move) {
1562
+ const bool prefer_type_ii = percent_distribution(rng) < options.type_ii_percentage;
1563
+ if (prefer_type_ii && apply_reverse_type_ii(result.code, rng)) {
1564
+ ++result.type_ii_moves;
1565
+ } else if (apply_reverse_type_i(result.code, rng)) {
1566
+ ++result.type_i_moves;
1567
+ } else if (apply_reverse_type_ii(result.code, rng)) {
1568
+ ++result.type_ii_moves;
1569
+ } else {
1570
+ throw std::runtime_error("Could not apply any random crossing-increasing move");
1571
+ }
1572
+ }
1573
+
1574
+ return result;
1575
+ }
1576
+
1577
+ ReidemeisterSimplificationResult simplify_reidemeister_i_ii(
1578
+ const PDCode& code,
1579
+ std::size_t known_crossingless_components) {
1580
+ ReidemeisterSimplificationResult result;
1581
+ result.code = code;
1582
+ result.crossingless_components = known_crossingless_components;
1583
+
1584
+ while (true) {
1585
+ if (apply_type_i_simplification(
1586
+ result.code, result.crossingless_components, result.type_i_moves)) {
1587
+ continue;
1588
+ }
1589
+ if (apply_type_ii_simplification(
1590
+ result.code, result.crossingless_components, result.type_ii_moves)) {
1591
+ continue;
1592
+ }
1593
+ break;
1594
+ }
1595
+
1596
+ return result;
1597
+ }
1598
+
1599
+ PDSimplificationResult simplify_pd_code(
1600
+ const PDCode& code,
1601
+ std::size_t known_crossingless_components) {
1602
+ PDSimplificationResult result;
1603
+ result.code = code;
1604
+ result.crossingless_components = known_crossingless_components;
1605
+
1606
+ result.code = erase_r1_moves(
1607
+ result.code,
1608
+ result.crossingless_components,
1609
+ result.reidemeister_i_moves);
1610
+
1611
+ while (true) {
1612
+ const int crossing_index = find_nugatory_crossing(result.code);
1613
+ if (crossing_index < 0) {
1614
+ break;
1615
+ }
1616
+ result.code = erase_one_nugatory_crossing(
1617
+ result.code,
1618
+ crossing_index,
1619
+ result.crossingless_components,
1620
+ result.nugatory_crossing_moves);
1621
+ }
1622
+
1623
+ return result;
1624
+ }
1625
+
1626
+ SimplificationResult find_simplification(
1627
+ const PDCode& code,
1628
+ const SimplifierOptions& options) {
1629
+ SimplificationResult result;
1630
+ Diagram diagram(code);
1631
+ DualGraph graph(diagram);
1632
+ const auto red_lines = possible_red_lines(diagram);
1633
+
1634
+ for (const auto& red_path : red_lines) {
1635
+ ++result.tested_red_paths;
1636
+ reset_weights(graph);
1637
+
1638
+ const Endpoint start = red_path.front();
1639
+ const Endpoint end = red_path.back();
1640
+ const int start_face = graph.edge_to_face[endpoint_key(start)];
1641
+ const int start_opposite_face = graph.edge_to_face[endpoint_key(diagram.opposite(start))];
1642
+ const int end_face = graph.edge_to_face[endpoint_key(end)];
1643
+ const int end_opposite_face = graph.edge_to_face[endpoint_key(diagram.opposite(end))];
1644
+ const std::array<int, 2> sources{start_face, start_opposite_face};
1645
+ const std::array<int, 2> destinations{end_face, end_opposite_face};
1646
+
1647
+ for (std::size_t i = 1; i + 1 < red_path.size(); ++i) {
1648
+ const Endpoint endpoint = red_path[i];
1649
+ const int right_region = graph.edge_to_face[endpoint_key(endpoint)];
1650
+ const int left_region = graph.edge_to_face[endpoint_key(diagram.opposite(endpoint))];
1651
+ if (GraphEdge* edge = graph.mutable_edge(right_region, left_region)) {
1652
+ edge->weight = kBlockedWeight;
1653
+ }
1654
+ }
1655
+
1656
+ std::vector<std::vector<int>> paths;
1657
+ const int cutoff = static_cast<int>(red_path.size()) - 1;
1658
+ for (int source : sources) {
1659
+ for (int destination : destinations) {
1660
+ auto found_paths = collect_simple_paths(graph, source, destination, cutoff, options.max_paths);
1661
+ paths.insert(paths.end(), found_paths.begin(), found_paths.end());
1662
+ if (options.max_paths != -1 && static_cast<int>(paths.size()) > options.max_paths) {
1663
+ break;
1664
+ }
1665
+ }
1666
+ }
1667
+
1668
+ for (const auto& green_path : paths) {
1669
+ ++result.tested_green_paths;
1670
+ if (green_path.size() >= red_path.size()) {
1671
+ continue;
1672
+ }
1673
+ if (do_check(diagram, graph, red_path, green_path, Direction::Left, result)) {
1674
+ return result;
1675
+ }
1676
+ if (do_check(diagram, graph, red_path, green_path, Direction::Right, result)) {
1677
+ return result;
1678
+ }
1679
+ }
1680
+ }
1681
+
1682
+ return result;
1683
+ }
1684
+
1685
+ std::ostream& operator<<(std::ostream& out, const Endpoint& endpoint) {
1686
+ out << format_endpoint(endpoint);
1687
+ return out;
1688
+ }
1689
+
1690
+ } // namespace pdcode_simplify