cpp-pd-code-simplify-interface 0.1.3__tar.gz → 0.1.4__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cpp-pd-code-simplify-interface
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: Python interface for cpp-pd-code-simplify with runtime C++ compilation.
5
5
  License-Expression: MIT
6
6
  Author: GGN_2015
@@ -50,6 +50,7 @@ struct GreenCrossing {
50
50
 
51
51
  struct SimplifierOptions {
52
52
  int max_paths = -1;
53
+ int max_threads = -1;
53
54
  bool ban_heuristic = false;
54
55
  bool require_applicable = false;
55
56
  bool verbose = false;
@@ -112,6 +112,7 @@ char* pdcode_simplify_run_json(
112
112
  int max_paths,
113
113
  int ban_heuristic,
114
114
  int reduction_round,
115
+ int max_thread,
115
116
  int verbose,
116
117
  unsigned long long known_crossingless_components,
117
118
  const int* removed_crossings,
@@ -124,6 +125,7 @@ char* pdcode_simplify_run_json(
124
125
  const std::string text(pd_text);
125
126
  pdcode_simplify::SimplifierOptions options;
126
127
  options.max_paths = max_paths;
128
+ options.max_threads = max_thread;
127
129
  options.ban_heuristic = ban_heuristic != 0;
128
130
  options.verbose = verbose != 0;
129
131
  options.progress = [](const std::string& message) {
@@ -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,12 +1002,6 @@ 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
- }
1006
-
1007
1005
  std::vector<int> heuristic_distances_to_target(
1008
1006
  const DualGraph& graph,
1009
1007
  int target,
@@ -1504,9 +1502,27 @@ private:
1504
1502
 
1505
1503
  std::map<std::pair<int, int>, std::string> green_crossing_levels(
1506
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
+
1507
1510
  std::map<std::pair<int, int>, std::string> levels;
1508
1511
  for (const GreenCrossing& crossing : result.green_crossings) {
1509
- levels[std::make_pair(crossing.from_face, crossing.to_face)] = crossing.strand_level;
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
+ }
1510
1526
  }
1511
1527
  return levels;
1512
1528
  }
@@ -1518,6 +1534,68 @@ void clear_witness(SimplificationResult& result) {
1518
1534
  result.green_crossings.clear();
1519
1535
  }
1520
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
+
1521
1599
  } // namespace
1522
1600
 
1523
1601
  MidSimplificationApplyResult apply_simplification_witness(
@@ -1637,14 +1715,14 @@ MidSimplificationApplyResult apply_simplification_witness(
1637
1715
  int green_out_pos = -1;
1638
1716
  if (crossed.level == "over") {
1639
1717
  existing_from_pos = 0;
1640
- green_in_pos = 1;
1641
1718
  existing_to_pos = 2;
1642
- green_out_pos = 3;
1719
+ green_in_pos = 3;
1720
+ green_out_pos = 1;
1643
1721
  } else if (crossed.level == "under") {
1722
+ existing_from_pos = 1;
1644
1723
  green_in_pos = 0;
1645
- existing_to_pos = 1;
1646
1724
  green_out_pos = 2;
1647
- existing_from_pos = 3;
1725
+ existing_to_pos = 3;
1648
1726
  } else {
1649
1727
  throw std::invalid_argument("Unknown green crossing strand level: " + crossed.level);
1650
1728
  }
@@ -1721,6 +1799,9 @@ MidSimplificationApplyResult apply_simplification_witness(
1721
1799
  const std::size_t total_components =
1722
1800
  analyze_components(code, known_crossingless_components).total_components();
1723
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
+ }
1724
1805
  const std::size_t crossing_components = analyze_components(output).components_with_crossings();
1725
1806
 
1726
1807
  MidSimplificationApplyResult applied;
@@ -2140,78 +2221,302 @@ PDSimplificationResult simplify_pd_code(
2140
2221
  return result;
2141
2222
  }
2142
2223
 
2143
- SimplificationResult find_simplification(
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(
2144
2283
  const PDCode& code,
2145
- const SimplifierOptions& options) {
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) {
2146
2375
  SimplificationResult result;
2147
- if (options.max_paths == -1 && !options.ban_heuristic) {
2148
- result.path_search_mode = "heuristic";
2149
- } else if (options.max_paths == -1) {
2150
- result.path_search_mode = "bruteforce";
2151
- } else {
2152
- result.path_search_mode = "bounded";
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
+ }
2153
2384
  }
2154
- Diagram diagram(code);
2155
- DualGraph graph(diagram);
2156
- const auto red_lines = possible_red_lines(diagram);
2157
2385
 
2158
- for (const auto& red_path : red_lines) {
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
+ }
2159
2393
  ++result.tested_red_paths;
2160
- reset_weights(graph);
2161
-
2162
- const Endpoint start = red_path.front();
2163
- const Endpoint end = red_path.back();
2164
- const int start_face = graph.edge_to_face[endpoint_key(start)];
2165
- const int start_opposite_face = graph.edge_to_face[endpoint_key(diagram.opposite(start))];
2166
- const int end_face = graph.edge_to_face[endpoint_key(end)];
2167
- const int end_opposite_face = graph.edge_to_face[endpoint_key(diagram.opposite(end))];
2168
- const std::array<int, 2> sources{start_face, start_opposite_face};
2169
- const std::array<int, 2> destinations{end_face, end_opposite_face};
2170
-
2171
- for (std::size_t i = 1; i + 1 < red_path.size(); ++i) {
2172
- const Endpoint endpoint = red_path[i];
2173
- const int right_region = graph.edge_to_face[endpoint_key(endpoint)];
2174
- const int left_region = graph.edge_to_face[endpoint_key(diagram.opposite(endpoint))];
2175
- if (GraphEdge* edge = graph.mutable_edge(right_region, left_region)) {
2176
- edge->weight = kBlockedWeight;
2177
- }
2178
- }
2179
-
2180
- std::vector<std::vector<int>> paths;
2181
- const int cutoff = static_cast<int>(red_path.size()) - 1;
2182
- for (int source : sources) {
2183
- for (int destination : destinations) {
2184
- std::vector<std::vector<int>> found_paths;
2185
- if (options.max_paths == -1 && !options.ban_heuristic) {
2186
- found_paths = collect_heuristic_paths(graph, source, destination, cutoff);
2187
- } else {
2188
- found_paths = collect_simple_paths(graph, source, destination, cutoff, options.max_paths);
2189
- }
2190
- paths.insert(paths.end(), found_paths.begin(), found_paths.end());
2191
- if (options.max_paths != -1 && static_cast<int>(paths.size()) > options.max_paths) {
2192
- break;
2193
- }
2194
- }
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;
2195
2404
  }
2405
+ }
2406
+ return result;
2407
+ }
2196
2408
 
2197
- for (const auto& green_path : paths) {
2198
- ++result.tested_green_paths;
2199
- if (green_path.size() >= red_path.size()) {
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;
2200
2432
  continue;
2201
2433
  }
2202
- if (do_check(diagram, graph, red_path, green_path, Direction::Left, result)) {
2203
- if (!options.require_applicable || witness_has_applicable_surgery(code, result)) {
2204
- return result;
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);
2205
2446
  }
2206
- clear_witness(result);
2207
- }
2208
- if (do_check(diagram, graph, red_path, green_path, Direction::Right, result)) {
2209
- if (!options.require_applicable || witness_has_applicable_surgery(code, result)) {
2210
- return result;
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();
2211
2453
  }
2212
- clear_witness(result);
2454
+ break;
2213
2455
  }
2214
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
+
2476
+ SimplificationResult find_simplification(
2477
+ const PDCode& code,
2478
+ const SimplifierOptions& options) {
2479
+ SimplificationResult result;
2480
+ result.path_search_mode = search_mode_for_options(options);
2481
+ Diagram diagram(code);
2482
+ DualGraph base_graph(diagram);
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
+ }
2500
+
2501
+ for (const auto& red_path : red_lines) {
2502
+ ++result.tested_red_paths;
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;
2519
+ }
2215
2520
  }
2216
2521
 
2217
2522
  return result;
@@ -2228,6 +2533,7 @@ ReductionResult reduce_pd_code(
2228
2533
  << " known_crossingless_components=" << known_crossingless_components
2229
2534
  << " reduction_round=" << reduction_round
2230
2535
  << " max_paths=" << options.max_paths
2536
+ << " max_thread=" << options.max_threads
2231
2537
  << " heuristic=" << (options.ban_heuristic ? "off" : "on");
2232
2538
  emit_progress(options, message.str());
2233
2539
  }
@@ -2256,7 +2562,8 @@ ReductionResult reduce_pd_code(
2256
2562
  std::ostringstream message;
2257
2563
  message << "round " << round
2258
2564
  << " search_start crossings=" << output.code.size()
2259
- << " mode=" << search_mode_for_options(search_options);
2565
+ << " mode=" << search_mode_for_options(search_options)
2566
+ << " max_thread=" << search_options.max_threads;
2260
2567
  emit_progress(options, message.str());
2261
2568
  }
2262
2569
  SimplificationResult search = find_simplification(output.code, search_options);
@@ -2282,7 +2589,8 @@ ReductionResult reduce_pd_code(
2282
2589
  {
2283
2590
  std::ostringstream message;
2284
2591
  message << "round " << round
2285
- << " brute_fallback_start crossings=" << output.code.size();
2592
+ << " brute_fallback_start crossings=" << output.code.size()
2593
+ << " max_thread=" << brute_options.max_threads;
2286
2594
  emit_progress(options, message.str());
2287
2595
  }
2288
2596
  SimplificationResult brute = find_simplification(output.code, brute_options);
@@ -404,6 +404,7 @@ def _load_library() -> ctypes.CDLL:
404
404
  ctypes.c_int,
405
405
  ctypes.c_int,
406
406
  ctypes.c_int,
407
+ ctypes.c_int,
407
408
  ctypes.c_ulonglong,
408
409
  ctypes.POINTER(ctypes.c_int),
409
410
  ctypes.c_ulonglong,
@@ -422,12 +423,15 @@ def _run_one(
422
423
  max_paths: int = -1,
423
424
  ban_heuristic: bool = False,
424
425
  reduction_round: int = -1,
426
+ max_thread: int = -1,
425
427
  verbose: bool = False,
426
428
  known_crossingless_components: int = 0,
427
429
  remove_crossings: Optional[Sequence[int]] = None,
428
430
  ) -> dict[str, Any]:
429
431
  if reduction_round < -1:
430
432
  raise ValueError("reduction_round must be -1 or a non-negative integer")
433
+ if max_thread < -1 or max_thread == 0:
434
+ raise ValueError("max_thread must be -1 or a positive integer")
431
435
  library = _load_library()
432
436
  removed_count = 0 if remove_crossings is None else len(remove_crossings)
433
437
  removed_array = None
@@ -439,6 +443,7 @@ def _run_one(
439
443
  int(max_paths),
440
444
  1 if ban_heuristic else 0,
441
445
  int(reduction_round),
446
+ int(max_thread),
442
447
  1 if verbose else 0,
443
448
  int(known_crossingless_components),
444
449
  removed_array,
@@ -466,6 +471,7 @@ def simplify(
466
471
  max_paths: int = -1,
467
472
  ban_heuristic: bool = False,
468
473
  reduction_round: int = -1,
474
+ max_thread: int = -1,
469
475
  verbose: bool = False,
470
476
  known_crossingless_components: int = 0,
471
477
  remove_crossings: Optional[Sequence[int]] = None,
@@ -477,6 +483,7 @@ def simplify(
477
483
  max_paths=max_paths,
478
484
  ban_heuristic=ban_heuristic,
479
485
  reduction_round=reduction_round,
486
+ max_thread=max_thread,
480
487
  verbose=verbose,
481
488
  known_crossingless_components=known_crossingless_components,
482
489
  remove_crossings=remove_crossings,
@@ -489,6 +496,7 @@ def simplify_many(
489
496
  max_paths: int = -1,
490
497
  ban_heuristic: bool = False,
491
498
  reduction_round: int = -1,
499
+ max_thread: int = -1,
492
500
  verbose: bool = False,
493
501
  known_crossingless_components: int = 0,
494
502
  remove_crossings: Optional[Sequence[int]] = None,
@@ -501,6 +509,7 @@ def simplify_many(
501
509
  max_paths=max_paths,
502
510
  ban_heuristic=ban_heuristic,
503
511
  reduction_round=reduction_round,
512
+ max_thread=max_thread,
504
513
  verbose=verbose,
505
514
  known_crossingless_components=known_crossingless_components,
506
515
  remove_crossings=remove_crossings,
@@ -517,12 +526,15 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
517
526
  parser.add_argument("--max-paths", type=int, default=-1)
518
527
  parser.add_argument("--ban-heuristic", action="store_true")
519
528
  parser.add_argument("--reduction-round", type=int, default=-1)
529
+ parser.add_argument("--max-thread", type=int, default=-1)
520
530
  parser.add_argument("--verbose", action="store_true")
521
531
  parser.add_argument("--known-crossingless-components", type=int, default=0)
522
532
  parser.add_argument("--remove-crossings", help="comma-separated zero-based crossing indices")
523
533
  args = parser.parse_args(argv)
524
534
  if args.reduction_round < -1:
525
535
  parser.error("--reduction-round must be -1 or a non-negative integer")
536
+ if args.max_thread < -1 or args.max_thread == 0:
537
+ parser.error("--max-thread must be -1 or a positive integer")
526
538
  if args.pd_code and args.pd_code_option:
527
539
  parser.error("pass either a positional PD code or --pd-code, not both")
528
540
  pd_code_text = args.pd_code_option or args.pd_code
@@ -546,6 +558,7 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
546
558
  max_paths=args.max_paths,
547
559
  ban_heuristic=args.ban_heuristic,
548
560
  reduction_round=args.reduction_round,
561
+ max_thread=args.max_thread,
549
562
  verbose=args.verbose,
550
563
  known_crossingless_components=args.known_crossingless_components,
551
564
  remove_crossings=remove_crossings,
@@ -566,6 +579,7 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
566
579
  max_paths=args.max_paths,
567
580
  ban_heuristic=args.ban_heuristic,
568
581
  reduction_round=args.reduction_round,
582
+ max_thread=args.max_thread,
569
583
  verbose=args.verbose,
570
584
  known_crossingless_components=args.known_crossingless_components,
571
585
  remove_crossings=remove_crossings,
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cpp-pd-code-simplify-interface"
3
- version = "0.1.3"
3
+ version = "0.1.4"
4
4
  description = "Python interface for cpp-pd-code-simplify with runtime C++ compilation."
5
5
  authors = [
6
6
  {name = "GGN_2015", email = "neko@jlulug.org"}