cpp-pd-code-simplify-interface 0.1.2__py3-none-any.whl → 0.1.4__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.
@@ -6,12 +6,16 @@
6
6
  #include <deque>
7
7
  #include <limits>
8
8
  #include <map>
9
+ #include <atomic>
10
+ #include <exception>
11
+ #include <mutex>
9
12
  #include <numeric>
10
13
  #include <queue>
11
14
  #include <random>
12
15
  #include <set>
13
16
  #include <sstream>
14
17
  #include <stdexcept>
18
+ #include <thread>
15
19
  #include <unordered_map>
16
20
  #include <unordered_set>
17
21
 
@@ -998,11 +1002,10 @@ std::set<int> normalized_removed_crossings(const PDCode& code, const std::vector
998
1002
  return removed;
999
1003
  }
1000
1004
 
1001
- void reset_weights(DualGraph& graph) {
1002
- for (auto& edge : graph.edges) {
1003
- edge.weight = 1;
1004
- }
1005
- }
1005
+ std::vector<int> heuristic_distances_to_target(
1006
+ const DualGraph& graph,
1007
+ int target,
1008
+ int cutoff);
1006
1009
 
1007
1010
  void collect_simple_paths_dfs(
1008
1011
  const DualGraph& graph,
@@ -1010,12 +1013,19 @@ void collect_simple_paths_dfs(
1010
1013
  int target,
1011
1014
  int cutoff,
1012
1015
  int max_paths,
1016
+ int current_weight,
1017
+ const std::vector<int>& distance,
1013
1018
  std::vector<char>& visited,
1014
1019
  std::vector<int>& current_path,
1015
1020
  std::vector<std::vector<int>>& paths) {
1021
+ const int infinity = std::numeric_limits<int>::max() / 4;
1016
1022
  if (static_cast<int>(current_path.size()) - 1 >= cutoff) {
1017
1023
  return;
1018
1024
  }
1025
+ if (current < 0 || current >= static_cast<int>(distance.size()) ||
1026
+ distance[current] == infinity || current_weight + distance[current] >= cutoff) {
1027
+ return;
1028
+ }
1019
1029
 
1020
1030
  for (int edge_index : graph.adjacency[current]) {
1021
1031
  const GraphEdge& edge = graph.edges[edge_index];
@@ -1023,32 +1033,37 @@ void collect_simple_paths_dfs(
1023
1033
  if (visited[next]) {
1024
1034
  continue;
1025
1035
  }
1036
+ const int next_weight = current_weight + edge.weight;
1037
+ if (next_weight >= cutoff) {
1038
+ continue;
1039
+ }
1040
+ if (next < 0 || next >= static_cast<int>(distance.size()) ||
1041
+ distance[next] == infinity || next_weight + distance[next] >= cutoff) {
1042
+ continue;
1043
+ }
1026
1044
 
1027
1045
  current_path.push_back(next);
1028
1046
  visited[next] = true;
1029
1047
 
1030
1048
  if (next == target) {
1031
- int path_weight = 0;
1032
- for (std::size_t i = 0; i + 1 < current_path.size(); ++i) {
1033
- const GraphEdge* path_edge = graph.edge(current_path[i], current_path[i + 1]);
1034
- if (path_edge == nullptr) {
1035
- throw std::logic_error("Missing dual edge while weighing a path");
1036
- }
1037
- path_weight += path_edge->weight;
1038
- if (path_weight >= cutoff) {
1039
- break;
1040
- }
1041
- }
1042
- if (path_weight < cutoff) {
1043
- paths.push_back(current_path);
1044
- }
1049
+ paths.push_back(current_path);
1045
1050
  if (max_paths != -1 && static_cast<int>(paths.size()) > max_paths) {
1046
1051
  visited[next] = false;
1047
1052
  current_path.pop_back();
1048
1053
  return;
1049
1054
  }
1050
1055
  } else {
1051
- collect_simple_paths_dfs(graph, next, target, cutoff, max_paths, visited, current_path, paths);
1056
+ collect_simple_paths_dfs(
1057
+ graph,
1058
+ next,
1059
+ target,
1060
+ cutoff,
1061
+ max_paths,
1062
+ next_weight,
1063
+ distance,
1064
+ visited,
1065
+ current_path,
1066
+ paths);
1052
1067
  if (max_paths != -1 && static_cast<int>(paths.size()) > max_paths) {
1053
1068
  visited[next] = false;
1054
1069
  current_path.pop_back();
@@ -1077,8 +1092,19 @@ std::vector<std::vector<int>> collect_simple_paths(
1077
1092
 
1078
1093
  std::vector<char> visited(graph.faces.size(), false);
1079
1094
  std::vector<int> current_path{source};
1095
+ const std::vector<int> distance = heuristic_distances_to_target(graph, target, cutoff);
1080
1096
  visited[source] = true;
1081
- collect_simple_paths_dfs(graph, source, target, cutoff, max_paths, visited, current_path, paths);
1097
+ collect_simple_paths_dfs(
1098
+ graph,
1099
+ source,
1100
+ target,
1101
+ cutoff,
1102
+ max_paths,
1103
+ 0,
1104
+ distance,
1105
+ visited,
1106
+ current_path,
1107
+ paths);
1082
1108
  return paths;
1083
1109
  }
1084
1110
 
@@ -1446,6 +1472,375 @@ bool do_check(
1446
1472
  return true;
1447
1473
  }
1448
1474
 
1475
+ class DisjointSet {
1476
+ public:
1477
+ int find(int value) {
1478
+ auto inserted = parent_.insert(std::make_pair(value, value));
1479
+ int parent = inserted.first->second;
1480
+ if (parent != value) {
1481
+ parent = find(parent);
1482
+ parent_[value] = parent;
1483
+ }
1484
+ return parent;
1485
+ }
1486
+
1487
+ void unite(int first, int second) {
1488
+ int first_root = find(first);
1489
+ int second_root = find(second);
1490
+ if (first_root == second_root) {
1491
+ return;
1492
+ }
1493
+ if (second_root < first_root) {
1494
+ std::swap(first_root, second_root);
1495
+ }
1496
+ parent_[second_root] = first_root;
1497
+ }
1498
+
1499
+ private:
1500
+ std::map<int, int> parent_;
1501
+ };
1502
+
1503
+ std::map<std::pair<int, int>, std::string> green_crossing_levels(
1504
+ const SimplificationResult& result) {
1505
+ std::set<std::pair<int, int>> path_edges;
1506
+ for (std::size_t i = 0; i + 1 < result.green_path.size(); ++i) {
1507
+ path_edges.insert(std::make_pair(result.green_path[i], result.green_path[i + 1]));
1508
+ }
1509
+
1510
+ std::map<std::pair<int, int>, std::string> levels;
1511
+ for (const GreenCrossing& crossing : result.green_crossings) {
1512
+ const std::pair<int, int> edge_key =
1513
+ std::make_pair(crossing.from_face, crossing.to_face);
1514
+ if (path_edges.count(edge_key) == 0) {
1515
+ throw std::invalid_argument("Simplification witness has a green crossing outside the green path");
1516
+ }
1517
+ const auto inserted = levels.insert(std::make_pair(edge_key, crossing.strand_level));
1518
+ if (!inserted.second && inserted.first->second != crossing.strand_level) {
1519
+ throw std::invalid_argument("Simplification witness has conflicting green crossing levels");
1520
+ }
1521
+ }
1522
+ for (const auto& edge_key : path_edges) {
1523
+ if (levels.count(edge_key) == 0) {
1524
+ throw std::invalid_argument("Simplification witness is missing a green crossing level");
1525
+ }
1526
+ }
1527
+ return levels;
1528
+ }
1529
+
1530
+ void clear_witness(SimplificationResult& result) {
1531
+ result.found = false;
1532
+ result.red_path.clear();
1533
+ result.green_path.clear();
1534
+ result.green_crossings.clear();
1535
+ }
1536
+
1537
+ int crossing_graph_component_count(const PDCode& code) {
1538
+ const int crossing_count = static_cast<int>(code.size());
1539
+ if (crossing_count == 0) {
1540
+ return 0;
1541
+ }
1542
+
1543
+ std::vector<int> parent(crossing_count);
1544
+ std::iota(parent.begin(), parent.end(), 0);
1545
+ std::function<int(int)> find = [&](int value) -> int {
1546
+ if (parent[value] != value) {
1547
+ parent[value] = find(parent[value]);
1548
+ }
1549
+ return parent[value];
1550
+ };
1551
+ auto unite = [&](int first, int second) {
1552
+ int first_root = find(first);
1553
+ int second_root = find(second);
1554
+ if (first_root == second_root) {
1555
+ return;
1556
+ }
1557
+ if (second_root < first_root) {
1558
+ std::swap(first_root, second_root);
1559
+ }
1560
+ parent[second_root] = first_root;
1561
+ };
1562
+
1563
+ std::map<int, std::vector<int>> label_crossings;
1564
+ for (int crossing_index = 0; crossing_index < crossing_count; ++crossing_index) {
1565
+ for (int strand = 0; strand < 4; ++strand) {
1566
+ label_crossings[code[crossing_index][strand]].push_back(crossing_index);
1567
+ }
1568
+ }
1569
+ for (const auto& item : label_crossings) {
1570
+ if (item.second.size() != 2) {
1571
+ std::ostringstream message;
1572
+ message << "PD label " << item.first << " appears " << item.second.size()
1573
+ << " times; each label must appear exactly twice";
1574
+ throw std::invalid_argument(message.str());
1575
+ }
1576
+ unite(item.second[0], item.second[1]);
1577
+ }
1578
+
1579
+ std::set<int> roots;
1580
+ for (int crossing_index = 0; crossing_index < crossing_count; ++crossing_index) {
1581
+ roots.insert(find(crossing_index));
1582
+ }
1583
+ return static_cast<int>(roots.size());
1584
+ }
1585
+
1586
+ bool is_planar_pd_code(const PDCode& code) {
1587
+ if (code.empty()) {
1588
+ return true;
1589
+ }
1590
+ Diagram diagram(code);
1591
+ DualGraph graph(diagram);
1592
+ const int vertices = static_cast<int>(code.size());
1593
+ const int edges = vertices * 2;
1594
+ const int faces = static_cast<int>(graph.faces.size());
1595
+ const int graph_components = crossing_graph_component_count(code);
1596
+ return vertices - edges + faces == 2 * graph_components;
1597
+ }
1598
+
1599
+ } // namespace
1600
+
1601
+ MidSimplificationApplyResult apply_simplification_witness(
1602
+ const PDCode& code,
1603
+ const SimplificationResult& result,
1604
+ std::size_t known_crossingless_components) {
1605
+ if (!result.found) {
1606
+ throw std::invalid_argument("Cannot apply a missing simplification witness");
1607
+ }
1608
+ if (result.red_path.size() < 2) {
1609
+ throw std::invalid_argument("Simplification witness red path is too short");
1610
+ }
1611
+
1612
+ Diagram diagram(code);
1613
+ DualGraph graph(diagram);
1614
+ std::set<int> removed_crossings;
1615
+ std::map<int, int> red_entry_by_crossing;
1616
+ for (std::size_t i = 0; i + 1 < result.red_path.size(); ++i) {
1617
+ removed_crossings.insert(result.red_path[i].crossing);
1618
+ red_entry_by_crossing[result.red_path[i].crossing] = result.red_path[i].strand;
1619
+ }
1620
+ if (removed_crossings.size() != result.red_path.size() - 1) {
1621
+ throw std::invalid_argument("Simplification witness repeats a removed red crossing");
1622
+ }
1623
+ if (removed_crossings.count(result.red_path.back().crossing) != 0) {
1624
+ throw std::invalid_argument("Simplification witness ends inside the removed red arc");
1625
+ }
1626
+
1627
+ const std::map<std::pair<int, int>, std::string> levels = green_crossing_levels(result);
1628
+ DisjointSet dsu;
1629
+ const int endpoint_count = static_cast<int>(code.size() * 4);
1630
+ const int new_crossing_count =
1631
+ result.green_path.empty() ? 0 : static_cast<int>(result.green_path.size()) - 1;
1632
+ const int new_base = endpoint_count;
1633
+
1634
+ auto new_node = [&](int crossing_index, int strand) {
1635
+ return new_base + crossing_index * 4 + strand;
1636
+ };
1637
+ auto is_removed_node = [&](int node) {
1638
+ return node < endpoint_count && removed_crossings.count(node / 4) != 0;
1639
+ };
1640
+ auto is_removed_red_node = [&](int node) {
1641
+ if (!is_removed_node(node)) {
1642
+ return false;
1643
+ }
1644
+ const int crossing = node / 4;
1645
+ const int strand = node % 4;
1646
+ const int red_strand = red_entry_by_crossing.at(crossing);
1647
+ return strand == red_strand || strand == positive_mod(red_strand + 2, 4);
1648
+ };
1649
+
1650
+ std::set<int> crossed_labels;
1651
+ struct CrossedEdge {
1652
+ int interface_from = -1;
1653
+ int interface_to = -1;
1654
+ std::string level;
1655
+ };
1656
+ std::vector<CrossedEdge> crossed_edges;
1657
+ crossed_edges.reserve(new_crossing_count);
1658
+ for (int i = 0; i < new_crossing_count; ++i) {
1659
+ const int from_face = result.green_path[i];
1660
+ const int to_face = result.green_path[i + 1];
1661
+ const GraphEdge* edge = graph.edge(from_face, to_face);
1662
+ if (edge == nullptr) {
1663
+ throw std::invalid_argument("Simplification witness green path crosses a missing dual edge");
1664
+ }
1665
+ const int interface_from = graph.interface_for_face(*edge, from_face);
1666
+ const int interface_to = graph.interface_for_face(*edge, to_face);
1667
+ if (is_removed_red_node(interface_from) || is_removed_red_node(interface_to)) {
1668
+ throw std::invalid_argument("Simplification witness crosses an edge removed with the red arc");
1669
+ }
1670
+ const int label = code[interface_from / 4][interface_from % 4];
1671
+ if (!crossed_labels.insert(label).second) {
1672
+ throw std::invalid_argument("Simplification witness crosses the same PD edge more than once");
1673
+ }
1674
+ const auto level = levels.find(std::make_pair(from_face, to_face));
1675
+ if (level == levels.end()) {
1676
+ throw std::invalid_argument("Simplification witness is missing a green crossing level");
1677
+ }
1678
+ CrossedEdge crossed;
1679
+ crossed.interface_from = interface_from;
1680
+ crossed.interface_to = interface_to;
1681
+ crossed.level = level->second;
1682
+ crossed_edges.push_back(std::move(crossed));
1683
+ }
1684
+
1685
+ std::map<int, std::vector<int>> label_endpoints;
1686
+ for (int crossing_index = 0; crossing_index < static_cast<int>(code.size()); ++crossing_index) {
1687
+ for (int strand = 0; strand < 4; ++strand) {
1688
+ label_endpoints[code[crossing_index][strand]].push_back(crossing_index * 4 + strand);
1689
+ }
1690
+ }
1691
+ for (const auto& item : label_endpoints) {
1692
+ if (item.second.size() != 2) {
1693
+ std::ostringstream message;
1694
+ message << "PD label " << item.first << " appears " << item.second.size() << " times";
1695
+ throw std::invalid_argument(message.str());
1696
+ }
1697
+ if (crossed_labels.count(item.first) == 0) {
1698
+ dsu.unite(item.second[0], item.second[1]);
1699
+ }
1700
+ }
1701
+
1702
+ for (const auto& item : red_entry_by_crossing) {
1703
+ const int crossing = item.first;
1704
+ const int strand = item.second;
1705
+ dsu.unite(crossing * 4 + positive_mod(strand + 1, 4),
1706
+ crossing * 4 + positive_mod(strand + 3, 4));
1707
+ }
1708
+
1709
+ int green_anchor = endpoint_key(result.red_path.front());
1710
+ for (int i = 0; i < static_cast<int>(crossed_edges.size()); ++i) {
1711
+ const CrossedEdge& crossed = crossed_edges[i];
1712
+ int existing_from_pos = -1;
1713
+ int existing_to_pos = -1;
1714
+ int green_in_pos = -1;
1715
+ int green_out_pos = -1;
1716
+ if (crossed.level == "over") {
1717
+ existing_from_pos = 0;
1718
+ existing_to_pos = 2;
1719
+ green_in_pos = 3;
1720
+ green_out_pos = 1;
1721
+ } else if (crossed.level == "under") {
1722
+ existing_from_pos = 1;
1723
+ green_in_pos = 0;
1724
+ green_out_pos = 2;
1725
+ existing_to_pos = 3;
1726
+ } else {
1727
+ throw std::invalid_argument("Unknown green crossing strand level: " + crossed.level);
1728
+ }
1729
+
1730
+ dsu.unite(crossed.interface_from, new_node(i, existing_from_pos));
1731
+ dsu.unite(crossed.interface_to, new_node(i, existing_to_pos));
1732
+ dsu.unite(green_anchor, new_node(i, green_in_pos));
1733
+ green_anchor = new_node(i, green_out_pos);
1734
+ }
1735
+ dsu.unite(green_anchor, endpoint_key(result.red_path.back()));
1736
+
1737
+ std::vector<int> active_nodes;
1738
+ active_nodes.reserve(endpoint_count + new_crossing_count * 4);
1739
+ for (int node = 0; node < endpoint_count; ++node) {
1740
+ if (!is_removed_node(node)) {
1741
+ active_nodes.push_back(node);
1742
+ }
1743
+ }
1744
+ for (int crossing = 0; crossing < new_crossing_count; ++crossing) {
1745
+ for (int strand = 0; strand < 4; ++strand) {
1746
+ active_nodes.push_back(new_node(crossing, strand));
1747
+ }
1748
+ }
1749
+
1750
+ std::map<int, std::vector<int>> grouped;
1751
+ for (int node : active_nodes) {
1752
+ grouped[dsu.find(node)].push_back(node);
1753
+ }
1754
+
1755
+ std::vector<std::vector<int>> groups;
1756
+ for (auto& item : grouped) {
1757
+ std::sort(item.second.begin(), item.second.end());
1758
+ groups.push_back(item.second);
1759
+ }
1760
+ std::sort(groups.begin(), groups.end(), [](const std::vector<int>& lhs, const std::vector<int>& rhs) {
1761
+ return lhs.front() < rhs.front();
1762
+ });
1763
+
1764
+ std::map<int, int> label_by_node;
1765
+ int next_label = 0;
1766
+ for (const std::vector<int>& nodes : groups) {
1767
+ if (nodes.size() != 2) {
1768
+ std::ostringstream message;
1769
+ message << "Applied simplification produced a non-PD edge with "
1770
+ << nodes.size() << " active endpoints";
1771
+ throw std::runtime_error(message.str());
1772
+ }
1773
+ for (int node : nodes) {
1774
+ label_by_node[node] = next_label;
1775
+ }
1776
+ ++next_label;
1777
+ }
1778
+
1779
+ PDCode output;
1780
+ output.reserve(code.size() - removed_crossings.size() + static_cast<std::size_t>(new_crossing_count));
1781
+ for (int crossing_index = 0; crossing_index < static_cast<int>(code.size()); ++crossing_index) {
1782
+ if (removed_crossings.count(crossing_index) != 0) {
1783
+ continue;
1784
+ }
1785
+ Crossing crossing{};
1786
+ for (int strand = 0; strand < 4; ++strand) {
1787
+ crossing[strand] = label_by_node.at(crossing_index * 4 + strand);
1788
+ }
1789
+ output.push_back(crossing);
1790
+ }
1791
+ for (int crossing_index = 0; crossing_index < new_crossing_count; ++crossing_index) {
1792
+ Crossing crossing{};
1793
+ for (int strand = 0; strand < 4; ++strand) {
1794
+ crossing[strand] = label_by_node.at(new_node(crossing_index, strand));
1795
+ }
1796
+ output.push_back(crossing);
1797
+ }
1798
+
1799
+ const std::size_t total_components =
1800
+ analyze_components(code, known_crossingless_components).total_components();
1801
+ output = renumber_full_dfs(output);
1802
+ if (!is_planar_pd_code(output)) {
1803
+ throw std::invalid_argument("Applied simplification produced a non-planar PD code");
1804
+ }
1805
+ const std::size_t crossing_components = analyze_components(output).components_with_crossings();
1806
+
1807
+ MidSimplificationApplyResult applied;
1808
+ applied.code = std::move(output);
1809
+ applied.crossingless_components =
1810
+ total_components > crossing_components ? total_components - crossing_components : 0;
1811
+ return applied;
1812
+ }
1813
+
1814
+ namespace {
1815
+
1816
+ bool witness_has_applicable_surgery(const PDCode& code, const SimplificationResult& result) {
1817
+ try {
1818
+ (void)apply_simplification_witness(code, result, 0);
1819
+ return true;
1820
+ } catch (const std::exception&) {
1821
+ return false;
1822
+ }
1823
+ }
1824
+
1825
+ void emit_progress(const SimplifierOptions& options, const std::string& message) {
1826
+ if (!options.verbose) {
1827
+ return;
1828
+ }
1829
+ if (options.progress) {
1830
+ options.progress(message);
1831
+ }
1832
+ }
1833
+
1834
+ std::string search_mode_for_options(const SimplifierOptions& options) {
1835
+ if (options.max_paths == -1 && !options.ban_heuristic) {
1836
+ return "heuristic";
1837
+ }
1838
+ if (options.max_paths == -1) {
1839
+ return "bruteforce";
1840
+ }
1841
+ return "bounded";
1842
+ }
1843
+
1449
1844
  } // namespace
1450
1845
 
1451
1846
  PDCode parse_pd_code(const std::string& text) {
@@ -1486,13 +1881,13 @@ PDCode parse_pd_code(const std::string& text) {
1486
1881
 
1487
1882
  std::string format_pd_code(const PDCode& code) {
1488
1883
  std::ostringstream out;
1489
- out << '[';
1884
+ out << "PD[";
1490
1885
  for (std::size_t i = 0; i < code.size(); ++i) {
1491
1886
  if (i != 0) {
1492
- out << ", ";
1887
+ out << ',';
1493
1888
  }
1494
- out << '(' << code[i][0] << ", " << code[i][1] << ", "
1495
- << code[i][2] << ", " << code[i][3] << ')';
1889
+ out << "X[" << code[i][0] << ',' << code[i][1] << ','
1890
+ << code[i][2] << ',' << code[i][3] << ']';
1496
1891
  }
1497
1892
  out << ']';
1498
1893
  return out.str();
@@ -1826,75 +2221,442 @@ PDSimplificationResult simplify_pd_code(
1826
2221
  return result;
1827
2222
  }
1828
2223
 
2224
+ namespace {
2225
+
2226
+ int detected_worker_count() {
2227
+ const unsigned int reported = std::thread::hardware_concurrency();
2228
+ if (reported == 0) {
2229
+ return 1;
2230
+ }
2231
+ if (reported <= 2) {
2232
+ return static_cast<int>(reported);
2233
+ }
2234
+ return static_cast<int>(reported - 1);
2235
+ }
2236
+
2237
+ int selected_bruteforce_worker_count(int max_threads, int task_count) {
2238
+ if (task_count <= 1) {
2239
+ return 1;
2240
+ }
2241
+ int requested = max_threads == -1 ? detected_worker_count() : max_threads;
2242
+ if (requested < 1) {
2243
+ requested = 1;
2244
+ }
2245
+ if (max_threads == -1 && task_count < 32) {
2246
+ return 1;
2247
+ }
2248
+ return std::max(1, std::min(requested, task_count));
2249
+ }
2250
+
2251
+ bool should_skip_parallel_red_path(
2252
+ int red_index,
2253
+ const std::atomic<int>* best_found_index) {
2254
+ return best_found_index != nullptr &&
2255
+ red_index > best_found_index->load(std::memory_order_relaxed);
2256
+ }
2257
+
2258
+ void record_parallel_found_index(
2259
+ int red_index,
2260
+ std::atomic<int>* best_found_index) {
2261
+ if (best_found_index == nullptr) {
2262
+ return;
2263
+ }
2264
+ int observed = best_found_index->load(std::memory_order_relaxed);
2265
+ while (red_index < observed &&
2266
+ !best_found_index->compare_exchange_weak(
2267
+ observed,
2268
+ red_index,
2269
+ std::memory_order_relaxed,
2270
+ std::memory_order_relaxed)) {
2271
+ }
2272
+ }
2273
+
2274
+ struct RedPathSearchOutcome {
2275
+ bool completed = false;
2276
+ bool skipped = false;
2277
+ bool found = false;
2278
+ std::size_t tested_green_paths = 0;
2279
+ SimplificationResult witness;
2280
+ };
2281
+
2282
+ RedPathSearchOutcome search_single_red_path(
2283
+ const PDCode& code,
2284
+ const Diagram& diagram,
2285
+ const DualGraph& base_graph,
2286
+ const std::vector<Endpoint>& red_path,
2287
+ const SimplifierOptions& options,
2288
+ const std::string& path_search_mode,
2289
+ int red_index,
2290
+ const std::atomic<int>* best_found_index) {
2291
+ RedPathSearchOutcome outcome;
2292
+ outcome.witness.path_search_mode = path_search_mode;
2293
+ if (should_skip_parallel_red_path(red_index, best_found_index)) {
2294
+ outcome.skipped = true;
2295
+ return outcome;
2296
+ }
2297
+
2298
+ DualGraph graph = base_graph;
2299
+ const Endpoint start = red_path.front();
2300
+ const Endpoint end = red_path.back();
2301
+ const int start_face = graph.edge_to_face[endpoint_key(start)];
2302
+ const int start_opposite_face = graph.edge_to_face[endpoint_key(diagram.opposite(start))];
2303
+ const int end_face = graph.edge_to_face[endpoint_key(end)];
2304
+ const int end_opposite_face = graph.edge_to_face[endpoint_key(diagram.opposite(end))];
2305
+ const std::array<int, 2> sources{start_face, start_opposite_face};
2306
+ const std::array<int, 2> destinations{end_face, end_opposite_face};
2307
+
2308
+ for (std::size_t i = 1; i + 1 < red_path.size(); ++i) {
2309
+ const Endpoint endpoint = red_path[i];
2310
+ const int right_region = graph.edge_to_face[endpoint_key(endpoint)];
2311
+ const int left_region = graph.edge_to_face[endpoint_key(diagram.opposite(endpoint))];
2312
+ if (GraphEdge* edge = graph.mutable_edge(right_region, left_region)) {
2313
+ edge->weight = kBlockedWeight;
2314
+ }
2315
+ }
2316
+
2317
+ std::vector<std::vector<int>> paths;
2318
+ const int cutoff = static_cast<int>(red_path.size()) - 1;
2319
+ for (int source : sources) {
2320
+ for (int destination : destinations) {
2321
+ if (should_skip_parallel_red_path(red_index, best_found_index)) {
2322
+ outcome.skipped = true;
2323
+ return outcome;
2324
+ }
2325
+ std::vector<std::vector<int>> found_paths;
2326
+ if (options.max_paths == -1 && !options.ban_heuristic) {
2327
+ found_paths = collect_heuristic_paths(graph, source, destination, cutoff);
2328
+ } else {
2329
+ found_paths = collect_simple_paths(graph, source, destination, cutoff, options.max_paths);
2330
+ }
2331
+ paths.insert(paths.end(), found_paths.begin(), found_paths.end());
2332
+ if (options.max_paths != -1 && static_cast<int>(paths.size()) > options.max_paths) {
2333
+ break;
2334
+ }
2335
+ }
2336
+ }
2337
+
2338
+ for (const auto& green_path : paths) {
2339
+ if (should_skip_parallel_red_path(red_index, best_found_index)) {
2340
+ outcome.skipped = true;
2341
+ return outcome;
2342
+ }
2343
+ ++outcome.tested_green_paths;
2344
+ if (green_path.size() >= red_path.size()) {
2345
+ continue;
2346
+ }
2347
+ if (do_check(diagram, graph, red_path, green_path, Direction::Left, outcome.witness)) {
2348
+ if (!options.require_applicable || witness_has_applicable_surgery(code, outcome.witness)) {
2349
+ outcome.found = true;
2350
+ outcome.completed = true;
2351
+ outcome.witness.tested_green_paths = outcome.tested_green_paths;
2352
+ return outcome;
2353
+ }
2354
+ clear_witness(outcome.witness);
2355
+ }
2356
+ if (do_check(diagram, graph, red_path, green_path, Direction::Right, outcome.witness)) {
2357
+ if (!options.require_applicable || witness_has_applicable_surgery(code, outcome.witness)) {
2358
+ outcome.found = true;
2359
+ outcome.completed = true;
2360
+ outcome.witness.tested_green_paths = outcome.tested_green_paths;
2361
+ return outcome;
2362
+ }
2363
+ clear_witness(outcome.witness);
2364
+ }
2365
+ }
2366
+
2367
+ outcome.completed = true;
2368
+ outcome.witness.tested_green_paths = outcome.tested_green_paths;
2369
+ return outcome;
2370
+ }
2371
+
2372
+ SimplificationResult merge_red_path_outcomes(
2373
+ const std::vector<RedPathSearchOutcome>& outcomes,
2374
+ const std::string& path_search_mode) {
2375
+ SimplificationResult result;
2376
+ result.path_search_mode = path_search_mode;
2377
+
2378
+ int first_found = -1;
2379
+ for (int i = 0; i < static_cast<int>(outcomes.size()); ++i) {
2380
+ if (outcomes[i].found) {
2381
+ first_found = i;
2382
+ break;
2383
+ }
2384
+ }
2385
+
2386
+ const int limit = first_found >= 0 ? first_found : static_cast<int>(outcomes.size()) - 1;
2387
+ for (int i = 0; i <= limit; ++i) {
2388
+ if (!outcomes[i].completed && !outcomes[i].found) {
2389
+ std::ostringstream message;
2390
+ message << "Parallel brute-force search did not complete red path " << i;
2391
+ throw std::runtime_error(message.str());
2392
+ }
2393
+ ++result.tested_red_paths;
2394
+ result.tested_green_paths += outcomes[i].tested_green_paths;
2395
+ }
2396
+
2397
+ if (first_found >= 0) {
2398
+ result = outcomes[first_found].witness;
2399
+ result.path_search_mode = path_search_mode;
2400
+ result.tested_red_paths = static_cast<std::size_t>(first_found + 1);
2401
+ result.tested_green_paths = 0;
2402
+ for (int i = 0; i <= first_found; ++i) {
2403
+ result.tested_green_paths += outcomes[i].tested_green_paths;
2404
+ }
2405
+ }
2406
+ return result;
2407
+ }
2408
+
2409
+ SimplificationResult find_simplification_parallel_bruteforce(
2410
+ const PDCode& code,
2411
+ const Diagram& diagram,
2412
+ const DualGraph& base_graph,
2413
+ const std::vector<std::vector<Endpoint>>& red_lines,
2414
+ const SimplifierOptions& options,
2415
+ const std::string& path_search_mode,
2416
+ int worker_count) {
2417
+ std::vector<RedPathSearchOutcome> outcomes(red_lines.size());
2418
+ std::atomic<int> next_index(0);
2419
+ std::atomic<int> best_found_index(static_cast<int>(red_lines.size()));
2420
+ std::atomic<bool> failed(false);
2421
+ std::exception_ptr first_exception;
2422
+ std::mutex exception_mutex;
2423
+
2424
+ auto worker = [&]() {
2425
+ while (!failed.load(std::memory_order_relaxed)) {
2426
+ const int index = next_index.fetch_add(1, std::memory_order_relaxed);
2427
+ if (index >= static_cast<int>(red_lines.size())) {
2428
+ break;
2429
+ }
2430
+ if (should_skip_parallel_red_path(index, &best_found_index)) {
2431
+ outcomes[index].skipped = true;
2432
+ continue;
2433
+ }
2434
+ try {
2435
+ RedPathSearchOutcome outcome = search_single_red_path(
2436
+ code,
2437
+ diagram,
2438
+ base_graph,
2439
+ red_lines[index],
2440
+ options,
2441
+ path_search_mode,
2442
+ index,
2443
+ &best_found_index);
2444
+ if (outcome.found) {
2445
+ record_parallel_found_index(index, &best_found_index);
2446
+ }
2447
+ outcomes[index] = std::move(outcome);
2448
+ } catch (...) {
2449
+ failed.store(true, std::memory_order_relaxed);
2450
+ std::lock_guard<std::mutex> lock(exception_mutex);
2451
+ if (!first_exception) {
2452
+ first_exception = std::current_exception();
2453
+ }
2454
+ break;
2455
+ }
2456
+ }
2457
+ };
2458
+
2459
+ std::vector<std::thread> workers;
2460
+ workers.reserve(static_cast<std::size_t>(worker_count));
2461
+ for (int i = 0; i < worker_count; ++i) {
2462
+ workers.emplace_back(worker);
2463
+ }
2464
+ for (std::thread& thread : workers) {
2465
+ thread.join();
2466
+ }
2467
+ if (first_exception) {
2468
+ std::rethrow_exception(first_exception);
2469
+ }
2470
+
2471
+ return merge_red_path_outcomes(outcomes, path_search_mode);
2472
+ }
2473
+
2474
+ } // namespace
2475
+
1829
2476
  SimplificationResult find_simplification(
1830
2477
  const PDCode& code,
1831
2478
  const SimplifierOptions& options) {
1832
2479
  SimplificationResult result;
1833
- if (options.max_paths == -1 && !options.ban_heuristic) {
1834
- result.path_search_mode = "heuristic";
1835
- } else if (options.max_paths == -1) {
1836
- result.path_search_mode = "bruteforce";
1837
- } else {
1838
- result.path_search_mode = "bounded";
1839
- }
2480
+ result.path_search_mode = search_mode_for_options(options);
1840
2481
  Diagram diagram(code);
1841
- DualGraph graph(diagram);
2482
+ DualGraph base_graph(diagram);
1842
2483
  const auto red_lines = possible_red_lines(diagram);
2484
+ const bool brute_force_mode = options.max_paths == -1 && options.ban_heuristic;
2485
+ const int worker_count = brute_force_mode
2486
+ ? selected_bruteforce_worker_count(
2487
+ options.max_threads,
2488
+ static_cast<int>(red_lines.size()))
2489
+ : 1;
2490
+ if (worker_count > 1) {
2491
+ return find_simplification_parallel_bruteforce(
2492
+ code,
2493
+ diagram,
2494
+ base_graph,
2495
+ red_lines,
2496
+ options,
2497
+ result.path_search_mode,
2498
+ worker_count);
2499
+ }
1843
2500
 
1844
2501
  for (const auto& red_path : red_lines) {
1845
2502
  ++result.tested_red_paths;
1846
- reset_weights(graph);
1847
-
1848
- const Endpoint start = red_path.front();
1849
- const Endpoint end = red_path.back();
1850
- const int start_face = graph.edge_to_face[endpoint_key(start)];
1851
- const int start_opposite_face = graph.edge_to_face[endpoint_key(diagram.opposite(start))];
1852
- const int end_face = graph.edge_to_face[endpoint_key(end)];
1853
- const int end_opposite_face = graph.edge_to_face[endpoint_key(diagram.opposite(end))];
1854
- const std::array<int, 2> sources{start_face, start_opposite_face};
1855
- const std::array<int, 2> destinations{end_face, end_opposite_face};
1856
-
1857
- for (std::size_t i = 1; i + 1 < red_path.size(); ++i) {
1858
- const Endpoint endpoint = red_path[i];
1859
- const int right_region = graph.edge_to_face[endpoint_key(endpoint)];
1860
- const int left_region = graph.edge_to_face[endpoint_key(diagram.opposite(endpoint))];
1861
- if (GraphEdge* edge = graph.mutable_edge(right_region, left_region)) {
1862
- edge->weight = kBlockedWeight;
1863
- }
1864
- }
1865
-
1866
- std::vector<std::vector<int>> paths;
1867
- const int cutoff = static_cast<int>(red_path.size()) - 1;
1868
- for (int source : sources) {
1869
- for (int destination : destinations) {
1870
- std::vector<std::vector<int>> found_paths;
1871
- if (options.max_paths == -1 && !options.ban_heuristic) {
1872
- found_paths = collect_heuristic_paths(graph, source, destination, cutoff);
1873
- } else {
1874
- found_paths = collect_simple_paths(graph, source, destination, cutoff, options.max_paths);
1875
- }
1876
- paths.insert(paths.end(), found_paths.begin(), found_paths.end());
1877
- if (options.max_paths != -1 && static_cast<int>(paths.size()) > options.max_paths) {
1878
- break;
1879
- }
1880
- }
2503
+ const RedPathSearchOutcome outcome = search_single_red_path(
2504
+ code,
2505
+ diagram,
2506
+ base_graph,
2507
+ red_path,
2508
+ options,
2509
+ result.path_search_mode,
2510
+ -1,
2511
+ nullptr);
2512
+ result.tested_green_paths += outcome.tested_green_paths;
2513
+ if (outcome.found) {
2514
+ SimplificationResult found = outcome.witness;
2515
+ found.path_search_mode = result.path_search_mode;
2516
+ found.tested_red_paths = result.tested_red_paths;
2517
+ found.tested_green_paths = result.tested_green_paths;
2518
+ return found;
1881
2519
  }
2520
+ }
1882
2521
 
1883
- for (const auto& green_path : paths) {
1884
- ++result.tested_green_paths;
1885
- if (green_path.size() >= red_path.size()) {
1886
- continue;
2522
+ return result;
2523
+ }
2524
+
2525
+ ReductionResult reduce_pd_code(
2526
+ const PDCode& code,
2527
+ std::size_t known_crossingless_components,
2528
+ const SimplifierOptions& options,
2529
+ int reduction_round) {
2530
+ {
2531
+ std::ostringstream message;
2532
+ message << "start input_crossings=" << code.size()
2533
+ << " known_crossingless_components=" << known_crossingless_components
2534
+ << " reduction_round=" << reduction_round
2535
+ << " max_paths=" << options.max_paths
2536
+ << " max_thread=" << options.max_threads
2537
+ << " heuristic=" << (options.ban_heuristic ? "off" : "on");
2538
+ emit_progress(options, message.str());
2539
+ }
2540
+
2541
+ const PDSimplificationResult prepared = simplify_pd_code(code, known_crossingless_components);
2542
+ ReductionResult output;
2543
+ output.code = prepared.code;
2544
+ output.crossingless_components = prepared.crossingless_components;
2545
+ output.reidemeister_i_moves = prepared.reidemeister_i_moves;
2546
+ output.nugatory_crossing_moves = prepared.nugatory_crossing_moves;
2547
+ {
2548
+ std::ostringstream message;
2549
+ message << "pre_simplify input_crossings=" << code.size()
2550
+ << " output_crossings=" << output.code.size()
2551
+ << " crossingless_components=" << output.crossingless_components
2552
+ << " r1_moves=" << prepared.reidemeister_i_moves
2553
+ << " nugatory_moves=" << prepared.nugatory_crossing_moves;
2554
+ emit_progress(options, message.str());
2555
+ }
2556
+
2557
+ while (reduction_round < 0 || output.mid_simplification_rounds < reduction_round) {
2558
+ SimplifierOptions search_options = options;
2559
+ search_options.require_applicable = true;
2560
+ const int round = output.mid_simplification_rounds + 1;
2561
+ {
2562
+ std::ostringstream message;
2563
+ message << "round " << round
2564
+ << " search_start crossings=" << output.code.size()
2565
+ << " mode=" << search_mode_for_options(search_options)
2566
+ << " max_thread=" << search_options.max_threads;
2567
+ emit_progress(options, message.str());
2568
+ }
2569
+ SimplificationResult search = find_simplification(output.code, search_options);
2570
+ output.tested_red_paths += search.tested_red_paths;
2571
+ output.tested_green_paths += search.tested_green_paths;
2572
+ output.last_path_search_mode = search.path_search_mode;
2573
+ {
2574
+ std::ostringstream message;
2575
+ message << "round " << round
2576
+ << " search_done found=" << (search.found ? "yes" : "no")
2577
+ << " mode=" << search.path_search_mode
2578
+ << " tested_red=" << search.tested_red_paths
2579
+ << " tested_green=" << search.tested_green_paths;
2580
+ emit_progress(options, message.str());
2581
+ }
2582
+
2583
+ if (!search.found && reduction_round < 0 &&
2584
+ options.max_paths == -1 && !options.ban_heuristic) {
2585
+ SimplifierOptions brute_options = options;
2586
+ brute_options.max_paths = -1;
2587
+ brute_options.ban_heuristic = true;
2588
+ brute_options.require_applicable = true;
2589
+ {
2590
+ std::ostringstream message;
2591
+ message << "round " << round
2592
+ << " brute_fallback_start crossings=" << output.code.size()
2593
+ << " max_thread=" << brute_options.max_threads;
2594
+ emit_progress(options, message.str());
2595
+ }
2596
+ SimplificationResult brute = find_simplification(output.code, brute_options);
2597
+ output.tested_red_paths += brute.tested_red_paths;
2598
+ output.tested_green_paths += brute.tested_green_paths;
2599
+ output.last_path_search_mode = brute.path_search_mode;
2600
+ {
2601
+ std::ostringstream message;
2602
+ message << "round " << round
2603
+ << " brute_fallback_done found=" << (brute.found ? "yes" : "no")
2604
+ << " tested_red=" << brute.tested_red_paths
2605
+ << " tested_green=" << brute.tested_green_paths;
2606
+ emit_progress(options, message.str());
1887
2607
  }
1888
- if (do_check(diagram, graph, red_path, green_path, Direction::Left, result)) {
1889
- return result;
2608
+ if (brute.found) {
2609
+ ++output.heuristic_failover_rounds;
2610
+ search = std::move(brute);
1890
2611
  }
1891
- if (do_check(diagram, graph, red_path, green_path, Direction::Right, result)) {
1892
- return result;
2612
+ }
2613
+
2614
+ if (!search.found) {
2615
+ {
2616
+ std::ostringstream message;
2617
+ message << "round " << round
2618
+ << " stop_no_path crossings=" << output.code.size();
2619
+ emit_progress(options, message.str());
1893
2620
  }
2621
+ break;
1894
2622
  }
1895
- }
1896
2623
 
1897
- return result;
2624
+ const std::size_t before_apply_crossings = output.code.size();
2625
+ const MidSimplificationApplyResult applied =
2626
+ apply_simplification_witness(output.code, search, output.crossingless_components);
2627
+ ++output.mid_simplification_rounds;
2628
+ const PDSimplificationResult simplified =
2629
+ simplify_pd_code(applied.code, applied.crossingless_components);
2630
+ output.code = simplified.code;
2631
+ output.crossingless_components = simplified.crossingless_components;
2632
+ output.reidemeister_i_moves += simplified.reidemeister_i_moves;
2633
+ output.nugatory_crossing_moves += simplified.nugatory_crossing_moves;
2634
+ {
2635
+ std::ostringstream message;
2636
+ message << "round " << round
2637
+ << " applied crossings=" << before_apply_crossings
2638
+ << " -> " << applied.code.size()
2639
+ << " -> " << output.code.size()
2640
+ << " crossingless_components=" << output.crossingless_components
2641
+ << " r1_moves=" << simplified.reidemeister_i_moves
2642
+ << " nugatory_moves=" << simplified.nugatory_crossing_moves;
2643
+ emit_progress(options, message.str());
2644
+ }
2645
+ }
2646
+
2647
+ output.stopped_by_round_limit =
2648
+ reduction_round >= 0 && output.mid_simplification_rounds >= reduction_round;
2649
+ {
2650
+ std::ostringstream message;
2651
+ message << "done final_crossings=" << output.code.size()
2652
+ << " crossingless_components=" << output.crossingless_components
2653
+ << " mid_rounds=" << output.mid_simplification_rounds
2654
+ << " heuristic_failover_rounds=" << output.heuristic_failover_rounds
2655
+ << " stopped_by_round_limit="
2656
+ << (output.stopped_by_round_limit ? "yes" : "no");
2657
+ emit_progress(options, message.str());
2658
+ }
2659
+ return output;
1898
2660
  }
1899
2661
 
1900
2662
  std::ostream& operator<<(std::ostream& out, const Endpoint& endpoint) {