-
Notifications
You must be signed in to change notification settings - Fork 219
Generate Gomory cuts at the nodes and add them into the cut pool. #1684
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,6 +38,7 @@ | |
| #include <functional> | ||
| #include <future> | ||
| #include <memory> | ||
| #include <optional> | ||
| #include <vector> | ||
|
|
||
| namespace cuopt::mathematical_optimization::mip { | ||
|
|
@@ -59,6 +60,7 @@ enum class mip_status_t { | |
| NUMERICAL = 5, // The solver encountered a numerical error | ||
| UNSET = 6, // The status is not set | ||
| WORK_LIMIT = 7, // The solver reached a deterministic work limit | ||
| RESTART = 8, // The solver triggered a restart of the B&B tree | ||
| }; | ||
|
|
||
| template <typename i_t, typename f_t> | ||
|
|
@@ -178,6 +180,16 @@ class branch_and_bound_t { | |
| std::vector<i_t> new_slacks_; | ||
| std::vector<simplex::variable_type_t> var_types_; | ||
|
|
||
| // Shared global cut pool: both the root cut passes and the per-node cut passes append to it. | ||
| // Constructed in solve() once the root LP dimensions are known. The pool self-locks on add_cut. | ||
| std::optional<cut_pool_t<i_t, f_t>> global_cut_pool_; | ||
| // Shared cut generator bound to global_cut_pool_, reused by the root loop and every worker's | ||
| // node cut generation. The Gomory path uses only local scratch (no mutable generator state), | ||
| // so a single instance can serve all workers concurrently. | ||
| std::optional<cut_generation_t<i_t, f_t>> cut_generation_; | ||
| // True when every integer variable is binary ([0,1]); node cut generation is gated on this. | ||
| bool is_pure_binary_{false}; | ||
|
|
||
| // Variable locks (see definition 3.3 from T. Achterberg, “Constraint Integer Programming,” | ||
| // PhD, Technischen Universität Berlin, Berlin, 2007. doi: 10.14279/depositonce-1634). | ||
| // Here we assume that the constraints are in the form `Ax = b, l <= x <= u`. | ||
|
|
@@ -232,6 +244,10 @@ class branch_and_bound_t { | |
| bool enable_concurrent_lp_root_solve_{false}; | ||
| std::atomic<int> root_concurrent_halt_{0}; | ||
| std::atomic<int> node_concurrent_halt_{0}; | ||
| // Set to 1 to signal all B&B workers to stop so the tree can be restarted. Owned here (unlike the | ||
| // reference PR which threads a pointer through the constructor) since the B&B taskgroup is the only | ||
| // consumer. | ||
| std::atomic<int> restart_concurrent_halt_{0}; | ||
|
Comment on lines
+247
to
+250
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Check for any reader of restart_concurrent_halt_.
rg -nP -C3 '\brestart_concurrent_halt_\b' cppRepository: NVIDIA/cuopt Length of output: 2125 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- relevant declarations and uses ---'
rg -nP -C5 '\b(node_concurrent_halt_|restart_concurrent_halt_|concurrent_halt|solver_status_)\b' cpp/src/branch_and_bound/branch_and_bound.hpp cpp/src/branch_and_bound/branch_and_bound.cpp
printf '%s\n' '--- taskgroup and worker setup ---'
rg -nP -C8 'taskgroup|concurrent_halt|node_concurrent_halt_' cpp/src/branch_and_bound/branch_and_bound.cppRepository: NVIDIA/cuopt Length of output: 49065 Remove or consume The flag is only assigned and reset. No code reads it. Wire it into the restart-stop path, or remove it and rely on 🤖 Prompt for AI Agents |
||
| bool is_root_solution_set{false}; | ||
|
|
||
| // Pseudocosts | ||
|
|
@@ -261,6 +277,9 @@ class branch_and_bound_t { | |
| omp_atomic_t<f_t> lower_bound_numerical_; | ||
| std::function<void(f_t)> user_bound_callback_; | ||
|
|
||
| // Number of restarts performed so far in the current solve(). | ||
| i_t restart_count_{0}; | ||
|
|
||
| void print_table_header(); | ||
| void report_heuristic(f_t obj); | ||
| void report(char symbol, | ||
|
|
@@ -293,7 +312,9 @@ class branch_and_bound_t { | |
| f_t& last_objective, | ||
| f_t root_relax_objective, | ||
| i_t& cut_pool_size, | ||
| const std::vector<f_t>& saved_solution); | ||
| const std::vector<f_t>& saved_solution, | ||
| bool generate_new = true, | ||
| i_t* num_cuts_added_out = nullptr); | ||
|
|
||
| // Set the solution when found at the root node | ||
| void set_solution_at_root(simplex::mip_solution_t<i_t, f_t>& solution, | ||
|
|
@@ -313,6 +334,10 @@ class branch_and_bound_t { | |
| // Repairs low-quality solutions from the heuristics, if it is applicable. | ||
| void repair_heuristic_solutions(); | ||
|
|
||
| // Decide whether to restart the B&B tree based on how much larger the estimated full tree is | ||
| // compared to the part explored so far. | ||
| bool should_restart(f_t current_abs_gap); | ||
|
|
||
| // Launch a new diving worker from a given best-first worker. | ||
| bool launch_diving_worker(bfs_worker_t<i_t, f_t>* bfs_worker); | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,6 +19,7 @@ | |
| #include <cstdio> | ||
| #include <cstdlib> | ||
| #include <limits> | ||
| #include <mutex> | ||
| #include <stdexcept> | ||
| #include <tuple> | ||
| #include <unordered_set> | ||
|
|
@@ -1164,12 +1165,62 @@ void cut_pool_t<i_t, f_t>::add_cut(cut_type_t cut_type, const inequality_t<i_t, | |
| return; | ||
| } | ||
|
|
||
| // Serialize appends: the global pool is shared by concurrent per-node cut passes. | ||
| std::lock_guard<omp_mutex_t> lock(mutex_); | ||
|
|
||
| cut_storage_.append_row(cut_squeezed.vector); | ||
| rhs_storage_.push_back(cut_squeezed.rhs); | ||
| cut_type_.push_back(cut_type); | ||
| cut_age_.push_back(0); | ||
| } | ||
|
|
||
| template <typename i_t, typename f_t> | ||
| i_t cut_pool_t<i_t, f_t>::verify_solution(const std::vector<f_t>& x, f_t tolerance) const | ||
| { | ||
| i_t num_violated = 0; | ||
| f_t max_violation = 0.0; | ||
| const i_t num_cuts = cut_storage_.m; | ||
| for (i_t row = 0; row < num_cuts; row++) { | ||
| const i_t row_start = cut_storage_.row_start[row]; | ||
| const i_t row_end = cut_storage_.row_start[row + 1]; | ||
| f_t cut_x = 0.0; | ||
| for (i_t p = row_start; p < row_end; p++) { | ||
| const i_t j = cut_storage_.j[p]; | ||
| const f_t cut_coeff = cut_storage_.x[p]; | ||
| cut_x += cut_coeff * x[j]; | ||
| } | ||
| // Cut is cut'*x >= rhs, so violation is rhs - cut'*x (positive means the solution violates it). | ||
| const f_t violation = rhs_storage_[row] - cut_x; | ||
| if (violation > tolerance) { | ||
| num_violated++; | ||
| max_violation = std::max(max_violation, violation); | ||
| settings_.log.printf( | ||
| "Cut pool verification: cut %d (type %d) violated by optimal solution: cut'x=%.10e < " | ||
| "rhs=%.10e (violation %.3e > tol %.1e)\n", | ||
| row, | ||
| static_cast<int>(cut_type_[row]), | ||
| cut_x, | ||
| rhs_storage_[row], | ||
| violation, | ||
| tolerance); | ||
| } | ||
| } | ||
| if (num_violated > 0) { | ||
| settings_.log.printf( | ||
| "Cut pool verification FAILED: %d of %d cuts violated by the optimal solution (max violation " | ||
| "%.3e). Some generated cut is not globally valid.\n", | ||
| num_violated, | ||
| num_cuts, | ||
| max_violation); | ||
| } else { | ||
| settings_.log.printf( | ||
| "Cut pool verification passed: optimal solution satisfies all %d cuts within tol %.1e\n", | ||
| num_cuts, | ||
| tolerance); | ||
| } | ||
| return num_violated; | ||
| } | ||
|
Comment on lines
+1177
to
+1222
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win Guard against a short
🛡️ Proposed guard i_t cut_pool_t<i_t, f_t>::verify_solution(const std::vector<f_t>& x, f_t tolerance) const
{
+ if (static_cast<i_t>(x.size()) < original_vars_) {
+ settings_.log.printf(
+ "Cut pool verification skipped: solution has %zu entries, pool indexes %d variables\n",
+ x.size(),
+ original_vars_);
+ return 0;
+ }
i_t num_violated = 0;🤖 Prompt for AI Agents |
||
|
|
||
| template <typename i_t, typename f_t> | ||
| f_t cut_pool_t<i_t, f_t>::cut_distance(i_t row, | ||
| const std::vector<f_t>& x, | ||
|
|
@@ -3431,6 +3482,34 @@ bool cut_generation_t<i_t, f_t>::generate_cuts(const lp_problem_t<i_t, f_t>& lp, | |
| return true; | ||
| } | ||
|
|
||
| template <typename i_t, typename f_t> | ||
| bool cut_generation_t<i_t, f_t>::generate_node_cuts( | ||
| const lp_problem_t<i_t, f_t>& lp, | ||
| const simplex_solver_settings_t<i_t, f_t>& settings, | ||
| csr_matrix_t<i_t, f_t>& Arow, | ||
| const std::vector<i_t>& new_slacks, | ||
| const std::vector<simplex::variable_type_t>& var_types, | ||
| simplex::basis_update_mpf_t<i_t, f_t>& basis_update, | ||
| const std::vector<f_t>& xstar, | ||
| const std::vector<i_t>& basic_list, | ||
| const std::vector<i_t>& nonbasic_list, | ||
| f_t start_time) | ||
| { | ||
| // Node cuts are Gomory-only for now. generate_gomory_cuts appends violated cuts to the shared | ||
| // global pool. We only accumulate the count here (reported once per restart); the per-node print | ||
| // is intentionally omitted to avoid flooding the log during B&B. | ||
| const i_t pool_size_before = cut_pool_.pool_size(); | ||
| generate_gomory_cuts( | ||
| lp, settings, Arow, new_slacks, var_types, basis_update, xstar, basic_list, nonbasic_list); | ||
| const i_t pool_size_after = cut_pool_.pool_size(); | ||
| if (pool_size_after > pool_size_before) { | ||
| node_cuts_added_ += pool_size_after - pool_size_before; | ||
| } | ||
|
Comment on lines
+3501
to
+3507
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win Partial locking of
📍 Affects 2 files
🤖 Prompt for AI Agents |
||
| return true; | ||
| } | ||
|
|
||
|
|
||
|
|
||
| template <typename i_t, typename f_t> | ||
| void cut_generation_t<i_t, f_t>::generate_knapsack_cuts( | ||
| const lp_problem_t<i_t, f_t>& lp, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we rename it to generate_node_cuts? The others imply a number and this implies a boolean decision.