From 6f88f6713b04c392652ad92f8b4accd8fcce63a9 Mon Sep 17 00:00:00 2001 From: Christopher Maes Date: Wed, 11 Jun 2025 16:57:12 -0700 Subject: [PATCH 01/51] Don't add extra slacks (artifical variables) for ranged rows --- cpp/src/dual_simplex/presolve.cpp | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/cpp/src/dual_simplex/presolve.cpp b/cpp/src/dual_simplex/presolve.cpp index e87c88b409..81504cf64a 100644 --- a/cpp/src/dual_simplex/presolve.cpp +++ b/cpp/src/dual_simplex/presolve.cpp @@ -459,13 +459,15 @@ i_t find_dependent_rows(lp_problem_t& problem, template i_t add_artifical_variables(lp_problem_t& problem, - std::vector& equality_rows, + const std::vector& range_rows, + const std::vector& equality_rows, std::vector& new_slacks) { const i_t n = problem.num_cols; const i_t m = problem.num_rows; - const i_t num_cols = n + equality_rows.size(); - const i_t nnz = problem.A.col_start[n] + equality_rows.size(); + const i_t num_artificial_vars = equality_rows.size() - range_rows.size(); + const i_t num_cols = n + num_artificial_vars; + i_t nnz = problem.A.col_start[n] + num_artificial_vars; problem.A.col_start.resize(num_cols + 1); problem.A.i.resize(nnz); problem.A.x.resize(nnz); @@ -473,9 +475,17 @@ i_t add_artifical_variables(lp_problem_t& problem, problem.upper.resize(num_cols); problem.objective.resize(num_cols); + std::vector is_range_row(problem.num_rows, false); + for (i_t i : range_rows) { + is_range_row[i] = true; + } + i_t p = problem.A.col_start[n]; i_t j = n; for (i_t i : equality_rows) { + if (is_range_row[i]) { + continue; + } // Add an artifical variable z to the equation a_i^T x == b // This now becomes a_i^T x + z == b, 0 <= z =< 0 problem.A.col_start[j] = p; @@ -491,8 +501,8 @@ i_t add_artifical_variables(lp_problem_t& problem, problem.A.col_start[num_cols] = p; assert(j == num_cols); assert(p == nnz); - constexpr bool verbose = false; - if (verbose) { printf("Added %d artificial variables\n", num_cols - n); } + constexpr bool verbose = true; + if (verbose) { printf("Added %d artificial variables\n", num_artificial_vars); } problem.A.n = num_cols; problem.num_cols = num_cols; return 0; @@ -550,7 +560,7 @@ void convert_user_problem(const user_problem_t& user_problem, if (verbose) { printf("Constraints < %d = %d > %d\n", less_rows, equal_rows, greater_rows); } if (user_problem.num_range_rows > 0) { - if (verbose) { printf("Problem has %d range rows\n", user_problem.num_range_rows); } + if (1 || verbose) { printf("Problem has %d range rows\n", user_problem.num_range_rows); } convert_range_rows( user_problem, row_sense, problem, less_rows, equal_rows, greater_rows, new_slacks); } @@ -579,7 +589,7 @@ void convert_user_problem(const user_problem_t& user_problem, } // Add artifical variables - add_artifical_variables(problem, equality_rows, new_slacks); + add_artifical_variables(problem, user_problem.range_rows, equality_rows, new_slacks); } template From c0f588687ad2bd6a03b2be895e981440fce5b810 Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Mon, 3 Nov 2025 15:14:14 -0800 Subject: [PATCH 02/51] Add callbacks to send root solution from original problem --- cpp/src/dual_simplex/branch_and_bound.cpp | 42 +++++++++++++++++-- cpp/src/dual_simplex/branch_and_bound.hpp | 12 ++++++ .../dual_simplex/simplex_solver_settings.hpp | 1 + cpp/src/mip/diversity/diversity_manager.cu | 23 ++++++++++ cpp/src/mip/diversity/diversity_manager.cuh | 1 + cpp/src/mip/problem/problem.cu | 4 +- cpp/src/mip/problem/problem.cuh | 2 + cpp/src/mip/solver.cu | 8 ++++ 8 files changed, 88 insertions(+), 5 deletions(-) diff --git a/cpp/src/dual_simplex/branch_and_bound.cpp b/cpp/src/dual_simplex/branch_and_bound.cpp index 2ce3ee0b4e..02772f979e 100644 --- a/cpp/src/dual_simplex/branch_and_bound.cpp +++ b/cpp/src/dual_simplex/branch_and_bound.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -1046,12 +1047,45 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut root_relax_soln_.resize(original_lp_.num_rows, original_lp_.num_cols); settings_.log.printf("Solving LP root relaxation\n"); - simplex_solver_settings_t lp_settings = settings_; - lp_settings.inside_mip = 1; - lp_status_t root_status = solve_linear_program_advanced( - original_lp_, stats_.start_time, lp_settings, root_relax_soln_, root_vstatus_, edge_norms_); + // simplex_solver_settings_t lp_settings = settings_; + // lp_settings.inside_mip = 1; + // lp_status_t root_status = solve_linear_program_advanced( + // original_lp_, stats_.start_time, lp_settings, root_relax_soln_, root_vstatus_, edge_norms_); + + // FIXME: Figure out correct status mapping + lp_status_t root_status = lp_status_t::INFEASIBLE; + + // Wait for the root relaxation solution to be set by diversity manager + while (root_relax_soln_.iterations == 0) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + continue; + } stats_.total_lp_iters = root_relax_soln_.iterations; stats_.total_lp_solve_time = toc(stats_.start_time); + + // TODO: Crush the root relaxation solution on converted user problem + std::vector crushed_root_x; + crush_primal_solution( + original_problem_, original_lp_, root_relax_soln_.x, new_slacks_, crushed_root_x); + std::vector crushed_root_dual; + std::vector crushed_root_z; + crush_dual_solution(original_problem_, + original_lp_, + new_slacks_, + root_relax_soln_.y, + root_relax_soln_.z, + crushed_root_dual, + crushed_root_z); + + // TODO: Call crossover on the crushed solution + lp_solution_t crossover_solution(original_lp_.num_rows, original_lp_.num_cols); + std::vector vstatus(original_lp_.num_cols); + crossover_status_t crossover_status = crossover( + original_lp_, settings_, root_relax_soln_, stats_.start_time, crossover_solution, vstatus); + settings_.log.printf("Crossover status: %d\n", crossover_status); + + // TODO: Call dual simplex phase 2 to verify basis + if (root_status == lp_status_t::INFEASIBLE) { settings_.log.printf("MIP Infeasible\n"); // FIXME: rarely dual simplex detects infeasible whereas it is feasible. diff --git a/cpp/src/dual_simplex/branch_and_bound.hpp b/cpp/src/dual_simplex/branch_and_bound.hpp index 23fb9eb7f8..98bc87f70b 100644 --- a/cpp/src/dual_simplex/branch_and_bound.hpp +++ b/cpp/src/dual_simplex/branch_and_bound.hpp @@ -128,6 +128,18 @@ class branch_and_bound_t { // Set an initial guess based on the user_problem. This should be called before solve. void set_initial_guess(const std::vector& user_guess) { guess_ = user_guess; } + // Set the root solution found by PDLP + void set_root_relaxation_solution(const std::vector& primal, + const std::vector& dual, + f_t objective, + i_t iterations) + { + root_relax_soln_.x = primal; + root_relax_soln_.y = dual; + root_objective_ = objective; + root_relax_soln_.iterations = iterations; + } + // Set a solution based on the user problem during the course of the solve void set_new_solution(const std::vector& solution); diff --git a/cpp/src/dual_simplex/simplex_solver_settings.hpp b/cpp/src/dual_simplex/simplex_solver_settings.hpp index 59e6dc7bbd..38a256b42c 100644 --- a/cpp/src/dual_simplex/simplex_solver_settings.hpp +++ b/cpp/src/dual_simplex/simplex_solver_settings.hpp @@ -25,6 +25,7 @@ #include #include #include +#include namespace cuopt::linear_programming::dual_simplex { diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index d8e413a0dd..15d20938ee 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -44,6 +44,7 @@ std::vector recombiner_t::enabled_recombiners; template diversity_manager_t::diversity_manager_t(mip_solver_context_t& context_) : context(context_), + branch_and_bound_ptr(nullptr), problem_ptr(context.problem_ptr), diversity_config(), population("population", @@ -390,6 +391,28 @@ solution_t diversity_manager_t::run_solver() // note to developer, in debug mode the LP run might be too slow and it might cause PDLP not // to bring variables within the bounds } + + // Send PDLP relaxed solution to branch and bound before it solves the root node + if (problem_ptr->set_root_relaxation_solution_callback != nullptr) { + // Copy solution from device to host + std::vector host_primal(lp_optimal_solution.size()); + std::vector host_dual(lp_dual_optimal_solution.size()); + raft::copy(host_primal.data(), + lp_optimal_solution.data(), + lp_optimal_solution.size(), + problem_ptr->handle_ptr->get_stream()); + raft::copy(host_dual.data(), + lp_dual_optimal_solution.data(), + lp_dual_optimal_solution.size(), + problem_ptr->handle_ptr->get_stream()); + problem_ptr->handle_ptr->sync_stream(); + + auto user_obj = problem_ptr->get_user_obj_from_solver_obj(lp_result.get_objective_value()); + auto iterations = lp_result.get_additional_termination_information().number_of_steps_taken; + problem_ptr->set_root_relaxation_solution_callback( + host_primal, host_dual, user_obj, iterations); + } + // in case the pdlp returned var boudns that are out of bounds clamp_within_var_bounds(lp_optimal_solution, problem_ptr, problem_ptr->handle_ptr); } diff --git a/cpp/src/mip/diversity/diversity_manager.cuh b/cpp/src/mip/diversity/diversity_manager.cuh index 5e94b1eccc..d9b2f8f9b3 100644 --- a/cpp/src/mip/diversity/diversity_manager.cuh +++ b/cpp/src/mip/diversity/diversity_manager.cuh @@ -80,6 +80,7 @@ class diversity_manager_t { f_t objective); mip_solver_context_t& context; + dual_simplex::branch_and_bound_t* branch_and_bound_ptr; problem_t* problem_ptr; diversity_config_t diversity_config; population_t population; diff --git a/cpp/src/mip/problem/problem.cu b/cpp/src/mip/problem/problem.cu index 79b39912fc..c6a58032b2 100644 --- a/cpp/src/mip/problem/problem.cu +++ b/cpp/src/mip/problem/problem.cu @@ -150,7 +150,8 @@ problem_t::problem_t( fixing_helpers(n_constraints, n_variables, handle_ptr) { op_problem_cstr_body(problem_); - branch_and_bound_callback = nullptr; + branch_and_bound_callback = nullptr; + set_root_relaxation_solution_callback = nullptr; } template @@ -161,6 +162,7 @@ problem_t::problem_t(const problem_t& problem_) integer_fixed_problem(problem_.integer_fixed_problem), integer_fixed_variable_map(problem_.integer_fixed_variable_map, handle_ptr->get_stream()), branch_and_bound_callback(nullptr), + set_root_relaxation_solution_callback(nullptr), n_variables(problem_.n_variables), n_constraints(problem_.n_constraints), n_binary_vars(problem_.n_binary_vars), diff --git a/cpp/src/mip/problem/problem.cuh b/cpp/src/mip/problem/problem.cuh index f0d044640a..49f81a63be 100644 --- a/cpp/src/mip/problem/problem.cuh +++ b/cpp/src/mip/problem/problem.cuh @@ -217,6 +217,8 @@ class problem_t { rmm::device_uvector integer_fixed_variable_map; std::function&)> branch_and_bound_callback; + std::function&, const std::vector&, f_t, i_t)> + set_root_relaxation_solution_callback; typename mip_solver_settings_t::tolerances_t tolerances{}; i_t n_variables{0}; diff --git a/cpp/src/mip/solver.cu b/cpp/src/mip/solver.cu index 0114882b03..3623afcaab 100644 --- a/cpp/src/mip/solver.cu +++ b/cpp/src/mip/solver.cu @@ -204,12 +204,20 @@ solution_t mip_solver_t::run_solver() // Create the branch and bound object branch_and_bound = std::make_unique>( branch_and_bound_problem, branch_and_bound_settings); + dm.branch_and_bound_ptr = branch_and_bound.get(); // Set the primal heuristics -> branch and bound callback context.problem_ptr->branch_and_bound_callback = std::bind(&dual_simplex::branch_and_bound_t::set_new_solution, branch_and_bound.get(), std::placeholders::_1); + context.problem_ptr->set_root_relaxation_solution_callback = + std::bind(&dual_simplex::branch_and_bound_t::set_root_relaxation_solution, + branch_and_bound.get(), + std::placeholders::_1, + std::placeholders::_2, + std::placeholders::_3, + std::placeholders::_4); // Fork a thread for branch and bound // std::async and std::future allow us to get the return value of bb::solve() From 3cb93e795890105d6e61bb401da7eb4f05b0bb69 Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Wed, 5 Nov 2025 09:55:02 -0800 Subject: [PATCH 03/51] Fix reduced costs and crossover call --- cpp/src/dual_simplex/branch_and_bound.cpp | 15 ++++++++++ cpp/src/dual_simplex/branch_and_bound.hpp | 13 +++++--- cpp/src/mip/diversity/diversity_manager.cu | 35 ++++++++++++++++------ cpp/src/mip/problem/problem.cuh | 3 +- cpp/src/mip/solver.cu | 4 ++- 5 files changed, 55 insertions(+), 15 deletions(-) diff --git a/cpp/src/dual_simplex/branch_and_bound.cpp b/cpp/src/dual_simplex/branch_and_bound.cpp index 02772f979e..17753db96f 100644 --- a/cpp/src/dual_simplex/branch_and_bound.cpp +++ b/cpp/src/dual_simplex/branch_and_bound.cpp @@ -1063,6 +1063,18 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut stats_.total_lp_iters = root_relax_soln_.iterations; stats_.total_lp_solve_time = toc(stats_.start_time); + // std::cout << "Root objective: " << root_objective_ << std::endl; + // std::cout << "Root primal solution: "; + // for (auto x : root_relax_soln_.x) { + // std::cout << x << " "; + // } + // std::cout << std::endl; + // std::cout << "Root dual solution: "; + // for (auto y : root_relax_soln_.y) { + // std::cout << y << " "; + // } + // std::cout << std::endl; + // TODO: Crush the root relaxation solution on converted user problem std::vector crushed_root_x; crush_primal_solution( @@ -1077,6 +1089,9 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut crushed_root_dual, crushed_root_z); + root_relax_soln_.x = crushed_root_x; + root_relax_soln_.y = crushed_root_dual; + root_relax_soln_.z = crushed_root_z; // TODO: Call crossover on the crushed solution lp_solution_t crossover_solution(original_lp_.num_rows, original_lp_.num_cols); std::vector vstatus(original_lp_.num_cols); diff --git a/cpp/src/dual_simplex/branch_and_bound.hpp b/cpp/src/dual_simplex/branch_and_bound.hpp index 98bc87f70b..5b64307d94 100644 --- a/cpp/src/dual_simplex/branch_and_bound.hpp +++ b/cpp/src/dual_simplex/branch_and_bound.hpp @@ -131,13 +131,18 @@ class branch_and_bound_t { // Set the root solution found by PDLP void set_root_relaxation_solution(const std::vector& primal, const std::vector& dual, + const std::vector& reduced_costs, f_t objective, + f_t user_objective, i_t iterations) { - root_relax_soln_.x = primal; - root_relax_soln_.y = dual; - root_objective_ = objective; - root_relax_soln_.iterations = iterations; + root_relax_soln_.x = primal; + root_relax_soln_.y = dual; + root_relax_soln_.z = reduced_costs; + root_objective_ = objective; + root_relax_soln_.objective = objective; + root_relax_soln_.user_objective = user_objective; + root_relax_soln_.iterations = iterations; } // Set a solution based on the user problem during the course of the solve diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index 15d20938ee..d590f2d5a7 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -392,25 +392,42 @@ solution_t diversity_manager_t::run_solver() // to bring variables within the bounds } + CUOPT_LOG_INFO( + "Optimal solution exists: %i, PDLP relaxed solution: objective %f, iterations %d", + ls.lp_optimal_exists, + lp_result.get_objective_value(), + lp_result.get_additional_termination_information().number_of_steps_taken); + // Send PDLP relaxed solution to branch and bound before it solves the root node if (problem_ptr->set_root_relaxation_solution_callback != nullptr) { - // Copy solution from device to host - std::vector host_primal(lp_optimal_solution.size()); - std::vector host_dual(lp_dual_optimal_solution.size()); + auto& d_primal_solution = lp_result.get_primal_solution(); + auto& d_dual_solution = lp_result.get_dual_solution(); + auto& d_reduced_costs = lp_result.get_reduced_cost(); + std::vector host_primal(d_primal_solution.size()); + std::vector host_dual(d_dual_solution.size()); + std::vector host_reduced_costs(d_reduced_costs.size()); raft::copy(host_primal.data(), - lp_optimal_solution.data(), - lp_optimal_solution.size(), + d_primal_solution.data(), + d_primal_solution.size(), problem_ptr->handle_ptr->get_stream()); raft::copy(host_dual.data(), - lp_dual_optimal_solution.data(), - lp_dual_optimal_solution.size(), + d_dual_solution.data(), + d_dual_solution.size(), + problem_ptr->handle_ptr->get_stream()); + raft::copy(host_reduced_costs.data(), + d_reduced_costs.data(), + d_reduced_costs.size(), problem_ptr->handle_ptr->get_stream()); problem_ptr->handle_ptr->sync_stream(); auto user_obj = problem_ptr->get_user_obj_from_solver_obj(lp_result.get_objective_value()); auto iterations = lp_result.get_additional_termination_information().number_of_steps_taken; - problem_ptr->set_root_relaxation_solution_callback( - host_primal, host_dual, user_obj, iterations); + problem_ptr->set_root_relaxation_solution_callback(host_primal, + host_dual, + host_reduced_costs, + lp_result.get_objective_value(), + user_obj, + iterations); } // in case the pdlp returned var boudns that are out of bounds diff --git a/cpp/src/mip/problem/problem.cuh b/cpp/src/mip/problem/problem.cuh index 49f81a63be..b88ff998e6 100644 --- a/cpp/src/mip/problem/problem.cuh +++ b/cpp/src/mip/problem/problem.cuh @@ -217,7 +217,8 @@ class problem_t { rmm::device_uvector integer_fixed_variable_map; std::function&)> branch_and_bound_callback; - std::function&, const std::vector&, f_t, i_t)> + std::function&, const std::vector&, const std::vector&, f_t, f_t, i_t)> set_root_relaxation_solution_callback; typename mip_solver_settings_t::tolerances_t tolerances{}; diff --git a/cpp/src/mip/solver.cu b/cpp/src/mip/solver.cu index 3623afcaab..1df02d182b 100644 --- a/cpp/src/mip/solver.cu +++ b/cpp/src/mip/solver.cu @@ -217,7 +217,9 @@ solution_t mip_solver_t::run_solver() std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, - std::placeholders::_4); + std::placeholders::_4, + std::placeholders::_5, + std::placeholders::_6); // Fork a thread for branch and bound // std::async and std::future allow us to get the return value of bb::solve() From 3eb950b5ea40976bcf7cfa3eefe4165bbcb2f5ae Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Wed, 5 Nov 2025 10:20:32 -0800 Subject: [PATCH 04/51] Pass correct vstatus vector --- cpp/src/dual_simplex/branch_and_bound.cpp | 31 +++++++++-------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/cpp/src/dual_simplex/branch_and_bound.cpp b/cpp/src/dual_simplex/branch_and_bound.cpp index 17753db96f..c9824cd92c 100644 --- a/cpp/src/dual_simplex/branch_and_bound.cpp +++ b/cpp/src/dual_simplex/branch_and_bound.cpp @@ -1063,19 +1063,7 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut stats_.total_lp_iters = root_relax_soln_.iterations; stats_.total_lp_solve_time = toc(stats_.start_time); - // std::cout << "Root objective: " << root_objective_ << std::endl; - // std::cout << "Root primal solution: "; - // for (auto x : root_relax_soln_.x) { - // std::cout << x << " "; - // } - // std::cout << std::endl; - // std::cout << "Root dual solution: "; - // for (auto y : root_relax_soln_.y) { - // std::cout << y << " "; - // } - // std::cout << std::endl; - - // TODO: Crush the root relaxation solution on converted user problem + // Crush the root relaxation solution on converted user problem std::vector crushed_root_x; crush_primal_solution( original_problem_, original_lp_, root_relax_soln_.x, new_slacks_, crushed_root_x); @@ -1092,16 +1080,20 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut root_relax_soln_.x = crushed_root_x; root_relax_soln_.y = crushed_root_dual; root_relax_soln_.z = crushed_root_z; - // TODO: Call crossover on the crushed solution + + // Call crossover on the crushed solution lp_solution_t crossover_solution(original_lp_.num_rows, original_lp_.num_cols); - std::vector vstatus(original_lp_.num_cols); - crossover_status_t crossover_status = crossover( - original_lp_, settings_, root_relax_soln_, stats_.start_time, crossover_solution, vstatus); + crossover_status_t crossover_status = crossover(original_lp_, + settings_, + root_relax_soln_, + stats_.start_time, + crossover_solution, + root_vstatus_); settings_.log.printf("Crossover status: %d\n", crossover_status); // TODO: Call dual simplex phase 2 to verify basis - if (root_status == lp_status_t::INFEASIBLE) { + if (crossover_status == crossover_status_t::NUMERICAL_ISSUES) { settings_.log.printf("MIP Infeasible\n"); // FIXME: rarely dual simplex detects infeasible whereas it is feasible. // to add a small safety net, check if there is a primal solution already. @@ -1119,7 +1111,8 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut return mip_status_t::UNBOUNDED; } - if (root_status == lp_status_t::TIME_LIMIT) { + if (crossover_status == crossover_status_t::TIME_LIMIT || + crossover_status == crossover_status_t::CONCURRENT_LIMIT) { status_ = mip_exploration_status_t::TIME_LIMIT; return set_final_solution(solution, -inf); } From 4a1cf948ae8df12ef17f5627ae784390d01dc61c Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Wed, 5 Nov 2025 15:13:02 -0800 Subject: [PATCH 05/51] Debugging dual inf norm assertion --- cpp/src/dual_simplex/branch_and_bound.cpp | 58 +++++++++++++++++++--- cpp/src/dual_simplex/presolve.cpp | 9 ++-- cpp/src/mip/diversity/diversity_manager.cu | 3 ++ cpp/tests/mip/miplib_test.cu | 5 +- 4 files changed, 63 insertions(+), 12 deletions(-) diff --git a/cpp/src/dual_simplex/branch_and_bound.cpp b/cpp/src/dual_simplex/branch_and_bound.cpp index c9824cd92c..56a2d89c47 100644 --- a/cpp/src/dual_simplex/branch_and_bound.cpp +++ b/cpp/src/dual_simplex/branch_and_bound.cpp @@ -1049,11 +1049,10 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut settings_.log.printf("Solving LP root relaxation\n"); // simplex_solver_settings_t lp_settings = settings_; // lp_settings.inside_mip = 1; + // auto copy_root_relax_soln = root_relax_soln_; // lp_status_t root_status = solve_linear_program_advanced( - // original_lp_, stats_.start_time, lp_settings, root_relax_soln_, root_vstatus_, edge_norms_); - - // FIXME: Figure out correct status mapping - lp_status_t root_status = lp_status_t::INFEASIBLE; + // original_lp_, stats_.start_time, lp_settings, copy_root_relax_soln, root_vstatus_, + // edge_norms_); // Wait for the root relaxation solution to be set by diversity manager while (root_relax_soln_.iterations == 0) { @@ -1062,23 +1061,66 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut } stats_.total_lp_iters = root_relax_soln_.iterations; stats_.total_lp_solve_time = toc(stats_.start_time); + // root_relax_soln_ = copy_root_relax_soln; + std::cout << "Root relaxation solution: " << root_relax_soln_.objective << std::endl; + std::cout << "Root relaxation primal solution: "; + for (auto x : root_relax_soln_.x) { + std::cout << x << " "; + } + std::cout << std::endl; + std::cout << "Root relaxation dual solution: "; + for (auto y : root_relax_soln_.y) { + std::cout << y << " "; + } + std::cout << std::endl; + std::cout << "Root relaxation reduced costs: "; + for (auto z : root_relax_soln_.z) { + std::cout << z << " "; + } + std::cout << std::endl; + + // std::cout << "copy_root_relax_soln.objective " << copy_root_relax_soln.objective << std::endl; + // std::cout << "copy_root_relax_soln.x "; + // for (auto x : copy_root_relax_soln.x) { + // std::cout << x << " "; + // } + // std::cout << std::endl; + // std::cout << "copy_root_relax_soln.y "; + // for (auto y : copy_root_relax_soln.y) { + // std::cout << y << " "; + // } + // std::cout << std::endl; + // std::cout << "copy_root_relax_soln.z "; + // for (auto z : copy_root_relax_soln.z) { + // std::cout << z << " "; + // } + // std::cout << std::endl; + + // std::cout << "new_slacks.size() " << new_slacks_.size() << std::endl; // Crush the root relaxation solution on converted user problem std::vector crushed_root_x; crush_primal_solution( original_problem_, original_lp_, root_relax_soln_.x, new_slacks_, crushed_root_x); - std::vector crushed_root_dual; + std::vector crushed_root_y; std::vector crushed_root_z; + // crush_dual_solution(original_problem_, + // original_lp_, + // new_slacks_, + // copy_root_relax_soln.y, + // copy_root_relax_soln.z, + // crushed_root_y, + // crushed_root_z); crush_dual_solution(original_problem_, original_lp_, new_slacks_, root_relax_soln_.y, root_relax_soln_.z, - crushed_root_dual, + crushed_root_y, crushed_root_z); root_relax_soln_.x = crushed_root_x; - root_relax_soln_.y = crushed_root_dual; + root_relax_soln_.y = crushed_root_y; root_relax_soln_.z = crushed_root_z; // Call crossover on the crushed solution @@ -1103,7 +1145,7 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut // } return mip_status_t::INFEASIBLE; } - if (root_status == lp_status_t::UNBOUNDED) { + if (crossover_status == crossover_status_t::NUMERICAL_ISSUES) { settings_.log.printf("MIP Unbounded\n"); if (settings_.heuristic_preemption_callback != nullptr) { settings_.heuristic_preemption_callback(); diff --git a/cpp/src/dual_simplex/presolve.cpp b/cpp/src/dual_simplex/presolve.cpp index 8d80337c74..2325126328 100644 --- a/cpp/src/dual_simplex/presolve.cpp +++ b/cpp/src/dual_simplex/presolve.cpp @@ -838,6 +838,8 @@ void convert_user_problem(const user_problem_t& user_problem, user_problem, row_sense, problem, less_rows, equal_rows, greater_rows, new_slacks); } + std::cout << "greater_rows " << greater_rows << std::endl; + std::cout << "less_rows " << less_rows << std::endl; if (greater_rows > 0) { convert_greater_to_less(user_problem, row_sense, problem, greater_rows, less_rows); } @@ -1344,9 +1346,9 @@ void crush_dual_solution(const user_problem_t& user_problem, } matrix_transpose_vector_multiply(problem.A, 1.0, y, 1.0, dual_residual); constexpr bool verbose = false; - if (verbose) { - printf("Converted solution || A^T y + z - c || %e\n", vector_norm_inf(dual_residual)); - } + // if (verbose) { + printf("Converted solution || A^T y + z - c || %e\n", vector_norm_inf(dual_residual)); + // } for (i_t j = 0; j < problem.num_cols; ++j) { if (std::abs(dual_residual[j]) > 1e-6) { f_t ajty = 0; @@ -1370,6 +1372,7 @@ void crush_dual_solution(const user_problem_t& user_problem, } } const f_t dual_res_inf = vector_norm_inf(dual_residual); + std::cout << "dual_res_inf " << dual_res_inf << std::endl; assert(dual_res_inf < 1e-6); } diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index d590f2d5a7..6e8db27d28 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -403,6 +403,9 @@ solution_t diversity_manager_t::run_solver() auto& d_primal_solution = lp_result.get_primal_solution(); auto& d_dual_solution = lp_result.get_dual_solution(); auto& d_reduced_costs = lp_result.get_reduced_cost(); + // cuopt::print("primal_solution", d_primal_solution); + // cuopt::print("dual_solution", d_dual_solution); + // cuopt::print("reduced_costs", d_reduced_costs); std::vector host_primal(d_primal_solution.size()); std::vector host_dual(d_dual_solution.size()); std::vector host_reduced_costs(d_reduced_costs.size()); diff --git a/cpp/tests/mip/miplib_test.cu b/cpp/tests/mip/miplib_test.cu index d0866455fa..582efe38b7 100644 --- a/cpp/tests/mip/miplib_test.cu +++ b/cpp/tests/mip/miplib_test.cu @@ -74,7 +74,10 @@ TEST(mip_solve, run_small_tests) { mip_solver_settings_t settings; std::vector test_instances = { - {"mip/50v-10.mps", 11311031.}, {"mip/neos5.mps", 15.}, {"mip/swath1.mps", 1300.}}; + // {"mip/50v-10.mps", 11311031.}, + {"mip/neos5.mps", 15.}, + // {"mip/swath1.mps", 1300.} + }; for (const auto& test_instance : test_instances) { test_miplib_file(test_instance, settings); } From 3229b0c9782f8d5f74e3e60be2941afdf605f0c1 Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Wed, 5 Nov 2025 15:52:57 -0800 Subject: [PATCH 06/51] Implement conversion of ge contraints to le --- cpp/src/mip/diversity/diversity_manager.cu | 7 +++ cpp/src/mip/problem/problem_helpers.cuh | 59 ++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index 6e8db27d28..a7fa17bb51 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -345,6 +345,13 @@ solution_t diversity_manager_t::run_solver() if (bb_thread_solution_exists) { ls.lp_optimal_exists = true; } else if (!fj_only_run) { + // Convert greater-than constraints to less-than constraints before calling LP solver + // This standardizes the constraint representation: a^T x >= lb becomes -a^T x <= -lb + // Note: This modifies the problem in-place, so all subsequent operations will use <= form + std::cout + << "Converting greater-than constraints to less-than constraints before calling LP solver" + << std::endl; + convert_greater_to_less(*problem_ptr); relaxed_lp_settings_t lp_settings; lp_settings.time_limit = lp_time_limit; lp_settings.tolerance = context.settings.tolerances.absolute_tolerance; diff --git a/cpp/src/mip/problem/problem_helpers.cuh b/cpp/src/mip/problem/problem_helpers.cuh index 8451983a99..3cd44be060 100644 --- a/cpp/src/mip/problem/problem_helpers.cuh +++ b/cpp/src/mip/problem/problem_helpers.cuh @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -321,4 +322,62 @@ static bool check_bounds_sanity(const detail::problem_t& problem) check_constraint_bounds_sanity(problem); } +// Kernel to negate coefficients for greater-than constraints +template +__global__ void kernel_convert_greater_to_less(raft::device_span coefficients, + raft::device_span offsets, + raft::device_span constraint_lower_bounds, + raft::device_span constraint_upper_bounds) +{ + const i_t constraint_id = blockIdx.x; + + const f_t lb = constraint_lower_bounds[constraint_id]; + const f_t ub = constraint_upper_bounds[constraint_id]; + + // Check if this is a greater-than constraint (lb is finite and ub is infinite) + if (!isfinite(lb) || isfinite(ub)) return; + + auto row_start = offsets[constraint_id]; + auto row_end = offsets[constraint_id + 1]; + auto row_size = row_end - row_start; + + // Negate all coefficients in this constraint row + for (i_t tid = threadIdx.x; tid < row_size; tid += blockDim.x) { + coefficients[row_start + tid] = -coefficients[row_start + tid]; + } + + // Negate the bounds for this constraint + if (threadIdx.x == 0) { + constraint_lower_bounds[constraint_id] = -ub; + constraint_upper_bounds[constraint_id] = -lb; + } +} + +// Function to convert greater-than constraints to less-than constraints +// This modifies the problem in-place by negating coefficient matrices and swapping bounds +template +static void convert_greater_to_less(detail::problem_t& problem) +{ + raft::common::nvtx::range scope("convert_greater_to_less"); + + auto* handle_ptr = problem.handle_ptr; + + // Negate coefficients and bounds for all greater-than constraints + constexpr i_t TPB = 256; + kernel_convert_greater_to_less + <<get_stream()>>>( + raft::device_span(problem.coefficients.data(), problem.coefficients.size()), + raft::device_span(problem.offsets.data(), problem.offsets.size()), + raft::device_span(problem.constraint_lower_bounds.data(), + problem.constraint_lower_bounds.size()), + raft::device_span(problem.constraint_upper_bounds.data(), + problem.constraint_upper_bounds.size())); + RAFT_CHECK_CUDA(handle_ptr->get_stream()); + + // Recompute the transpose since we modified the coefficients + problem.compute_transpose_of_problem(); + + handle_ptr->sync_stream(); +} + } // namespace cuopt::linear_programming::detail From 49c97baee8fc19b94ea23ec1852741420201ca04 Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Thu, 6 Nov 2025 15:26:02 -0800 Subject: [PATCH 07/51] Lower tolerance and run dual phase 2 validation --- cpp/src/dual_simplex/branch_and_bound.cpp | 49 +++++++---------------- cpp/src/dual_simplex/branch_and_bound.hpp | 1 + cpp/src/dual_simplex/presolve.cpp | 4 +- cpp/src/mip/relaxed_lp/relaxed_lp.cu | 14 ++++--- cpp/tests/mip/miplib_test.cu | 6 +-- 5 files changed, 28 insertions(+), 46 deletions(-) diff --git a/cpp/src/dual_simplex/branch_and_bound.cpp b/cpp/src/dual_simplex/branch_and_bound.cpp index 56a2d89c47..5018587494 100644 --- a/cpp/src/dual_simplex/branch_and_bound.cpp +++ b/cpp/src/dual_simplex/branch_and_bound.cpp @@ -1047,6 +1047,7 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut root_relax_soln_.resize(original_lp_.num_rows, original_lp_.num_cols); settings_.log.printf("Solving LP root relaxation\n"); + // simplex_solver_settings_t lp_settings = settings_; // lp_settings.inside_mip = 1; // auto copy_root_relax_soln = root_relax_soln_; @@ -1063,41 +1064,6 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut stats_.total_lp_solve_time = toc(stats_.start_time); // root_relax_soln_ = copy_root_relax_soln; - std::cout << "Root relaxation solution: " << root_relax_soln_.objective << std::endl; - std::cout << "Root relaxation primal solution: "; - for (auto x : root_relax_soln_.x) { - std::cout << x << " "; - } - std::cout << std::endl; - std::cout << "Root relaxation dual solution: "; - for (auto y : root_relax_soln_.y) { - std::cout << y << " "; - } - std::cout << std::endl; - std::cout << "Root relaxation reduced costs: "; - for (auto z : root_relax_soln_.z) { - std::cout << z << " "; - } - std::cout << std::endl; - - // std::cout << "copy_root_relax_soln.objective " << copy_root_relax_soln.objective << std::endl; - // std::cout << "copy_root_relax_soln.x "; - // for (auto x : copy_root_relax_soln.x) { - // std::cout << x << " "; - // } - // std::cout << std::endl; - // std::cout << "copy_root_relax_soln.y "; - // for (auto y : copy_root_relax_soln.y) { - // std::cout << y << " "; - // } - // std::cout << std::endl; - // std::cout << "copy_root_relax_soln.z "; - // for (auto z : copy_root_relax_soln.z) { - // std::cout << z << " "; - // } - // std::cout << std::endl; - - // std::cout << "new_slacks.size() " << new_slacks_.size() << std::endl; // Crush the root relaxation solution on converted user problem std::vector crushed_root_x; crush_primal_solution( @@ -1134,6 +1100,19 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut settings_.log.printf("Crossover status: %d\n", crossover_status); // TODO: Call dual simplex phase 2 to verify basis + i_t validation_iters = 0; + // should probably set the cut off here lp_settings.cut_off + dual::status_t lp_status = dual_phase2(2, + 0, + stats_.start_time, + original_lp_, + settings_, + root_vstatus_, + root_relax_soln_, + validation_iters, + edge_norms_); + cuopt_assert(validation_iters < 100, "Validation iterations exceeded 10"); + std::cout << "Validation iterations: " << validation_iters << std::endl; if (crossover_status == crossover_status_t::NUMERICAL_ISSUES) { settings_.log.printf("MIP Infeasible\n"); diff --git a/cpp/src/dual_simplex/branch_and_bound.hpp b/cpp/src/dual_simplex/branch_and_bound.hpp index 5b64307d94..35e3428f01 100644 --- a/cpp/src/dual_simplex/branch_and_bound.hpp +++ b/cpp/src/dual_simplex/branch_and_bound.hpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include diff --git a/cpp/src/dual_simplex/presolve.cpp b/cpp/src/dual_simplex/presolve.cpp index 2325126328..88f538d298 100644 --- a/cpp/src/dual_simplex/presolve.cpp +++ b/cpp/src/dual_simplex/presolve.cpp @@ -838,8 +838,6 @@ void convert_user_problem(const user_problem_t& user_problem, user_problem, row_sense, problem, less_rows, equal_rows, greater_rows, new_slacks); } - std::cout << "greater_rows " << greater_rows << std::endl; - std::cout << "less_rows " << less_rows << std::endl; if (greater_rows > 0) { convert_greater_to_less(user_problem, row_sense, problem, greater_rows, less_rows); } @@ -1373,7 +1371,7 @@ void crush_dual_solution(const user_problem_t& user_problem, } const f_t dual_res_inf = vector_norm_inf(dual_residual); std::cout << "dual_res_inf " << dual_res_inf << std::endl; - assert(dual_res_inf < 1e-6); + assert(dual_res_inf < 1e-4); } template diff --git a/cpp/src/mip/relaxed_lp/relaxed_lp.cu b/cpp/src/mip/relaxed_lp/relaxed_lp.cu index e8f9ca2a27..7f65c9fa39 100644 --- a/cpp/src/mip/relaxed_lp/relaxed_lp.cu +++ b/cpp/src/mip/relaxed_lp/relaxed_lp.cu @@ -58,11 +58,15 @@ optimization_problem_solution_t get_relaxed_lp_solution( if (tolerance_divisor == 0) { tolerance_divisor = 1; } pdlp_settings.tolerances.relative_primal_tolerance = settings.tolerance / tolerance_divisor; pdlp_settings.tolerances.relative_dual_tolerance = settings.tolerance / tolerance_divisor; - pdlp_settings.time_limit = settings.time_limit; - pdlp_settings.concurrent_halt = settings.concurrent_halt; - pdlp_settings.per_constraint_residual = settings.per_constraint_residual; - pdlp_settings.first_primal_feasible = settings.return_first_feasible; - pdlp_settings.pdlp_solver_mode = pdlp_solver_mode_t::Stable2; + std::cout << "relative primal tolerance: " << pdlp_settings.tolerances.relative_primal_tolerance + << std::endl; + std::cout << "relative dual tolerance: " << pdlp_settings.tolerances.relative_dual_tolerance + << std::endl; + pdlp_settings.time_limit = settings.time_limit; + pdlp_settings.concurrent_halt = settings.concurrent_halt; + pdlp_settings.per_constraint_residual = settings.per_constraint_residual; + pdlp_settings.first_primal_feasible = settings.return_first_feasible; + pdlp_settings.pdlp_solver_mode = pdlp_solver_mode_t::Stable2; set_pdlp_solver_mode(pdlp_settings); // TODO: set Stable3 here? pdlp_solver_t lp_solver(op_problem, pdlp_settings); diff --git a/cpp/tests/mip/miplib_test.cu b/cpp/tests/mip/miplib_test.cu index 582efe38b7..674c81b4b3 100644 --- a/cpp/tests/mip/miplib_test.cu +++ b/cpp/tests/mip/miplib_test.cu @@ -74,9 +74,9 @@ TEST(mip_solve, run_small_tests) { mip_solver_settings_t settings; std::vector test_instances = { - // {"mip/50v-10.mps", 11311031.}, - {"mip/neos5.mps", 15.}, - // {"mip/swath1.mps", 1300.} + {"mip/50v-10.mps", 11311031.}, + // {"mip/neos5.mps", 15.}, + // {"mip/swath1.mps", 1300.} }; for (const auto& test_instance : test_instances) { test_miplib_file(test_instance, settings); From d33384665078a8c242456b5168c960cc42e54840 Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Fri, 7 Nov 2025 11:05:45 -0800 Subject: [PATCH 08/51] Wip save --- cpp/src/dual_simplex/presolve.cpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/cpp/src/dual_simplex/presolve.cpp b/cpp/src/dual_simplex/presolve.cpp index 88f538d298..e27f468d00 100644 --- a/cpp/src/dual_simplex/presolve.cpp +++ b/cpp/src/dual_simplex/presolve.cpp @@ -615,16 +615,20 @@ i_t convert_range_rows(const user_problem_t& user_problem, f_t h; f_t u; if (row_sense[i] == 'L') { + std::cout << "Transforming less than constraint " << i << " to equal constraint" << std::endl; h = b - std::abs(r); u = b; less_rows--; equal_rows++; } else if (row_sense[i] == 'G') { + std::cout << "Transforming greater than constraint " << i << " to less than constraint" + << std::endl; h = b; u = b + std::abs(r); greater_rows--; equal_rows++; } else if (row_sense[i] == 'E') { + std::cout << "Transforming equal constraint " << i << " to equal constraint" << std::endl; if (r > 0) { h = b; u = b + std::abs(r); @@ -1006,7 +1010,8 @@ void convert_user_problem(const user_problem_t& user_problem, } // Add artifical variables - if (!settings.barrier_presolve) { add_artifical_variables(problem, equality_rows, new_slacks); } + // if (!settings.barrier_presolve) { add_artifical_variables(problem, equality_rows, new_slacks); + // } } template @@ -1323,6 +1328,13 @@ void crush_dual_solution(const user_problem_t& user_problem, z[j] = user_z[j]; } + std::cout << "user_problem.num_rows " << user_problem.num_rows << std::endl; + std::cout << "problem.num_rows " << problem.num_rows << std::endl; + std::cout << "user_y.size() " << user_y.size() << std::endl; + std::cout << "y.size() " << y.size() << std::endl; + std::cout << "user_z.size() " << user_z.size() << std::endl; + std::cout << "z.size() " << z.size() << std::endl; + for (i_t j : new_slacks) { const i_t col_start = problem.A.col_start[j]; const i_t col_end = problem.A.col_start[j + 1]; @@ -1343,7 +1355,7 @@ void crush_dual_solution(const user_problem_t& user_problem, dual_residual[j] -= problem.objective[j]; } matrix_transpose_vector_multiply(problem.A, 1.0, y, 1.0, dual_residual); - constexpr bool verbose = false; + constexpr bool verbose = true; // if (verbose) { printf("Converted solution || A^T y + z - c || %e\n", vector_norm_inf(dual_residual)); // } @@ -1371,7 +1383,7 @@ void crush_dual_solution(const user_problem_t& user_problem, } const f_t dual_res_inf = vector_norm_inf(dual_residual); std::cout << "dual_res_inf " << dual_res_inf << std::endl; - assert(dual_res_inf < 1e-4); + // assert(dual_res_inf < 1e-4); } template From 00a89feb9dc829f59a206b6d72500ba466f07527 Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Fri, 7 Nov 2025 16:00:43 -0800 Subject: [PATCH 09/51] Remove prints --- cpp/src/dual_simplex/branch_and_bound.cpp | 29 ++++++++------------ cpp/src/dual_simplex/presolve.cpp | 32 ++++++++++++++-------- cpp/src/dual_simplex/presolve.hpp | 14 +++++----- cpp/src/mip/diversity/diversity_manager.cu | 4 +-- 4 files changed, 40 insertions(+), 39 deletions(-) diff --git a/cpp/src/dual_simplex/branch_and_bound.cpp b/cpp/src/dual_simplex/branch_and_bound.cpp index 54aabe8ae8..9b9b1e702a 100644 --- a/cpp/src/dual_simplex/branch_and_bound.cpp +++ b/cpp/src/dual_simplex/branch_and_bound.cpp @@ -1046,7 +1046,7 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut root_relax_soln_.resize(original_lp_.num_rows, original_lp_.num_cols); - settings_.log.printf("Solving LP root relaxation\n"); + settings_.log.printf("Waiting for PDLP root relaxation\n"); // simplex_solver_settings_t lp_settings = settings_; // lp_settings.inside_mip = 1; @@ -1062,28 +1062,21 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut } stats_.total_lp_iters = root_relax_soln_.iterations; stats_.total_lp_solve_time = toc(stats_.start_time); - // root_relax_soln_ = copy_root_relax_soln; - // Crush the root relaxation solution on converted user problem std::vector crushed_root_x; crush_primal_solution( original_problem_, original_lp_, root_relax_soln_.x, new_slacks_, crushed_root_x); std::vector crushed_root_y; std::vector crushed_root_z; - // crush_dual_solution(original_problem_, - // original_lp_, - // new_slacks_, - // copy_root_relax_soln.y, - // copy_root_relax_soln.z, - // crushed_root_y, - // crushed_root_z); - crush_dual_solution(original_problem_, - original_lp_, - new_slacks_, - root_relax_soln_.y, - root_relax_soln_.z, - crushed_root_y, - crushed_root_z); + + f_t dual_res_inf = crush_dual_solution(original_problem_, + original_lp_, + new_slacks_, + root_relax_soln_.y, + root_relax_soln_.z, + crushed_root_y, + crushed_root_z); + settings_.log.printf("Dual residual inf: %e\n", dual_res_inf); root_relax_soln_.x = crushed_root_x; root_relax_soln_.y = crushed_root_y; @@ -1112,7 +1105,7 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut root_relax_soln_, validation_iters, edge_norms_); - std::cout << "Validation iterations: " << validation_iters << std::endl; + settings_.log.printf("Validation iterations: %d\n", validation_iters); cuopt_assert(validation_iters < 100, "Validation iterations exceeded 10"); if (crossover_status == crossover_status_t::NUMERICAL_ISSUES) { diff --git a/cpp/src/dual_simplex/presolve.cpp b/cpp/src/dual_simplex/presolve.cpp index ff168cf5d7..62166d2918 100644 --- a/cpp/src/dual_simplex/presolve.cpp +++ b/cpp/src/dual_simplex/presolve.cpp @@ -1320,13 +1320,13 @@ void crush_primal_solution_with_slack(const user_problem_t& user_probl } template -void crush_dual_solution(const user_problem_t& user_problem, - const lp_problem_t& problem, - const std::vector& new_slacks, - const std::vector& user_y, - const std::vector& user_z, - std::vector& y, - std::vector& z) +f_t crush_dual_solution(const user_problem_t& user_problem, + const lp_problem_t& problem, + const std::vector& new_slacks, + const std::vector& user_y, + const std::vector& user_z, + std::vector& y, + std::vector& z) { y.resize(problem.num_rows); for (i_t i = 0; i < user_problem.num_rows; i++) { @@ -1374,10 +1374,10 @@ void crush_dual_solution(const user_problem_t& user_problem, dual_residual[j] -= problem.objective[j]; } matrix_transpose_vector_multiply(problem.A, 1.0, y, 1.0, dual_residual); - constexpr bool verbose = true; - // if (verbose) { - printf("Converted solution || A^T y + z - c || %e\n", vector_norm_inf(dual_residual)); - // } + constexpr bool verbose = false; + if (verbose) { + printf("Converted solution || A^T y + z - c || %e\n", vector_norm_inf(dual_residual)); + } for (i_t j = 0; j < problem.num_cols; ++j) { if (std::abs(dual_residual[j]) > 1e-6) { f_t ajty = 0; @@ -1401,8 +1401,8 @@ void crush_dual_solution(const user_problem_t& user_problem, } } const f_t dual_res_inf = vector_norm_inf(dual_residual); - std::cout << "dual_res_inf " << dual_res_inf << std::endl; // assert(dual_res_inf < 1e-4); + return dual_res_inf; } template @@ -1619,6 +1619,14 @@ template void crush_primal_solution(const user_problem_t& new_slacks, std::vector& solution); +template double crush_dual_solution(const user_problem_t& user_problem, + const lp_problem_t& problem, + const std::vector& new_slacks, + const std::vector& user_y, + const std::vector& user_z, + std::vector& y, + std::vector& z); + template void uncrush_primal_solution(const user_problem_t& user_problem, const lp_problem_t& problem, const std::vector& solution, diff --git a/cpp/src/dual_simplex/presolve.hpp b/cpp/src/dual_simplex/presolve.hpp index bf0aab8997..903d78768a 100644 --- a/cpp/src/dual_simplex/presolve.hpp +++ b/cpp/src/dual_simplex/presolve.hpp @@ -158,13 +158,13 @@ void crush_primal_solution_with_slack(const user_problem_t& user_probl std::vector& solution); template -void crush_dual_solution(const user_problem_t& user_problem, - const lp_problem_t& problem, - const std::vector& new_slacks, - const std::vector& user_y, - const std::vector& user_z, - std::vector& y, - std::vector& z); +f_t crush_dual_solution(const user_problem_t& user_problem, + const lp_problem_t& problem, + const std::vector& new_slacks, + const std::vector& user_y, + const std::vector& user_z, + std::vector& y, + std::vector& z); template void uncrush_primal_solution(const user_problem_t& user_problem, diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index f52e8a9db1..9e9dfa224d 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -351,9 +351,9 @@ solution_t diversity_manager_t::run_solver() // std::cout // << "Converting greater-than constraints to less-than constraints before calling LP solver" // << std::endl; - // convert_greater_to_less(*problem_ptr); + convert_greater_to_less(*problem_ptr); relaxed_lp_settings_t lp_settings; - lp_settings.time_limit = 3600; // lp_time_limit; + lp_settings.time_limit = lp_time_limit; lp_settings.tolerance = context.settings.tolerances.absolute_tolerance; lp_settings.return_first_feasible = false; lp_settings.save_state = true; From bfe9be9ffe9b8182a99428fd09e79516c95d019a Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Fri, 7 Nov 2025 16:35:31 -0800 Subject: [PATCH 10/51] Remove prints --- cpp/src/dual_simplex/branch_and_bound.cpp | 1 + cpp/src/dual_simplex/presolve.cpp | 13 +------------ cpp/src/mip/diversity/diversity_manager.cu | 2 +- cpp/src/mip/relaxed_lp/relaxed_lp.cu | 14 +++++--------- 4 files changed, 8 insertions(+), 22 deletions(-) diff --git a/cpp/src/dual_simplex/branch_and_bound.cpp b/cpp/src/dual_simplex/branch_and_bound.cpp index fd719d0b5c..3453bfb3b7 100644 --- a/cpp/src/dual_simplex/branch_and_bound.cpp +++ b/cpp/src/dual_simplex/branch_and_bound.cpp @@ -1097,6 +1097,7 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut edge_norms_); settings_.log.printf("Validation iterations: %d\n", validation_iters); cuopt_assert(validation_iters < 100, "Validation iterations exceeded 10"); + exit(0); if (crossover_status == crossover_status_t::NUMERICAL_ISSUES) { settings_.log.printf("MIP Infeasible\n"); diff --git a/cpp/src/dual_simplex/presolve.cpp b/cpp/src/dual_simplex/presolve.cpp index 5f746a683b..87b4d0f629 100644 --- a/cpp/src/dual_simplex/presolve.cpp +++ b/cpp/src/dual_simplex/presolve.cpp @@ -605,20 +605,16 @@ i_t convert_range_rows(const user_problem_t& user_problem, f_t h; f_t u; if (row_sense[i] == 'L') { - std::cout << "Transforming less than constraint " << i << " to equal constraint" << std::endl; h = b - std::abs(r); u = b; less_rows--; equal_rows++; } else if (row_sense[i] == 'G') { - std::cout << "Transforming greater than constraint " << i << " to less than constraint" - << std::endl; h = b; u = b + std::abs(r); greater_rows--; equal_rows++; } else if (row_sense[i] == 'E') { - std::cout << "Transforming equal constraint " << i << " to equal constraint" << std::endl; if (r > 0) { h = b; u = b + std::abs(r); @@ -774,7 +770,7 @@ i_t add_artifical_variables(lp_problem_t& problem, problem.A.col_start[num_cols] = p; assert(j == num_cols); assert(p == nnz); - constexpr bool verbose = true; + constexpr bool verbose = false; if (verbose) { printf("Added %d artificial variables\n", num_artificial_vars); } problem.A.n = num_cols; problem.num_cols = num_cols; @@ -1327,13 +1323,6 @@ f_t crush_dual_solution(const user_problem_t& user_problem, z[j] = user_z[j]; } - std::cout << "user_problem.num_rows " << user_problem.num_rows << std::endl; - std::cout << "problem.num_rows " << problem.num_rows << std::endl; - std::cout << "user_y.size() " << user_y.size() << std::endl; - std::cout << "y.size() " << y.size() << std::endl; - std::cout << "user_z.size() " << user_z.size() << std::endl; - std::cout << "z.size() " << z.size() << std::endl; - std::vector is_range_row(problem.num_rows, false); for (i_t i = 0; i < user_problem.range_rows.size(); i++) { is_range_row[user_problem.range_rows[i]] = true; diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index b0e146afe6..822d658b23 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -354,7 +354,7 @@ solution_t diversity_manager_t::run_solver() auto lp_result = get_relaxed_lp_solution(*problem_ptr, lp_optimal_solution_copy, lp_state, lp_settings); - std::cout << "LP enum: " << lp_result.get_termination_status_string() << std::endl; + CUOPT_LOG_INFO("LP enum: %s", lp_result.get_termination_status_string().c_str()); { std::lock_guard guard(relaxed_solution_mutex); if (!simplex_solution_exists.load()) { diff --git a/cpp/src/mip/relaxed_lp/relaxed_lp.cu b/cpp/src/mip/relaxed_lp/relaxed_lp.cu index 8fa6d5f76b..6807e1cece 100644 --- a/cpp/src/mip/relaxed_lp/relaxed_lp.cu +++ b/cpp/src/mip/relaxed_lp/relaxed_lp.cu @@ -48,15 +48,11 @@ optimization_problem_solution_t get_relaxed_lp_solution( if (tolerance_divisor == 0) { tolerance_divisor = 1; } pdlp_settings.tolerances.relative_primal_tolerance = settings.tolerance / tolerance_divisor; pdlp_settings.tolerances.relative_dual_tolerance = settings.tolerance / tolerance_divisor; - std::cout << "relative primal tolerance: " << pdlp_settings.tolerances.relative_primal_tolerance - << std::endl; - std::cout << "relative dual tolerance: " << pdlp_settings.tolerances.relative_dual_tolerance - << std::endl; - pdlp_settings.time_limit = settings.time_limit; - pdlp_settings.concurrent_halt = settings.concurrent_halt; - pdlp_settings.per_constraint_residual = settings.per_constraint_residual; - pdlp_settings.first_primal_feasible = settings.return_first_feasible; - pdlp_settings.pdlp_solver_mode = pdlp_solver_mode_t::Stable2; + pdlp_settings.time_limit = settings.time_limit; + pdlp_settings.concurrent_halt = settings.concurrent_halt; + pdlp_settings.per_constraint_residual = settings.per_constraint_residual; + pdlp_settings.first_primal_feasible = settings.return_first_feasible; + pdlp_settings.pdlp_solver_mode = pdlp_solver_mode_t::Stable2; set_pdlp_solver_mode(pdlp_settings); // TODO: set Stable3 here? pdlp_solver_t lp_solver(op_problem, pdlp_settings); From 2a9c994512c262fc17e650d199684738cd2de8c0 Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Mon, 10 Nov 2025 17:01:25 -0800 Subject: [PATCH 11/51] Debugging successive calls with vstatus --- cpp/src/dual_simplex/branch_and_bound.cpp | 13 ++++++++++++- cpp/src/mip/relaxed_lp/relaxed_lp.cu | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/cpp/src/dual_simplex/branch_and_bound.cpp b/cpp/src/dual_simplex/branch_and_bound.cpp index 3453bfb3b7..7f224337f1 100644 --- a/cpp/src/dual_simplex/branch_and_bound.cpp +++ b/cpp/src/dual_simplex/branch_and_bound.cpp @@ -1095,7 +1095,18 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut root_relax_soln_, validation_iters, edge_norms_); - settings_.log.printf("Validation iterations: %d\n", validation_iters); + settings_.log.printf("Validation iterations first call: %d\n", validation_iters); + validation_iters = 0; + lp_status = dual_phase2(2, + 0, + stats_.start_time, + original_lp_, + settings_, + root_vstatus_, + root_relax_soln_, + validation_iters, + edge_norms_); + settings_.log.printf("Validation iterations second call: %d\n", validation_iters); cuopt_assert(validation_iters < 100, "Validation iterations exceeded 10"); exit(0); diff --git a/cpp/src/mip/relaxed_lp/relaxed_lp.cu b/cpp/src/mip/relaxed_lp/relaxed_lp.cu index 6807e1cece..4842a986ac 100644 --- a/cpp/src/mip/relaxed_lp/relaxed_lp.cu +++ b/cpp/src/mip/relaxed_lp/relaxed_lp.cu @@ -85,7 +85,7 @@ optimization_problem_solution_t get_relaxed_lp_solution( cuopt::default_logger().flush(); // temporarily add timer auto start_time = timer_t(pdlp_settings.time_limit); - lp_solver.set_inside_mip(false); + lp_solver.set_inside_mip(true); auto solver_response = lp_solver.run_solver(start_time); if (solver_response.get_primal_solution().size() != 0 && From 32c6edbf9bda743e3fa3448f53d360cef5c80c60 Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Wed, 12 Nov 2025 10:56:39 -0800 Subject: [PATCH 12/51] Fix precision issues --- cpp/src/dual_simplex/phase2.cpp | 63 ++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 24 deletions(-) diff --git a/cpp/src/dual_simplex/phase2.cpp b/cpp/src/dual_simplex/phase2.cpp index 39ea9b4657..b1fdc0fa51 100644 --- a/cpp/src/dual_simplex/phase2.cpp +++ b/cpp/src/dual_simplex/phase2.cpp @@ -1910,6 +1910,7 @@ void set_primal_variables_on_bounds(const lp_problem_t& lp, std::vector& x) { const i_t n = lp.num_cols; + settings.log.printf("tolerance %e\n", settings.fixed_tol); for (i_t j = 0; j < n; ++j) { // We set z_j = 0 for basic variables // But we explicitally skip setting basic variables here @@ -1919,21 +1920,29 @@ void set_primal_variables_on_bounds(const lp_problem_t& lp, const f_t fixed_tolerance = settings.fixed_tol; if (std::abs(lp.lower[j] - lp.upper[j]) < fixed_tolerance) { if (vstatus[j] != variable_status_t::NONBASIC_FIXED) { - settings.log.debug("Setting fixed variable %d to %e (current %e). vstatus %d\n", - j, - lp.lower[j], - x[j], - static_cast(vstatus[j])); + settings.log.printf("Setting fixed variable %d to %e (current %e). vstatus %d\n", + j, + lp.lower[j], + x[j], + static_cast(vstatus[j])); } x[j] = lp.lower[j]; vstatus[j] = variable_status_t::NONBASIC_FIXED; - } else if (z[j] == 0 && lp.lower[j] > -inf && vstatus[j] == variable_status_t::NONBASIC_LOWER) { + // } else if (z[j] == 0 && lp.lower[j] > -inf && vstatus[j] == + // variable_status_t::NONBASIC_LOWER) { + // x[j] = lp.lower[j]; + // } else if (z[j] == 0 && lp.upper[j] < inf && vstatus[j] == + // variable_status_t::NONBASIC_UPPER) { + // x[j] = lp.upper[j]; + } else if (std::abs(z[j]) <= fixed_tolerance && lp.lower[j] > -inf && + vstatus[j] == variable_status_t::NONBASIC_LOWER) { x[j] = lp.lower[j]; - } else if (z[j] == 0 && lp.upper[j] < inf && vstatus[j] == variable_status_t::NONBASIC_UPPER) { + } else if (std::abs(z[j]) <= fixed_tolerance && lp.upper[j] < inf && + vstatus[j] == variable_status_t::NONBASIC_UPPER) { x[j] = lp.upper[j]; - } else if (z[j] >= 0 && lp.lower[j] > -inf) { + } else if (z[j] > fixed_tolerance && lp.lower[j] > -inf) { if (vstatus[j] != variable_status_t::NONBASIC_LOWER) { - settings.log.debug( + settings.log.printf( "Setting nonbasic lower variable (zj %e) %d to %e (current %e). vstatus %d\n", z[j], j, @@ -1943,9 +1952,9 @@ void set_primal_variables_on_bounds(const lp_problem_t& lp, } x[j] = lp.lower[j]; vstatus[j] = variable_status_t::NONBASIC_LOWER; - } else if (z[j] <= 0 && lp.upper[j] < inf) { + } else if (z[j] < fixed_tolerance && lp.upper[j] < inf) { if (vstatus[j] != variable_status_t::NONBASIC_UPPER) { - settings.log.debug( + settings.log.printf( "Setting nonbasic upper variable (zj %e) %d to %e (current %e). vstatus %d\n", z[j], j, @@ -1958,29 +1967,29 @@ void set_primal_variables_on_bounds(const lp_problem_t& lp, } else if (lp.upper[j] == inf && lp.lower[j] > -inf && z[j] < 0) { // dual infeasible if (vstatus[j] != variable_status_t::NONBASIC_LOWER) { - settings.log.debug("Setting nonbasic lower variable %d to %e (current %e). vstatus %d\n", - j, - lp.lower[j], - x[j], - static_cast(vstatus[j])); + settings.log.printf("Setting nonbasic lower variable %d to %e (current %e). vstatus %d\n", + j, + lp.lower[j], + x[j], + static_cast(vstatus[j])); } x[j] = lp.lower[j]; vstatus[j] = variable_status_t::NONBASIC_LOWER; } else if (lp.lower[j] == -inf && lp.upper[j] < inf && z[j] > 0) { // dual infeasible if (vstatus[j] != variable_status_t::NONBASIC_UPPER) { - settings.log.debug("Setting nonbasic upper variable %d to %e (current %e). vstatus %d\n", - j, - lp.upper[j], - x[j], - static_cast(vstatus[j])); + settings.log.printf("Setting nonbasic upper variable %d to %e (current %e). vstatus %d\n", + j, + lp.upper[j], + x[j], + static_cast(vstatus[j])); } x[j] = lp.upper[j]; vstatus[j] = variable_status_t::NONBASIC_UPPER; } else if (lp.lower[j] == -inf && lp.upper[j] == inf) { x[j] = 0; // Set nonbasic free variables to 0 this overwrites previous lines if (vstatus[j] != variable_status_t::NONBASIC_FREE) { - settings.log.debug( + settings.log.printf( "Setting free variable %d to %e. vstatus %d\n", j, 0, static_cast(vstatus[j])); } vstatus[j] = variable_status_t::NONBASIC_FREE; @@ -2192,6 +2201,7 @@ dual::status_t dual_phase2(i_t phase, iter, delta_y_steepest_edge); } +#define PRINT_VSTATUS_CHANGES template dual::status_t dual_phase2_with_advanced_basis(i_t phase, @@ -2255,14 +2265,19 @@ dual::status_t dual_phase2_with_advanced_basis(i_t phase, // Solve B'*y = cB ft.b_transpose_solve(c_basic, y); if (toc(start_time) > settings.time_limit) { return dual::status_t::TIME_LIMIT; } - constexpr bool print_norms = false; + constexpr bool print_norms = true; if constexpr (print_norms) { settings.log.printf( "|| y || %e || cB || %e\n", vector_norm_inf(y), vector_norm_inf(c_basic)); } phase2::compute_reduced_costs(objective, lp.A, y, basic_list, nonbasic_list, z); - if constexpr (print_norms) { settings.log.printf("|| z || %e\n", vector_norm_inf(z)); } + if constexpr (print_norms) { + settings.log.printf("|| z || %e\n", vector_norm_inf(z)); + // for (i_t j = 0; j < n; ++j) { + // settings.log.printf("z[%d] = %e\n", j, z[j]); + // } + } #ifdef COMPUTE_DUAL_RESIDUAL std::vector dual_res1; From 4764d51393bf1aec2bc0550600f78df17819e608 Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Wed, 12 Nov 2025 13:14:38 -0800 Subject: [PATCH 13/51] Save debugging state --- cpp/src/dual_simplex/branch_and_bound.cpp | 22 +++++++++++++++++++--- cpp/src/mip/diversity/diversity_manager.cu | 2 +- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/cpp/src/dual_simplex/branch_and_bound.cpp b/cpp/src/dual_simplex/branch_and_bound.cpp index 7f224337f1..083598bd27 100644 --- a/cpp/src/dual_simplex/branch_and_bound.cpp +++ b/cpp/src/dual_simplex/branch_and_bound.cpp @@ -1038,13 +1038,28 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut settings_.log.printf("Waiting for PDLP root relaxation\n"); - // simplex_solver_settings_t lp_settings = settings_; - // lp_settings.inside_mip = 1; + simplex_solver_settings_t lp_settings = settings_; + lp_settings.inside_mip = 1; // auto copy_root_relax_soln = root_relax_soln_; // lp_status_t root_status = solve_linear_program_advanced( // original_lp_, stats_.start_time, lp_settings, copy_root_relax_soln, root_vstatus_, // edge_norms_); + i_t validation_iters = 0; + // edge_norms_.clear(); + // settings_.set_log(true); + // dual::status_t lp_status = dual_phase2(2, + // 0, + // stats_.start_time, + // original_lp_, + // settings_, + // root_vstatus_, + // root_relax_soln_, + // validation_iters, + // edge_norms_); + // settings_.log.printf("Validation iterations root relaxation: %d\n", validation_iters); + // exit(0); + // Wait for the root relaxation solution to be set by diversity manager while (root_relax_soln_.iterations == 0) { std::this_thread::sleep_for(std::chrono::milliseconds(10)); @@ -1083,7 +1098,7 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut settings_.log.printf("Crossover status: %d\n", crossover_status); // TODO: Call dual simplex phase 2 to verify basis - i_t validation_iters = 0; + validation_iters = 0; // should probably set the cut off here lp_settings.cut_off settings_.set_log(true); dual::status_t lp_status = dual_phase2(2, @@ -1096,6 +1111,7 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut validation_iters, edge_norms_); settings_.log.printf("Validation iterations first call: %d\n", validation_iters); + validation_iters = 0; lp_status = dual_phase2(2, 0, diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index 822d658b23..a3db30a516 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -343,7 +343,7 @@ solution_t diversity_manager_t::run_solver() // << std::endl; convert_greater_to_less(*problem_ptr); relaxed_lp_settings_t lp_settings; - lp_settings.time_limit = lp_time_limit; + lp_settings.time_limit = 3600; // lp_time_limit; lp_settings.tolerance = context.settings.tolerances.absolute_tolerance; lp_settings.return_first_feasible = false; lp_settings.save_state = true; From b6aad5dc6b701552b763287ebec52668fbe39cdb Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Wed, 12 Nov 2025 13:58:09 -0800 Subject: [PATCH 14/51] Remove unecessary original problem argument --- cpp/src/linear_programming/solve.cu | 14 +++++--------- cpp/src/linear_programming/solve.cuh | 1 - cpp/src/mip/diversity/diversity_manager.cu | 2 +- cpp/src/mip/solver.cu | 3 +-- 4 files changed, 7 insertions(+), 13 deletions(-) diff --git a/cpp/src/linear_programming/solve.cu b/cpp/src/linear_programming/solve.cu index 25304ef1de..7f9d0670e5 100644 --- a/cpp/src/linear_programming/solve.cu +++ b/cpp/src/linear_programming/solve.cu @@ -642,7 +642,6 @@ void run_dual_simplex_thread( template optimization_problem_solution_t run_concurrent( - const optimization_problem_t& op_problem, detail::problem_t& problem, pdlp_solver_settings_t const& settings, const timer_t& timer, @@ -652,8 +651,7 @@ optimization_problem_solution_t run_concurrent( timer_t timer_concurrent(timer.remaining_time()); // Copy the settings so that we can set the concurrent halt pointer - pdlp_solver_settings_t settings_pdlp(settings, - op_problem.get_handle_ptr()->get_stream()); + pdlp_solver_settings_t settings_pdlp(settings, problem.handle_ptr->get_stream()); // Set the concurrent halt pointer global_concurrent_halt = 0; @@ -726,7 +724,7 @@ optimization_problem_solution_t run_concurrent( sol_dual_simplex.get_termination_status() == pdlp_termination_status_t::PrimalInfeasible || sol_dual_simplex.get_termination_status() == pdlp_termination_status_t::DualInfeasible) { CUOPT_LOG_INFO("Solved with dual simplex"); - sol_pdlp.copy_from(op_problem.get_handle_ptr(), sol_dual_simplex); + sol_pdlp.copy_from(problem.handle_ptr, sol_dual_simplex); sol_pdlp.set_solve_time(end_time); CUOPT_LOG_INFO("Status: %s Objective: %.8e Iterations: %d Time: %.3fs", sol_pdlp.get_termination_status_string().c_str(), @@ -736,7 +734,7 @@ optimization_problem_solution_t run_concurrent( return sol_pdlp; } else if (sol_barrier.get_termination_status() == pdlp_termination_status_t::Optimal) { CUOPT_LOG_INFO("Solved with barrier"); - sol_pdlp.copy_from(op_problem.get_handle_ptr(), sol_barrier); + sol_pdlp.copy_from(problem.handle_ptr, sol_barrier); sol_pdlp.set_solve_time(end_time); CUOPT_LOG_INFO("Status: %s Objective: %.8e Iterations: %d Time: %.3fs", sol_pdlp.get_termination_status_string().c_str(), @@ -758,7 +756,6 @@ optimization_problem_solution_t run_concurrent( template optimization_problem_solution_t solve_lp_with_method( - const optimization_problem_t& op_problem, detail::problem_t& problem, pdlp_solver_settings_t const& settings, const timer_t& timer, @@ -769,7 +766,7 @@ optimization_problem_solution_t solve_lp_with_method( } else if (settings.method == method_t::Barrier) { return run_barrier(problem, settings, timer); } else if (settings.method == method_t::Concurrent) { - return run_concurrent(op_problem, problem, settings, timer, is_batch_mode); + return run_concurrent(problem, settings, timer, is_batch_mode); } else { return run_pdlp(problem, settings, timer, is_batch_mode); } @@ -861,7 +858,7 @@ optimization_problem_solution_t solve_lp(optimization_problem_tget_stream()); - auto solution = solve_lp_with_method(op_problem, problem, settings, lp_timer, is_batch_mode); + auto solution = solve_lp_with_method(problem, settings, lp_timer, is_batch_mode); if (run_presolve) { auto primal_solution = cuopt::device_copy(solution.get_primal_solution(), @@ -1017,7 +1014,6 @@ optimization_problem_solution_t solve_lp( bool use_pdlp_solver_mode); \ \ template optimization_problem_solution_t solve_lp_with_method( \ - const optimization_problem_t& op_problem, \ detail::problem_t& problem, \ pdlp_solver_settings_t const& settings, \ const timer_t& timer, \ diff --git a/cpp/src/linear_programming/solve.cuh b/cpp/src/linear_programming/solve.cuh index 83d7529c9b..a3c3240f40 100644 --- a/cpp/src/linear_programming/solve.cuh +++ b/cpp/src/linear_programming/solve.cuh @@ -22,7 +22,6 @@ cuopt::linear_programming::optimization_problem_t mps_data_model_to_op template cuopt::linear_programming::optimization_problem_solution_t solve_lp_with_method( - const optimization_problem_t& op_problem, detail::problem_t& problem, pdlp_solver_settings_t const& settings, const timer_t& timer, diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index a3db30a516..822d658b23 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -343,7 +343,7 @@ solution_t diversity_manager_t::run_solver() // << std::endl; convert_greater_to_less(*problem_ptr); relaxed_lp_settings_t lp_settings; - lp_settings.time_limit = 3600; // lp_time_limit; + lp_settings.time_limit = lp_time_limit; lp_settings.tolerance = context.settings.tolerances.absolute_tolerance; lp_settings.return_first_feasible = false; lp_settings.save_state = true; diff --git a/cpp/src/mip/solver.cu b/cpp/src/mip/solver.cu index 452c493a20..811db918da 100644 --- a/cpp/src/mip/solver.cu +++ b/cpp/src/mip/solver.cu @@ -127,8 +127,7 @@ solution_t mip_solver_t::run_solver() auto lp_timer = timer_t(settings.time_limit); settings.method = method_t::Concurrent; - auto opt_sol = solve_lp_with_method( - *context.problem_ptr->original_problem_ptr, *context.problem_ptr, settings, lp_timer); + auto opt_sol = solve_lp_with_method(*context.problem_ptr, settings, lp_timer); solution_t sol(*context.problem_ptr); sol.copy_new_assignment(host_copy(opt_sol.get_primal_solution())); From 1c0d13914b498191d9be10875d8220523bec99d9 Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Wed, 12 Nov 2025 14:14:46 -0800 Subject: [PATCH 15/51] Call concurrent mode from mip root solve --- cpp/src/mip/diversity/diversity_manager.cu | 44 ++++++++++++++++------ 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index 822d658b23..9205d3bc9c 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -5,15 +5,17 @@ */ /* clang-format on */ +#include "cuda_profiler_api.h" +#include "diversity_manager.cuh" + #include #include #include #include -#include "diversity_manager.cuh" -#include +#include -#include "cuda_profiler_api.h" +#include constexpr bool fj_only_run = false; @@ -342,17 +344,35 @@ solution_t diversity_manager_t::run_solver() // << "Converting greater-than constraints to less-than constraints before calling LP solver" // << std::endl; convert_greater_to_less(*problem_ptr); - relaxed_lp_settings_t lp_settings; - lp_settings.time_limit = lp_time_limit; - lp_settings.tolerance = context.settings.tolerances.absolute_tolerance; - lp_settings.return_first_feasible = false; - lp_settings.save_state = true; - lp_settings.concurrent_halt = &global_concurrent_halt; - lp_settings.has_initial_primal = false; + // relaxed_lp_settings_t lp_settings; + // lp_settings.time_limit = lp_time_limit; + // lp_settings.tolerance = context.settings.tolerances.absolute_tolerance; + // lp_settings.return_first_feasible = false; + // lp_settings.save_state = true; + // lp_settings.concurrent_halt = &global_concurrent_halt; + // lp_settings.has_initial_primal = false; + // rmm::device_uvector lp_optimal_solution_copy(lp_optimal_solution.size(), + // problem_ptr->handle_ptr->get_stream()); + // auto lp_result = + // get_relaxed_lp_solution(*problem_ptr, lp_optimal_solution_copy, lp_state, lp_settings); + f_t tolerance_divisor = + problem_ptr->tolerances.absolute_tolerance / problem_ptr->tolerances.relative_tolerance; + if (tolerance_divisor == 0) { tolerance_divisor = 1; } + f_t absolute_tolerance = context.settings.tolerances.absolute_tolerance; + + pdlp_solver_settings_t pdlp_settings{}; + pdlp_settings.tolerances.relative_primal_tolerance = absolute_tolerance / tolerance_divisor; + pdlp_settings.tolerances.relative_dual_tolerance = absolute_tolerance / tolerance_divisor; + pdlp_settings.time_limit = lp_time_limit; + pdlp_settings.first_primal_feasible = false; + pdlp_settings.concurrent_halt = &global_concurrent_halt; + pdlp_settings.method = method_t::PDLP; + pdlp_settings.pdlp_solver_mode = pdlp_solver_mode_t::Stable2; + rmm::device_uvector lp_optimal_solution_copy(lp_optimal_solution.size(), problem_ptr->handle_ptr->get_stream()); - auto lp_result = - get_relaxed_lp_solution(*problem_ptr, lp_optimal_solution_copy, lp_state, lp_settings); + timer_t lp_timer(lp_time_limit); + auto lp_result = solve_lp_with_method(*problem_ptr, pdlp_settings, lp_timer); CUOPT_LOG_INFO("LP enum: %s", lp_result.get_termination_status_string().c_str()); { From a198738eeb896b32ab8dcad3dd5fd5941b54874d Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Wed, 12 Nov 2025 16:14:37 -0800 Subject: [PATCH 16/51] Use atomic flag to send root solution --- cpp/src/dual_simplex/branch_and_bound.cpp | 5 ++--- cpp/src/dual_simplex/branch_and_bound.hpp | 2 ++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/cpp/src/dual_simplex/branch_and_bound.cpp b/cpp/src/dual_simplex/branch_and_bound.cpp index 083598bd27..d268ef6678 100644 --- a/cpp/src/dual_simplex/branch_and_bound.cpp +++ b/cpp/src/dual_simplex/branch_and_bound.cpp @@ -1060,9 +1060,8 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut // settings_.log.printf("Validation iterations root relaxation: %d\n", validation_iters); // exit(0); - // Wait for the root relaxation solution to be set by diversity manager - while (root_relax_soln_.iterations == 0) { - std::this_thread::sleep_for(std::chrono::milliseconds(10)); + // Wait for the root relaxation solution to be sent by the diversity manager + while (!root_relaxation_solution_set_.load(std::memory_order_acquire)) { continue; } stats_.total_lp_iters = root_relax_soln_.iterations; diff --git a/cpp/src/dual_simplex/branch_and_bound.hpp b/cpp/src/dual_simplex/branch_and_bound.hpp index 9af2d18354..26cd2fd9c0 100644 --- a/cpp/src/dual_simplex/branch_and_bound.hpp +++ b/cpp/src/dual_simplex/branch_and_bound.hpp @@ -134,6 +134,7 @@ class branch_and_bound_t { root_relax_soln_.objective = objective; root_relax_soln_.user_objective = user_objective; root_relax_soln_.iterations = iterations; + root_relaxation_solution_set_.store(true, std::memory_order_release); } // Set a solution based on the user problem during the course of the solve @@ -198,6 +199,7 @@ class branch_and_bound_t { f_t root_objective_; lp_solution_t root_relax_soln_; std::vector edge_norms_; + std::atomic root_relaxation_solution_set_{false}; // Pseudocosts pseudo_costs_t pc_; From 031b7deabeb2199579e2fdc18af0f2791b957120 Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Wed, 12 Nov 2025 16:14:37 -0800 Subject: [PATCH 17/51] Use atomic flag to send root solution --- cpp/src/dual_simplex/branch_and_bound.cpp | 34 ++----------------- cpp/src/dual_simplex/branch_and_bound.hpp | 2 ++ cpp/src/dual_simplex/phase2.cpp | 38 +++++++++++----------- cpp/src/mip/diversity/diversity_manager.cu | 2 +- 4 files changed, 24 insertions(+), 52 deletions(-) diff --git a/cpp/src/dual_simplex/branch_and_bound.cpp b/cpp/src/dual_simplex/branch_and_bound.cpp index 083598bd27..1141948b6c 100644 --- a/cpp/src/dual_simplex/branch_and_bound.cpp +++ b/cpp/src/dual_simplex/branch_and_bound.cpp @@ -1060,9 +1060,8 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut // settings_.log.printf("Validation iterations root relaxation: %d\n", validation_iters); // exit(0); - // Wait for the root relaxation solution to be set by diversity manager - while (root_relax_soln_.iterations == 0) { - std::this_thread::sleep_for(std::chrono::milliseconds(10)); + // Wait for the root relaxation solution to be sent by the diversity manager + while (!root_relaxation_solution_set_.load(std::memory_order_acquire)) { continue; } stats_.total_lp_iters = root_relax_soln_.iterations; @@ -1097,35 +1096,6 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut root_vstatus_); settings_.log.printf("Crossover status: %d\n", crossover_status); - // TODO: Call dual simplex phase 2 to verify basis - validation_iters = 0; - // should probably set the cut off here lp_settings.cut_off - settings_.set_log(true); - dual::status_t lp_status = dual_phase2(2, - 0, - stats_.start_time, - original_lp_, - settings_, - root_vstatus_, - root_relax_soln_, - validation_iters, - edge_norms_); - settings_.log.printf("Validation iterations first call: %d\n", validation_iters); - - validation_iters = 0; - lp_status = dual_phase2(2, - 0, - stats_.start_time, - original_lp_, - settings_, - root_vstatus_, - root_relax_soln_, - validation_iters, - edge_norms_); - settings_.log.printf("Validation iterations second call: %d\n", validation_iters); - cuopt_assert(validation_iters < 100, "Validation iterations exceeded 10"); - exit(0); - if (crossover_status == crossover_status_t::NUMERICAL_ISSUES) { settings_.log.printf("MIP Infeasible\n"); // FIXME: rarely dual simplex detects infeasible whereas it is feasible. diff --git a/cpp/src/dual_simplex/branch_and_bound.hpp b/cpp/src/dual_simplex/branch_and_bound.hpp index 9af2d18354..26cd2fd9c0 100644 --- a/cpp/src/dual_simplex/branch_and_bound.hpp +++ b/cpp/src/dual_simplex/branch_and_bound.hpp @@ -134,6 +134,7 @@ class branch_and_bound_t { root_relax_soln_.objective = objective; root_relax_soln_.user_objective = user_objective; root_relax_soln_.iterations = iterations; + root_relaxation_solution_set_.store(true, std::memory_order_release); } // Set a solution based on the user problem during the course of the solve @@ -198,6 +199,7 @@ class branch_and_bound_t { f_t root_objective_; lp_solution_t root_relax_soln_; std::vector edge_norms_; + std::atomic root_relaxation_solution_set_{false}; // Pseudocosts pseudo_costs_t pc_; diff --git a/cpp/src/dual_simplex/phase2.cpp b/cpp/src/dual_simplex/phase2.cpp index b1fdc0fa51..e70640ad25 100644 --- a/cpp/src/dual_simplex/phase2.cpp +++ b/cpp/src/dual_simplex/phase2.cpp @@ -1920,11 +1920,11 @@ void set_primal_variables_on_bounds(const lp_problem_t& lp, const f_t fixed_tolerance = settings.fixed_tol; if (std::abs(lp.lower[j] - lp.upper[j]) < fixed_tolerance) { if (vstatus[j] != variable_status_t::NONBASIC_FIXED) { - settings.log.printf("Setting fixed variable %d to %e (current %e). vstatus %d\n", - j, - lp.lower[j], - x[j], - static_cast(vstatus[j])); + settings.log.debug("Setting fixed variable %d to %e (current %e). vstatus %d\n", + j, + lp.lower[j], + x[j], + static_cast(vstatus[j])); } x[j] = lp.lower[j]; vstatus[j] = variable_status_t::NONBASIC_FIXED; @@ -1942,7 +1942,7 @@ void set_primal_variables_on_bounds(const lp_problem_t& lp, x[j] = lp.upper[j]; } else if (z[j] > fixed_tolerance && lp.lower[j] > -inf) { if (vstatus[j] != variable_status_t::NONBASIC_LOWER) { - settings.log.printf( + settings.log.debug( "Setting nonbasic lower variable (zj %e) %d to %e (current %e). vstatus %d\n", z[j], j, @@ -1954,7 +1954,7 @@ void set_primal_variables_on_bounds(const lp_problem_t& lp, vstatus[j] = variable_status_t::NONBASIC_LOWER; } else if (z[j] < fixed_tolerance && lp.upper[j] < inf) { if (vstatus[j] != variable_status_t::NONBASIC_UPPER) { - settings.log.printf( + settings.log.debug( "Setting nonbasic upper variable (zj %e) %d to %e (current %e). vstatus %d\n", z[j], j, @@ -1967,33 +1967,33 @@ void set_primal_variables_on_bounds(const lp_problem_t& lp, } else if (lp.upper[j] == inf && lp.lower[j] > -inf && z[j] < 0) { // dual infeasible if (vstatus[j] != variable_status_t::NONBASIC_LOWER) { - settings.log.printf("Setting nonbasic lower variable %d to %e (current %e). vstatus %d\n", - j, - lp.lower[j], - x[j], - static_cast(vstatus[j])); + settings.log.debug("Setting nonbasic lower variable %d to %e (current %e). vstatus %d\n", + j, + lp.lower[j], + x[j], + static_cast(vstatus[j])); } x[j] = lp.lower[j]; vstatus[j] = variable_status_t::NONBASIC_LOWER; } else if (lp.lower[j] == -inf && lp.upper[j] < inf && z[j] > 0) { // dual infeasible if (vstatus[j] != variable_status_t::NONBASIC_UPPER) { - settings.log.printf("Setting nonbasic upper variable %d to %e (current %e). vstatus %d\n", - j, - lp.upper[j], - x[j], - static_cast(vstatus[j])); + settings.log.debug("Setting nonbasic upper variable %d to %e (current %e). vstatus %d\n", + j, + lp.upper[j], + x[j], + static_cast(vstatus[j])); } x[j] = lp.upper[j]; vstatus[j] = variable_status_t::NONBASIC_UPPER; } else if (lp.lower[j] == -inf && lp.upper[j] == inf) { x[j] = 0; // Set nonbasic free variables to 0 this overwrites previous lines if (vstatus[j] != variable_status_t::NONBASIC_FREE) { - settings.log.printf( + settings.log.debug( "Setting free variable %d to %e. vstatus %d\n", j, 0, static_cast(vstatus[j])); } vstatus[j] = variable_status_t::NONBASIC_FREE; - settings.log.printf("Setting free variable %d as nonbasic at 0\n", j); + settings.log.debug("Setting free variable %d as nonbasic at 0\n", j); } else { assert(1 == 0); } diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index 9205d3bc9c..9065480edc 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -366,7 +366,7 @@ solution_t diversity_manager_t::run_solver() pdlp_settings.time_limit = lp_time_limit; pdlp_settings.first_primal_feasible = false; pdlp_settings.concurrent_halt = &global_concurrent_halt; - pdlp_settings.method = method_t::PDLP; + pdlp_settings.method = method_t::Concurrent; pdlp_settings.pdlp_solver_mode = pdlp_solver_mode_t::Stable2; rmm::device_uvector lp_optimal_solution_copy(lp_optimal_solution.size(), From 68361a5976625eea0433d91d7b985a3b53c73c12 Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Thu, 13 Nov 2025 11:08:22 -0800 Subject: [PATCH 18/51] Disable print --- cpp/src/dual_simplex/presolve.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/dual_simplex/presolve.cpp b/cpp/src/dual_simplex/presolve.cpp index ced6c21a4d..9b1de8e9fc 100644 --- a/cpp/src/dual_simplex/presolve.cpp +++ b/cpp/src/dual_simplex/presolve.cpp @@ -831,7 +831,7 @@ void convert_user_problem(const user_problem_t& user_problem, if (verbose) { printf("Constraints < %d = %d > %d\n", less_rows, equal_rows, greater_rows); } if (user_problem.num_range_rows > 0) { - if (1 || verbose) { printf("Problem has %d range rows\n", user_problem.num_range_rows); } + if (verbose) { printf("Problem has %d range rows\n", user_problem.num_range_rows); } convert_range_rows( user_problem, row_sense, problem, less_rows, equal_rows, greater_rows, new_slacks); } From dcca90b4ead6a105855d146937b931a1e53b785b Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Thu, 13 Nov 2025 14:02:32 -0800 Subject: [PATCH 19/51] Set root solution in rins --- cpp/src/mip/diversity/diversity_manager.cu | 8 +++++ cpp/src/mip/diversity/lns/rins.cu | 34 ++++++++++++++++++++-- cpp/src/mip/diversity/lns/rins.cuh | 11 +++++++ 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index 609c6fe5d3..b3f49e79ac 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -446,12 +446,20 @@ solution_t diversity_manager_t::run_solver() auto user_obj = problem_ptr->get_user_obj_from_solver_obj(lp_result.get_objective_value()); auto iterations = lp_result.get_additional_termination_information().number_of_steps_taken; + // Set for the B&B problem_ptr->set_root_relaxation_solution_callback(host_primal, host_dual, host_reduced_costs, lp_result.get_objective_value(), user_obj, iterations); + // Set for RINS new B&B object + rins.set_root_relaxation_solution(host_primal, + host_dual, + host_reduced_costs, + lp_result.get_objective_value(), + user_obj, + iterations); } // in case the pdlp returned var boudns that are out of bounds diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index 1efc971b21..8eafa7946f 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -23,12 +23,15 @@ #include namespace cuopt::linear_programming::detail { - template rins_t::rins_t(mip_solver_context_t& context_, diversity_manager_t& dm_, rins_settings_t settings_) - : context(context_), problem_ptr(context.problem_ptr), dm(dm_), settings(settings_) + : context(context_), + problem_ptr(context.problem_ptr), + dm(dm_), + settings(settings_), + root_relax_soln_(1, 1) { fixrate = settings.default_fixrate; time_limit = settings.default_time_limit; @@ -85,6 +88,23 @@ void rins_t::enable() enabled = true; } +template +void rins_t::set_root_relaxation_solution(const std::vector& primal, + const std::vector& dual, + const std::vector& reduced_costs, + f_t objective, + f_t user_objective, + i_t iterations) +{ + root_relax_soln_.x = primal; + root_relax_soln_.y = dual; + root_relax_soln_.z = reduced_costs; + root_relax_soln_.objective = objective; + root_relax_soln_.user_objective = user_objective; + root_relax_soln_.iterations = iterations; + root_relaxation_solution_set_.store(true, std::memory_order_release); +} + template void rins_t::run_rins() { @@ -252,6 +272,16 @@ void rins_t::run_rins() dual_simplex::branch_and_bound_t branch_and_bound(branch_and_bound_problem, branch_and_bound_settings); branch_and_bound.set_initial_guess(cuopt::host_copy(fixed_assignment, rins_handle.get_stream())); + + while (!root_relaxation_solution_set_.load(std::memory_order_acquire)) { + continue; + } + branch_and_bound.set_root_relaxation_solution(root_relax_soln_.x, + root_relax_soln_.y, + root_relax_soln_.z, + root_relax_soln_.objective, + root_relax_soln_.user_objective, + root_relax_soln_.iterations); branch_and_bound_status = branch_and_bound.solve(branch_and_bound_solution); if (!std::isnan(branch_and_bound_solution.objective)) { diff --git a/cpp/src/mip/diversity/lns/rins.cuh b/cpp/src/mip/diversity/lns/rins.cuh index fcc724ebc8..559ae344f4 100644 --- a/cpp/src/mip/diversity/lns/rins.cuh +++ b/cpp/src/mip/diversity/lns/rins.cuh @@ -21,6 +21,7 @@ #include #include #include + #include #include @@ -79,6 +80,15 @@ class rins_t { void run_rins(); + void set_root_relaxation_solution(const std::vector& primal, + const std::vector& dual, + const std::vector& reduced_costs, + f_t objective, + f_t user_objective, + i_t iterations); + + cuopt::linear_programming::dual_simplex::lp_solution_t root_relax_soln_; + mip_solver_context_t& context; problem_t* problem_ptr; diversity_manager_t& dm; @@ -104,6 +114,7 @@ class rins_t { std::atomic node_count_at_last_rins{0}; std::atomic node_count_at_last_improvement{0}; std::mutex rins_mutex; + std::atomic root_relaxation_solution_set_{false}; std::unique_ptr> rins_thread; }; From 4a10885046c24f645d664f5c630f8ffd0d22e986 Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Thu, 13 Nov 2025 15:46:11 -0800 Subject: [PATCH 20/51] Only use concurrent root solve for main bb thread --- cpp/src/dual_simplex/branch_and_bound.cpp | 177 ++++++++++++--------- cpp/src/dual_simplex/branch_and_bound.hpp | 4 + cpp/src/mip/diversity/diversity_manager.cu | 7 - cpp/src/mip/diversity/lns/rins.cu | 33 +--- cpp/src/mip/diversity/lns/rins.cuh | 10 -- cpp/src/mip/solver.cu | 1 + 6 files changed, 107 insertions(+), 125 deletions(-) diff --git a/cpp/src/dual_simplex/branch_and_bound.cpp b/cpp/src/dual_simplex/branch_and_bound.cpp index 439e244335..a313bad3b5 100644 --- a/cpp/src/dual_simplex/branch_and_bound.cpp +++ b/cpp/src/dual_simplex/branch_and_bound.cpp @@ -1064,84 +1064,109 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut simplex_solver_settings_t lp_settings = settings_; lp_settings.inside_mip = 1; - // auto copy_root_relax_soln = root_relax_soln_; - // lp_status_t root_status = solve_linear_program_advanced( - // original_lp_, stats_.start_time, lp_settings, copy_root_relax_soln, root_vstatus_, - // edge_norms_); - - i_t validation_iters = 0; - // edge_norms_.clear(); - // settings_.set_log(true); - // dual::status_t lp_status = dual_phase2(2, - // 0, - // stats_.start_time, - // original_lp_, - // settings_, - // root_vstatus_, - // root_relax_soln_, - // validation_iters, - // edge_norms_); - // settings_.log.printf("Validation iterations root relaxation: %d\n", validation_iters); - // exit(0); - - // Wait for the root relaxation solution to be sent by the diversity manager - while (!root_relaxation_solution_set_.load(std::memory_order_acquire)) { - continue; - } - stats_.total_lp_iters = root_relax_soln_.iterations; - stats_.total_lp_solve_time = toc(stats_.start_time); - // Crush the root relaxation solution on converted user problem - std::vector crushed_root_x; - crush_primal_solution( - original_problem_, original_lp_, root_relax_soln_.x, new_slacks_, crushed_root_x); - std::vector crushed_root_y; - std::vector crushed_root_z; - - f_t dual_res_inf = crush_dual_solution(original_problem_, - original_lp_, - new_slacks_, - root_relax_soln_.y, - root_relax_soln_.z, - crushed_root_y, - crushed_root_z); - settings_.log.printf("Dual residual inf: %e\n", dual_res_inf); - - root_relax_soln_.x = crushed_root_x; - root_relax_soln_.y = crushed_root_y; - root_relax_soln_.z = crushed_root_z; - - // Call crossover on the crushed solution - lp_solution_t crossover_solution(original_lp_.num_rows, original_lp_.num_cols); - crossover_status_t crossover_status = crossover(original_lp_, - settings_, - root_relax_soln_, - stats_.start_time, - crossover_solution, - root_vstatus_); - settings_.log.printf("Crossover status: %d\n", crossover_status); - - if (crossover_status == crossover_status_t::NUMERICAL_ISSUES) { - settings_.log.printf("MIP Infeasible\n"); - // FIXME: rarely dual simplex detects infeasible whereas it is feasible. - // to add a small safety net, check if there is a primal solution already. - // Uncomment this if the issue with cost266-UUE is resolved - // if (settings.heuristic_preemption_callback != nullptr) { - // settings.heuristic_preemption_callback(); - // } - return mip_status_t::INFEASIBLE; - } - if (crossover_status == crossover_status_t::NUMERICAL_ISSUES) { - settings_.log.printf("MIP Unbounded\n"); - if (settings_.heuristic_preemption_callback != nullptr) { - settings_.heuristic_preemption_callback(); + if (!is_main_thread()) { + lp_status_t root_status = solve_linear_program_advanced( + original_lp_, stats_.start_time, lp_settings, root_relax_soln_, root_vstatus_, edge_norms_); + stats_.total_lp_iters = root_relax_soln_.iterations; + stats_.total_lp_solve_time = toc(stats_.start_time); + + if (root_status == lp_status_t::INFEASIBLE) { + settings_.log.printf("MIP Infeasible\n"); + // FIXME: rarely dual simplex detects infeasible whereas it is feasible. + // to add a small safety net, check if there is a primal solution already. + // Uncomment this if the issue with cost266-UUE is resolved + // if (settings.heuristic_preemption_callback != nullptr) { + // settings.heuristic_preemption_callback(); + // } + return mip_status_t::INFEASIBLE; + } + if (root_status == lp_status_t::UNBOUNDED) { + settings_.log.printf("MIP Unbounded\n"); + if (settings_.heuristic_preemption_callback != nullptr) { + settings_.heuristic_preemption_callback(); + } + return mip_status_t::UNBOUNDED; } - return mip_status_t::UNBOUNDED; - } - if (crossover_status == crossover_status_t::TIME_LIMIT || - crossover_status == crossover_status_t::CONCURRENT_LIMIT) { - status_ = mip_exploration_status_t::TIME_LIMIT; - return set_final_solution(solution, -inf); + if (root_status == lp_status_t::TIME_LIMIT) { + status_ = mip_exploration_status_t::TIME_LIMIT; + return set_final_solution(solution, -inf); + } + } else { + i_t validation_iters = 0; + // edge_norms_.clear(); + // settings_.set_log(true); + // dual::status_t lp_status = dual_phase2(2, + // 0, + // stats_.start_time, + // original_lp_, + // settings_, + // root_vstatus_, + // root_relax_soln_, + // validation_iters, + // edge_norms_); + // settings_.log.printf("Validation iterations root relaxation: %d\n", validation_iters); + // exit(0); + + // Wait for the root relaxation solution to be sent by the diversity manager + while (!root_relaxation_solution_set_.load(std::memory_order_acquire)) { + continue; + } + stats_.total_lp_iters = root_relax_soln_.iterations; + stats_.total_lp_solve_time = toc(stats_.start_time); + // Crush the root relaxation solution on converted user problem + std::vector crushed_root_x; + crush_primal_solution( + original_problem_, original_lp_, root_relax_soln_.x, new_slacks_, crushed_root_x); + std::vector crushed_root_y; + std::vector crushed_root_z; + + f_t dual_res_inf = crush_dual_solution(original_problem_, + original_lp_, + new_slacks_, + root_relax_soln_.y, + root_relax_soln_.z, + crushed_root_y, + crushed_root_z); + settings_.log.printf("Dual residual inf: %e\n", dual_res_inf); + + root_relax_soln_.x = crushed_root_x; + root_relax_soln_.y = crushed_root_y; + root_relax_soln_.z = crushed_root_z; + + // Call crossover on the crushed solution + lp_solution_t crossover_solution(original_lp_.num_rows, original_lp_.num_cols); + crossover_status_t crossover_status = crossover(original_lp_, + settings_, + root_relax_soln_, + stats_.start_time, + crossover_solution, + root_vstatus_); + settings_.log.printf("Crossover status: %d\n", crossover_status); + + if (crossover_status == crossover_status_t::NUMERICAL_ISSUES) { + settings_.log.printf("MIP Infeasible\n"); + // FIXME: rarely dual simplex detects infeasible whereas it is feasible. + // to add a small safety net, check if there is a primal solution already. + // Uncomment this if the issue with cost266-UUE is resolved + // if (settings.heuristic_preemption_callback != nullptr) { + // settings.heuristic_preemption_callback(); + // } + return mip_status_t::INFEASIBLE; + } + if (crossover_status == crossover_status_t::NUMERICAL_ISSUES) { + settings_.log.printf("MIP Unbounded\n"); + if (settings_.heuristic_preemption_callback != nullptr) { + settings_.heuristic_preemption_callback(); + } + return mip_status_t::UNBOUNDED; + } + + if (crossover_status == crossover_status_t::TIME_LIMIT || + crossover_status == crossover_status_t::CONCURRENT_LIMIT) { + status_ = mip_exploration_status_t::TIME_LIMIT; + return set_final_solution(solution, -inf); + } } assert(root_vstatus_.size() == original_lp_.num_cols); diff --git a/cpp/src/dual_simplex/branch_and_bound.hpp b/cpp/src/dual_simplex/branch_and_bound.hpp index 26cd2fd9c0..49dfc39d43 100644 --- a/cpp/src/dual_simplex/branch_and_bound.hpp +++ b/cpp/src/dual_simplex/branch_and_bound.hpp @@ -140,6 +140,8 @@ class branch_and_bound_t { // Set a solution based on the user problem during the course of the solve void set_new_solution(const std::vector& solution); + void set_main_thread(bool main_thread) { main_thread_ = main_thread; } + // Repair a low-quality solution from the heuristics. bool repair_solution(const std::vector& leaf_edge_norms, const std::vector& potential_solution, @@ -149,6 +151,7 @@ class branch_and_bound_t { f_t get_upper_bound(); f_t get_lower_bound(); i_t get_heap_size(); + bool is_main_thread() const { return main_thread_; } // The main entry routine. Returns the solver status and populates solution with the incumbent. mip_status_t solve(mip_solution_t& solution); @@ -200,6 +203,7 @@ class branch_and_bound_t { lp_solution_t root_relax_soln_; std::vector edge_norms_; std::atomic root_relaxation_solution_set_{false}; + bool main_thread_{false}; // Pseudocosts pseudo_costs_t pc_; diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index b3f49e79ac..98963c6301 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -453,13 +453,6 @@ solution_t diversity_manager_t::run_solver() lp_result.get_objective_value(), user_obj, iterations); - // Set for RINS new B&B object - rins.set_root_relaxation_solution(host_primal, - host_dual, - host_reduced_costs, - lp_result.get_objective_value(), - user_obj, - iterations); } // in case the pdlp returned var boudns that are out of bounds diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index 8eafa7946f..078bf60720 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -27,11 +27,7 @@ template rins_t::rins_t(mip_solver_context_t& context_, diversity_manager_t& dm_, rins_settings_t settings_) - : context(context_), - problem_ptr(context.problem_ptr), - dm(dm_), - settings(settings_), - root_relax_soln_(1, 1) + : context(context_), problem_ptr(context.problem_ptr), dm(dm_), settings(settings_) { fixrate = settings.default_fixrate; time_limit = settings.default_time_limit; @@ -88,23 +84,6 @@ void rins_t::enable() enabled = true; } -template -void rins_t::set_root_relaxation_solution(const std::vector& primal, - const std::vector& dual, - const std::vector& reduced_costs, - f_t objective, - f_t user_objective, - i_t iterations) -{ - root_relax_soln_.x = primal; - root_relax_soln_.y = dual; - root_relax_soln_.z = reduced_costs; - root_relax_soln_.objective = objective; - root_relax_soln_.user_objective = user_objective; - root_relax_soln_.iterations = iterations; - root_relaxation_solution_set_.store(true, std::memory_order_release); -} - template void rins_t::run_rins() { @@ -272,16 +251,6 @@ void rins_t::run_rins() dual_simplex::branch_and_bound_t branch_and_bound(branch_and_bound_problem, branch_and_bound_settings); branch_and_bound.set_initial_guess(cuopt::host_copy(fixed_assignment, rins_handle.get_stream())); - - while (!root_relaxation_solution_set_.load(std::memory_order_acquire)) { - continue; - } - branch_and_bound.set_root_relaxation_solution(root_relax_soln_.x, - root_relax_soln_.y, - root_relax_soln_.z, - root_relax_soln_.objective, - root_relax_soln_.user_objective, - root_relax_soln_.iterations); branch_and_bound_status = branch_and_bound.solve(branch_and_bound_solution); if (!std::isnan(branch_and_bound_solution.objective)) { diff --git a/cpp/src/mip/diversity/lns/rins.cuh b/cpp/src/mip/diversity/lns/rins.cuh index 559ae344f4..64be351a87 100644 --- a/cpp/src/mip/diversity/lns/rins.cuh +++ b/cpp/src/mip/diversity/lns/rins.cuh @@ -80,15 +80,6 @@ class rins_t { void run_rins(); - void set_root_relaxation_solution(const std::vector& primal, - const std::vector& dual, - const std::vector& reduced_costs, - f_t objective, - f_t user_objective, - i_t iterations); - - cuopt::linear_programming::dual_simplex::lp_solution_t root_relax_soln_; - mip_solver_context_t& context; problem_t* problem_ptr; diversity_manager_t& dm; @@ -114,7 +105,6 @@ class rins_t { std::atomic node_count_at_last_rins{0}; std::atomic node_count_at_last_improvement{0}; std::mutex rins_mutex; - std::atomic root_relaxation_solution_set_{false}; std::unique_ptr> rins_thread; }; diff --git a/cpp/src/mip/solver.cu b/cpp/src/mip/solver.cu index f0a05b7254..40eda98346 100644 --- a/cpp/src/mip/solver.cu +++ b/cpp/src/mip/solver.cu @@ -206,6 +206,7 @@ solution_t mip_solver_t::run_solver() branch_and_bound = std::make_unique>( branch_and_bound_problem, branch_and_bound_settings); context.branch_and_bound_ptr = branch_and_bound.get(); + branch_and_bound->set_main_thread(true); // Set the primal heuristics -> branch and bound callback context.problem_ptr->branch_and_bound_callback = From 26ccae5ed1aed319fb45a0592e01588d21d44946 Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Fri, 14 Nov 2025 14:51:53 -0800 Subject: [PATCH 21/51] Fix asserts --- cpp/src/dual_simplex/right_looking_lu.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/src/dual_simplex/right_looking_lu.cpp b/cpp/src/dual_simplex/right_looking_lu.cpp index 59dac3ac90..a63c1181fa 100644 --- a/cpp/src/dual_simplex/right_looking_lu.cpp +++ b/cpp/src/dual_simplex/right_looking_lu.cpp @@ -52,12 +52,12 @@ i_t initialize_degree_data(const csc_matrix_t& A, } for (i_t k = 0; k < n; ++k) { - assert(Cdegree[k] <= n && Cdegree[k] >= 0); + assert(Cdegree[k] <= m && Cdegree[k] >= 0); col_count[Cdegree[k]].push_back(k); } for (i_t k = 0; k < m; ++k) { - assert(Rdegree[k] <= m && Rdegree[k] >= 0); + assert(Rdegree[k] <= n && Rdegree[k] >= 0); row_count[Rdegree[k]].push_back(k); if (Rdegree[k] == 0) { constexpr bool verbose = false; From 093cbcb5f78e746c2b0cddaf3b8c37f5f9a26ceb Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Fri, 14 Nov 2025 14:54:06 -0800 Subject: [PATCH 22/51] Disable rins logs --- cpp/src/mip/diversity/lns/rins.cu | 1 + 1 file changed, 1 insertion(+) diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index 078bf60720..a37155fe4e 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -243,6 +243,7 @@ void rins_t::run_rins() branch_and_bound_settings.num_threads = 2; branch_and_bound_settings.num_bfs_threads = 1; branch_and_bound_settings.num_diving_threads = 1; + branch_and_bound_settings.log.log = false; branch_and_bound_settings.log.log_prefix = "[RINS] "; branch_and_bound_settings.solution_callback = [this, &rins_solution_queue]( std::vector& solution, f_t objective) { From ca7c9d379f55164ef01b6e0fca6ed0509303ac22 Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Mon, 17 Nov 2025 10:29:44 -0800 Subject: [PATCH 23/51] Recompute dual solution after primal push --- cpp/src/dual_simplex/crossover.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/cpp/src/dual_simplex/crossover.cpp b/cpp/src/dual_simplex/crossover.cpp index 2e7bea111a..f5517c01ec 100644 --- a/cpp/src/dual_simplex/crossover.cpp +++ b/cpp/src/dual_simplex/crossover.cpp @@ -1204,6 +1204,7 @@ crossover_status_t crossover(const lp_problem_t& lp, lp, settings, start_time, solution, ft, basic_list, nonbasic_list, superbasic_list, vstatus); if (primal_push_status < 0) { return return_to_status(primal_push_status); } print_crossover_info(lp, settings, vstatus, solution, "Primal push complete"); + compute_dual_solution_from_basis(lp, ft, basic_list, nonbasic_list, solution.y, solution.z); } else { settings.log.printf("No primal push needed. No superbasic variables\n"); } From 5741a173520764501d38015f8d8d3a99a7d1b546 Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Sun, 30 Nov 2025 16:28:11 -0800 Subject: [PATCH 24/51] Fix merge conflicts --- cpp/src/dual_simplex/branch_and_bound.cpp | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/cpp/src/dual_simplex/branch_and_bound.cpp b/cpp/src/dual_simplex/branch_and_bound.cpp index 40f0c81216..586eddd1e7 100644 --- a/cpp/src/dual_simplex/branch_and_bound.cpp +++ b/cpp/src/dual_simplex/branch_and_bound.cpp @@ -1238,10 +1238,14 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut simplex_solver_settings_t lp_settings = settings_; lp_settings.inside_mip = 1; if (!is_main_thread()) { - lp_status_t root_status = solve_linear_program_advanced( - original_lp_, stats_.start_time, lp_settings, root_relax_soln_, root_vstatus_, edge_norms_); - stats_.total_lp_iters = root_relax_soln_.iterations; - stats_.total_lp_solve_time = toc(stats_.start_time); + lp_status_t root_status = solve_linear_program_advanced(original_lp_, + exploration_stats_.start_time, + lp_settings, + root_relax_soln_, + root_vstatus_, + edge_norms_); + exploration_stats_.total_lp_iters = root_relax_soln_.iterations; + exploration_stats_.total_lp_solve_time = toc(exploration_stats_.start_time); if (root_status == lp_status_t::INFEASIBLE) { settings_.log.printf("MIP Infeasible\n"); @@ -1262,7 +1266,7 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut } if (root_status == lp_status_t::TIME_LIMIT) { - status_ = mip_exploration_status_t::TIME_LIMIT; + solver_status_ = mip_exploration_status_t::TIME_LIMIT; return set_final_solution(solution, -inf); } } else { @@ -1285,8 +1289,8 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut while (!root_relaxation_solution_set_.load(std::memory_order_acquire)) { continue; } - stats_.total_lp_iters = root_relax_soln_.iterations; - stats_.total_lp_solve_time = toc(stats_.start_time); + exploration_stats_.total_lp_iters = root_relax_soln_.iterations; + exploration_stats_.total_lp_solve_time = toc(exploration_stats_.start_time); // Crush the root relaxation solution on converted user problem std::vector crushed_root_x; crush_primal_solution( @@ -1312,7 +1316,7 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut crossover_status_t crossover_status = crossover(original_lp_, settings_, root_relax_soln_, - stats_.start_time, + exploration_stats_.start_time, crossover_solution, root_vstatus_); settings_.log.printf("Crossover status: %d\n", crossover_status); @@ -1337,7 +1341,7 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut if (crossover_status == crossover_status_t::TIME_LIMIT || crossover_status == crossover_status_t::CONCURRENT_LIMIT) { - status_ = mip_exploration_status_t::TIME_LIMIT; + solver_status_ = mip_exploration_status_t::TIME_LIMIT; return set_final_solution(solution, -inf); } } From 8b7b6a61e7a70e82d52beff4607b1a007dc77615 Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Sun, 30 Nov 2025 17:23:24 -0800 Subject: [PATCH 25/51] Fix default value for num cpu thread in benchmark script --- benchmarks/linear_programming/run_mps_files.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks/linear_programming/run_mps_files.sh b/benchmarks/linear_programming/run_mps_files.sh index 988c1eaa29..365334c4df 100755 --- a/benchmarks/linear_programming/run_mps_files.sh +++ b/benchmarks/linear_programming/run_mps_files.sh @@ -183,7 +183,7 @@ OUTPUT_DIR=${OUTPUT_DIR:-.} RELAXATION=${RELAXATION:-false} MIP_HEURISTICS_ONLY=${MIP_HEURISTICS_ONLY:-false} WRITE_LOG_FILE=${WRITE_LOG_FILE:-false} -NUM_CPU_THREADS=${NUM_CPU_THREADS:-1} +NUM_CPU_THREADS=${NUM_CPU_THREADS:--1} PRESOLVE=${PRESOLVE:-true} BATCH_NUM=${BATCH_NUM:-0} N_BATCHES=${N_BATCHES:-1} From 558bb109832a411e1fc564b5e5d96a2845eb94e8 Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Mon, 1 Dec 2025 16:08:40 -0800 Subject: [PATCH 26/51] Implement crossover as stopping criteria --- cpp/CMakeLists.txt | 1 + .../pdlp/solver_settings.hpp | 2 +- .../cuopt/linear_programming/solve.hpp | 6 +- cpp/src/dual_simplex/branch_and_bound.cpp | 169 ++++++++++-------- cpp/src/dual_simplex/branch_and_bound.hpp | 22 ++- cpp/src/linear_programming/solve.cu | 50 +++--- cpp/src/linear_programming/solve.cuh | 3 +- cpp/src/mip/diversity/diversity_manager.cu | 1 + cpp/src/mip/solver.cu | 4 + 9 files changed, 155 insertions(+), 103 deletions(-) diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 4391c53df7..8b5d7c7be2 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -440,6 +440,7 @@ target_link_libraries(cuopt_cli cuopt OpenMP::OpenMP_CXX ${CUDSS_LIBRARIES} + TBB::tbb PRIVATE ) set_property(TARGET cuopt_cli PROPERTY INSTALL_RPATH "$ORIGIN/../${lib_dir}") diff --git a/cpp/include/cuopt/linear_programming/pdlp/solver_settings.hpp b/cpp/include/cuopt/linear_programming/pdlp/solver_settings.hpp index 441d8600d7..2ee74b4a1f 100644 --- a/cpp/include/cuopt/linear_programming/pdlp/solver_settings.hpp +++ b/cpp/include/cuopt/linear_programming/pdlp/solver_settings.hpp @@ -212,7 +212,7 @@ class pdlp_solver_settings_t { bool dual_postsolve{true}; method_t method{method_t::Concurrent}; // For concurrent termination - volatile int* concurrent_halt; + volatile int* concurrent_halt{nullptr}; static constexpr f_t minimal_absolute_tolerance = 1.0e-12; private: diff --git a/cpp/include/cuopt/linear_programming/solve.hpp b/cpp/include/cuopt/linear_programming/solve.hpp index 364fee30aa..041ada2e2f 100644 --- a/cpp/include/cuopt/linear_programming/solve.hpp +++ b/cpp/include/cuopt/linear_programming/solve.hpp @@ -43,7 +43,8 @@ optimization_problem_solution_t solve_lp( pdlp_solver_settings_t const& settings = pdlp_solver_settings_t{}, bool problem_checking = true, bool use_pdlp_solver_mode = true, - bool is_batch_mode = false); + bool is_batch_mode = false, + bool inside_mip = false); /** * @brief Linear programming solve function. @@ -69,7 +70,8 @@ optimization_problem_solution_t solve_lp( const cuopt::mps_parser::mps_data_model_t& mps_data_model, pdlp_solver_settings_t const& settings = pdlp_solver_settings_t{}, bool problem_checking = true, - bool use_pdlp_solver_mode = true); + bool use_pdlp_solver_mode = true, + bool inside_mip = false); /** * @brief Mixed integer programming solve function. diff --git a/cpp/src/dual_simplex/branch_and_bound.cpp b/cpp/src/dual_simplex/branch_and_bound.cpp index 586eddd1e7..b22342bcbf 100644 --- a/cpp/src/dual_simplex/branch_and_bound.cpp +++ b/cpp/src/dual_simplex/branch_and_bound.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -218,6 +219,7 @@ branch_and_bound_t::branch_and_bound_t( original_lp_(user_problem.handle_ptr, 1, 1, 1), incumbent_(1), root_relax_soln_(1, 1), + root_crossover_soln_(1, 1), pc_(1), solver_status_(mip_exploration_status_t::UNSET) { @@ -1237,6 +1239,7 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut simplex_solver_settings_t lp_settings = settings_; lp_settings.inside_mip = 1; + // RINS/SUBMIP path if (!is_main_thread()) { lp_status_t root_status = solve_linear_program_advanced(original_lp_, exploration_stats_.start_time, @@ -1270,79 +1273,105 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut return set_final_solution(solution, -inf); } } else { - i_t validation_iters = 0; - // edge_norms_.clear(); - // settings_.set_log(true); - // dual::status_t lp_status = dual_phase2(2, - // 0, - // stats_.start_time, - // original_lp_, - // settings_, - // root_vstatus_, - // root_relax_soln_, - // validation_iters, - // edge_norms_); - // settings_.log.printf("Validation iterations root relaxation: %d\n", validation_iters); - // exit(0); - - // Wait for the root relaxation solution to be sent by the diversity manager - while (!root_relaxation_solution_set_.load(std::memory_order_acquire)) { + // Root node path + std::future root_status_future; + root_status_future = std::async(std::launch::async, + &solve_linear_program_advanced, + std::ref(original_lp_), + exploration_stats_.start_time, + std::ref(lp_settings), + std::ref(root_relax_soln_), + std::ref(root_vstatus_), + std::ref(edge_norms_)); + // Wait for the root relaxation solution to be sent by the diversity manager or dual simplex to + // finish + while (!root_crossover_solution_set_.load(std::memory_order_acquire) && + global_root_concurrent_halt == 0) { continue; } - exploration_stats_.total_lp_iters = root_relax_soln_.iterations; - exploration_stats_.total_lp_solve_time = toc(exploration_stats_.start_time); - // Crush the root relaxation solution on converted user problem - std::vector crushed_root_x; - crush_primal_solution( - original_problem_, original_lp_, root_relax_soln_.x, new_slacks_, crushed_root_x); - std::vector crushed_root_y; - std::vector crushed_root_z; - - f_t dual_res_inf = crush_dual_solution(original_problem_, - original_lp_, - new_slacks_, - root_relax_soln_.y, - root_relax_soln_.z, - crushed_root_y, - crushed_root_z); - settings_.log.printf("Dual residual inf: %e\n", dual_res_inf); - - root_relax_soln_.x = crushed_root_x; - root_relax_soln_.y = crushed_root_y; - root_relax_soln_.z = crushed_root_z; - - // Call crossover on the crushed solution - lp_solution_t crossover_solution(original_lp_.num_rows, original_lp_.num_cols); - crossover_status_t crossover_status = crossover(original_lp_, - settings_, - root_relax_soln_, - exploration_stats_.start_time, - crossover_solution, - root_vstatus_); - settings_.log.printf("Crossover status: %d\n", crossover_status); - - if (crossover_status == crossover_status_t::NUMERICAL_ISSUES) { - settings_.log.printf("MIP Infeasible\n"); - // FIXME: rarely dual simplex detects infeasible whereas it is feasible. - // to add a small safety net, check if there is a primal solution already. - // Uncomment this if the issue with cost266-UUE is resolved - // if (settings.heuristic_preemption_callback != nullptr) { - // settings.heuristic_preemption_callback(); - // } - return mip_status_t::INFEASIBLE; - } - if (crossover_status == crossover_status_t::NUMERICAL_ISSUES) { - settings_.log.printf("MIP Unbounded\n"); - if (settings_.heuristic_preemption_callback != nullptr) { - settings_.heuristic_preemption_callback(); - } - return mip_status_t::UNBOUNDED; - } - if (crossover_status == crossover_status_t::TIME_LIMIT || - crossover_status == crossover_status_t::CONCURRENT_LIMIT) { - solver_status_ = mip_exploration_status_t::TIME_LIMIT; - return set_final_solution(solution, -inf); + if (root_crossover_solution_set_.load(std::memory_order_acquire)) { + exploration_stats_.total_lp_iters = root_crossover_soln_.iterations; + exploration_stats_.total_lp_solve_time = toc(exploration_stats_.start_time); + // Crush the root relaxation solution on converted user problem + std::vector crushed_root_x; + crush_primal_solution( + original_problem_, original_lp_, root_crossover_soln_.x, new_slacks_, crushed_root_x); + std::vector crushed_root_y; + std::vector crushed_root_z; + + f_t dual_res_inf = crush_dual_solution(original_problem_, + original_lp_, + new_slacks_, + root_crossover_soln_.y, + root_crossover_soln_.z, + crushed_root_y, + crushed_root_z); + settings_.log.printf("Dual residual inf: %e\n", dual_res_inf); + + root_crossover_soln_.x = crushed_root_x; + root_crossover_soln_.y = crushed_root_y; + root_crossover_soln_.z = crushed_root_z; + + // Call crossover on the crushed solution + crossover_status_t crossover_status = crossover(original_lp_, + settings_, + root_crossover_soln_, + exploration_stats_.start_time, + root_crossover_soln_, + crossover_vstatus_); + settings_.log.printf("Crossover status: %d\n", crossover_status); + + // Check if crossover was stopped by dual simplex + if (crossover_status == crossover_status_t::CONCURRENT_LIMIT) { + exploration_stats_.total_lp_iters = root_crossover_soln_.iterations; + exploration_stats_.total_lp_solve_time = toc(exploration_stats_.start_time); + auto root_status = root_status_future.get(); + + if (root_status == lp_status_t::INFEASIBLE) { + settings_.log.printf("MIP Infeasible\n"); + return mip_status_t::INFEASIBLE; + } + if (root_status == lp_status_t::UNBOUNDED) { + settings_.log.printf("MIP Unbounded\n"); + if (settings_.heuristic_preemption_callback != nullptr) { + settings_.heuristic_preemption_callback(); + } + return mip_status_t::UNBOUNDED; + } + + if (root_status == lp_status_t::TIME_LIMIT) { + solver_status_ = mip_exploration_status_t::TIME_LIMIT; + return set_final_solution(solution, -inf); + } + } else { + // Solution was found by crossover + if (crossover_status == crossover_status_t::NUMERICAL_ISSUES) { + settings_.log.printf("MIP Infeasible\n"); + // FIXME: rarely dual simplex detects infeasible whereas it is feasible. + // to add a small safety net, check if there is a primal solution already. + // Uncomment this if the issue with cost266-UUE is resolved + // if (settings.heuristic_preemption_callback != nullptr) { + // settings.heuristic_preemption_callback(); + // } + return mip_status_t::INFEASIBLE; + } + if (crossover_status == crossover_status_t::NUMERICAL_ISSUES) { + settings_.log.printf("MIP Unbounded\n"); + if (settings_.heuristic_preemption_callback != nullptr) { + settings_.heuristic_preemption_callback(); + } + return mip_status_t::UNBOUNDED; + } + + if (crossover_status == crossover_status_t::TIME_LIMIT) { + solver_status_ = mip_exploration_status_t::TIME_LIMIT; + return set_final_solution(solution, -inf); + } + } + // Override the root relaxation solution with the crossover solution + root_relax_soln_ = root_crossover_soln_; + root_vstatus_ = crossover_vstatus_; } } diff --git a/cpp/src/dual_simplex/branch_and_bound.hpp b/cpp/src/dual_simplex/branch_and_bound.hpp index 1abe072f0a..b5d4d33616 100644 --- a/cpp/src/dual_simplex/branch_and_bound.hpp +++ b/cpp/src/dual_simplex/branch_and_bound.hpp @@ -61,6 +61,8 @@ enum class thread_type_t { DIVING = 1, }; +extern volatile int global_root_concurrent_halt; + template class bounds_strengthening_t; @@ -87,14 +89,14 @@ class branch_and_bound_t { f_t user_objective, i_t iterations) { - root_relax_soln_.x = primal; - root_relax_soln_.y = dual; - root_relax_soln_.z = reduced_costs; - root_objective_ = objective; - root_relax_soln_.objective = objective; - root_relax_soln_.user_objective = user_objective; - root_relax_soln_.iterations = iterations; - root_relaxation_solution_set_.store(true, std::memory_order_release); + root_crossover_soln_.x = primal; + root_crossover_soln_.y = dual; + root_crossover_soln_.z = reduced_costs; + root_objective_ = objective; + root_crossover_soln_.objective = objective; + root_crossover_soln_.user_objective = user_objective; + root_crossover_soln_.iterations = iterations; + root_crossover_solution_set_.store(true, std::memory_order_release); } // Set a solution based on the user problem during the course of the solve @@ -159,10 +161,12 @@ class branch_and_bound_t { // Variables for the root node in the search tree. std::vector root_vstatus_; + std::vector crossover_vstatus_; f_t root_objective_; lp_solution_t root_relax_soln_; + lp_solution_t root_crossover_soln_; std::vector edge_norms_; - std::atomic root_relaxation_solution_set_{false}; + std::atomic root_crossover_solution_set_{false}; bool main_thread_{false}; // Pseudocosts diff --git a/cpp/src/linear_programming/solve.cu b/cpp/src/linear_programming/solve.cu index 7f9d0670e5..127a5e3d7f 100644 --- a/cpp/src/linear_programming/solve.cu +++ b/cpp/src/linear_programming/solve.cu @@ -645,7 +645,8 @@ optimization_problem_solution_t run_concurrent( detail::problem_t& problem, pdlp_solver_settings_t const& settings, const timer_t& timer, - bool is_batch_mode) + bool is_batch_mode, + bool inside_mip) { CUOPT_LOG_INFO("Running concurrent\n"); timer_t timer_concurrent(timer.remaining_time()); @@ -654,9 +655,10 @@ optimization_problem_solution_t run_concurrent( pdlp_solver_settings_t settings_pdlp(settings, problem.handle_ptr->get_stream()); // Set the concurrent halt pointer - global_concurrent_halt = 0; - settings_pdlp.concurrent_halt = &global_concurrent_halt; - + global_concurrent_halt = 0; + if (settings.concurrent_halt == nullptr) { + settings_pdlp.concurrent_halt = &global_concurrent_halt; + } // Initialize the dual simplex structures before we run PDLP. // Otherwise, CUDA API calls to the problem stream may occur in both threads and throw graph // capture off @@ -671,12 +673,14 @@ optimization_problem_solution_t run_concurrent( std::unique_ptr< std::tuple, dual_simplex::lp_status_t, f_t, f_t, f_t>> sol_dual_simplex_ptr; - std::thread dual_simplex_thread(run_dual_simplex_thread, - std::ref(dual_simplex_problem), - std::ref(settings_pdlp), - std::ref(sol_dual_simplex_ptr), - std::ref(timer)); - + std::thread dual_simplex_thread; + if (!inside_mip) { + dual_simplex_thread = std::thread(run_dual_simplex_thread, + std::ref(dual_simplex_problem), + std::ref(settings_pdlp), + std::ref(sol_dual_simplex_ptr), + std::ref(timer)); + } dual_simplex::user_problem_t barrier_problem = dual_simplex_problem; // Create a thread for barrier std::unique_ptr< @@ -692,7 +696,7 @@ optimization_problem_solution_t run_concurrent( auto sol_pdlp = run_pdlp(problem, settings_pdlp, timer, is_batch_mode); // Wait for dual simplex thread to finish - dual_simplex_thread.join(); + if (!inside_mip) { dual_simplex_thread.join(); } // Wait for barrier thread to finish barrier_handle.sync_stream(); @@ -759,14 +763,15 @@ optimization_problem_solution_t solve_lp_with_method( detail::problem_t& problem, pdlp_solver_settings_t const& settings, const timer_t& timer, - bool is_batch_mode) + bool is_batch_mode, + bool inside_mip) { if (settings.method == method_t::DualSimplex) { return run_dual_simplex(problem, settings, timer); } else if (settings.method == method_t::Barrier) { return run_barrier(problem, settings, timer); } else if (settings.method == method_t::Concurrent) { - return run_concurrent(problem, settings, timer, is_batch_mode); + return run_concurrent(problem, settings, timer, is_batch_mode, inside_mip); } else { return run_pdlp(problem, settings, timer, is_batch_mode); } @@ -777,7 +782,8 @@ optimization_problem_solution_t solve_lp(optimization_problem_t const& settings, bool problem_checking, bool use_pdlp_solver_mode, - bool is_batch_mode) + bool is_batch_mode, + bool inside_mip) { try { // Create log stream for file logging and add it to default logger @@ -858,7 +864,7 @@ optimization_problem_solution_t solve_lp(optimization_problem_tget_stream()); - auto solution = solve_lp_with_method(problem, settings, lp_timer, is_batch_mode); + auto solution = solve_lp_with_method(problem, settings, lp_timer, is_batch_mode, inside_mip); if (run_presolve) { auto primal_solution = cuopt::device_copy(solution.get_primal_solution(), @@ -992,10 +998,11 @@ optimization_problem_solution_t solve_lp( const cuopt::mps_parser::mps_data_model_t& mps_data_model, pdlp_solver_settings_t const& settings, bool problem_checking, - bool use_pdlp_solver_mode) + bool use_pdlp_solver_mode, + bool inside_mip) { auto op_problem = mps_data_model_to_optimization_problem(handle_ptr, mps_data_model); - return solve_lp(op_problem, settings, problem_checking, use_pdlp_solver_mode); + return solve_lp(op_problem, settings, problem_checking, use_pdlp_solver_mode, inside_mip); } #define INSTANTIATE(F_TYPE) \ @@ -1004,20 +1011,23 @@ optimization_problem_solution_t solve_lp( pdlp_solver_settings_t const& settings, \ bool problem_checking, \ bool use_pdlp_solver_mode, \ - bool is_batch_mode); \ + bool is_batch_mode, \ + bool inside_mip); \ \ template optimization_problem_solution_t solve_lp( \ raft::handle_t const* handle_ptr, \ const cuopt::mps_parser::mps_data_model_t& mps_data_model, \ pdlp_solver_settings_t const& settings, \ bool problem_checking, \ - bool use_pdlp_solver_mode); \ + bool use_pdlp_solver_mode, \ + bool inside_mip); \ \ template optimization_problem_solution_t solve_lp_with_method( \ detail::problem_t& problem, \ pdlp_solver_settings_t const& settings, \ const timer_t& timer, \ - bool is_batch_mode); \ + bool is_batch_mode, \ + bool inside_mip); \ \ template optimization_problem_t mps_data_model_to_optimization_problem( \ raft::handle_t const* handle_ptr, \ diff --git a/cpp/src/linear_programming/solve.cuh b/cpp/src/linear_programming/solve.cuh index a3c3240f40..ab29c6f9e6 100644 --- a/cpp/src/linear_programming/solve.cuh +++ b/cpp/src/linear_programming/solve.cuh @@ -25,7 +25,8 @@ cuopt::linear_programming::optimization_problem_solution_t solve_lp_wi detail::problem_t& problem, pdlp_solver_settings_t const& settings, const timer_t& timer, - bool is_batch_mode = false); + bool is_batch_mode = false, + bool inside_mip = false); template void set_pdlp_solver_mode(pdlp_solver_settings_t const& settings); diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index 98963c6301..3189c1ae79 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -367,6 +367,7 @@ solution_t diversity_manager_t::run_solver() pdlp_settings.tolerances.relative_dual_tolerance = absolute_tolerance / tolerance_divisor; pdlp_settings.time_limit = lp_time_limit; pdlp_settings.first_primal_feasible = false; + global_concurrent_halt = 0; pdlp_settings.concurrent_halt = &global_concurrent_halt; pdlp_settings.method = method_t::Concurrent; pdlp_settings.pdlp_solver_mode = pdlp_solver_mode_t::Stable2; diff --git a/cpp/src/mip/solver.cu b/cpp/src/mip/solver.cu index 40eda98346..d46941c621 100644 --- a/cpp/src/mip/solver.cu +++ b/cpp/src/mip/solver.cu @@ -27,6 +27,8 @@ #include #include +volatile int cuopt::linear_programming::dual_simplex::global_root_concurrent_halt = 0; + namespace cuopt::linear_programming::detail { // This serves as both a warm up but also a mandatory initial call to setup cuSparse and cuBLAS @@ -150,6 +152,8 @@ solution_t mip_solver_t::run_solver() std::future branch_and_bound_status_future; dual_simplex::user_problem_t branch_and_bound_problem(context.problem_ptr->handle_ptr); dual_simplex::simplex_solver_settings_t branch_and_bound_settings; + dual_simplex::global_root_concurrent_halt = 0; + branch_and_bound_settings.concurrent_halt = &dual_simplex::global_root_concurrent_halt; std::unique_ptr> branch_and_bound; branch_and_bound_solution_helper_t solution_helper(&dm, branch_and_bound_settings); dual_simplex::mip_solution_t branch_and_bound_solution(1); From 62f33b272019efe886205cb292bebf989beaa562 Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Tue, 2 Dec 2025 11:25:44 -0800 Subject: [PATCH 27/51] Wip debugging --- cpp/src/dual_simplex/branch_and_bound.cpp | 2 ++ cpp/src/mip/diversity/diversity_manager.cu | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/cpp/src/dual_simplex/branch_and_bound.cpp b/cpp/src/dual_simplex/branch_and_bound.cpp index b22342bcbf..9d15a68ac5 100644 --- a/cpp/src/dual_simplex/branch_and_bound.cpp +++ b/cpp/src/dual_simplex/branch_and_bound.cpp @@ -1275,6 +1275,8 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut } else { // Root node path std::future root_status_future; + settings_.log.printf("Async call to dual simplex\n"); + settings_.log.printf("&global_root_concurrent_halt: %p\n", &global_root_concurrent_halt); root_status_future = std::async(std::launch::async, &solve_linear_program_advanced, std::ref(original_lp_), diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index 3189c1ae79..3530f43f9c 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -375,7 +375,10 @@ solution_t diversity_manager_t::run_solver() rmm::device_uvector lp_optimal_solution_copy(lp_optimal_solution.size(), problem_ptr->handle_ptr->get_stream()); timer_t lp_timer(lp_time_limit); - auto lp_result = solve_lp_with_method(*problem_ptr, pdlp_settings, lp_timer); + auto const is_batch = false; + auto const inside_mip = true; + auto lp_result = + solve_lp_with_method(*problem_ptr, pdlp_settings, lp_timer, is_batch, inside_mip); CUOPT_LOG_INFO("LP enum: %s", lp_result.get_termination_status_string().c_str()); { From 359ddff1d30e9c22dda286012d2918324e2b087f Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Tue, 2 Dec 2025 14:16:43 -0800 Subject: [PATCH 28/51] Disable dual simplex in concurrent mode inside mip --- cpp/src/linear_programming/solve.cu | 24 +++++++++++++--------- cpp/src/mip/diversity/diversity_manager.cu | 1 - 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/cpp/src/linear_programming/solve.cu b/cpp/src/linear_programming/solve.cu index 127a5e3d7f..d05c15067b 100644 --- a/cpp/src/linear_programming/solve.cu +++ b/cpp/src/linear_programming/solve.cu @@ -703,13 +703,16 @@ optimization_problem_solution_t run_concurrent( barrier_thread.join(); // copy the dual simplex solution to the device - auto sol_dual_simplex = convert_dual_simplex_sol(problem, - std::get<0>(*sol_dual_simplex_ptr), - std::get<1>(*sol_dual_simplex_ptr), - std::get<2>(*sol_dual_simplex_ptr), - std::get<3>(*sol_dual_simplex_ptr), - std::get<4>(*sol_dual_simplex_ptr), - 0); + auto sol_dual_simplex = + !inside_mip ? convert_dual_simplex_sol(problem, + std::get<0>(*sol_dual_simplex_ptr), + std::get<1>(*sol_dual_simplex_ptr), + std::get<2>(*sol_dual_simplex_ptr), + std::get<3>(*sol_dual_simplex_ptr), + std::get<4>(*sol_dual_simplex_ptr), + 0) + : optimization_problem_solution_t{ + pdlp_termination_status_t::ConcurrentLimit, problem.handle_ptr->get_stream()}; // copy the barrier solution to the device auto sol_barrier = convert_dual_simplex_sol(problem, @@ -724,9 +727,10 @@ optimization_problem_solution_t run_concurrent( CUOPT_LOG_INFO( "Concurrent time: %.3fs, total time %.3fs", timer_concurrent.elapsed_time(), end_time); // Check status to see if we should return the pdlp solution or the dual simplex solution - if (sol_dual_simplex.get_termination_status() == pdlp_termination_status_t::Optimal || - sol_dual_simplex.get_termination_status() == pdlp_termination_status_t::PrimalInfeasible || - sol_dual_simplex.get_termination_status() == pdlp_termination_status_t::DualInfeasible) { + if (!inside_mip && + (sol_dual_simplex.get_termination_status() == pdlp_termination_status_t::Optimal || + sol_dual_simplex.get_termination_status() == pdlp_termination_status_t::PrimalInfeasible || + sol_dual_simplex.get_termination_status() == pdlp_termination_status_t::DualInfeasible)) { CUOPT_LOG_INFO("Solved with dual simplex"); sol_pdlp.copy_from(problem.handle_ptr, sol_dual_simplex); sol_pdlp.set_solve_time(end_time); diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index 3530f43f9c..fd678cbff7 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -367,7 +367,6 @@ solution_t diversity_manager_t::run_solver() pdlp_settings.tolerances.relative_dual_tolerance = absolute_tolerance / tolerance_divisor; pdlp_settings.time_limit = lp_time_limit; pdlp_settings.first_primal_feasible = false; - global_concurrent_halt = 0; pdlp_settings.concurrent_halt = &global_concurrent_halt; pdlp_settings.method = method_t::Concurrent; pdlp_settings.pdlp_solver_mode = pdlp_solver_mode_t::Stable2; From dfc3102a8e56efa73420e261f5544a5465fb24ce Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Tue, 2 Dec 2025 14:39:18 -0800 Subject: [PATCH 29/51] Disable prints --- cpp/src/dual_simplex/branch_and_bound.cpp | 270 ++++++++++----------- cpp/src/dual_simplex/phase2.cpp | 11 +- cpp/src/mip/diversity/diversity_manager.cu | 130 +++++----- 3 files changed, 204 insertions(+), 207 deletions(-) diff --git a/cpp/src/dual_simplex/branch_and_bound.cpp b/cpp/src/dual_simplex/branch_and_bound.cpp index 9d15a68ac5..987ac4e935 100644 --- a/cpp/src/dual_simplex/branch_and_bound.cpp +++ b/cpp/src/dual_simplex/branch_and_bound.cpp @@ -1235,147 +1235,147 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut root_relax_soln_.resize(original_lp_.num_rows, original_lp_.num_cols); - settings_.log.printf("Waiting for PDLP root relaxation\n"); - + settings_.log.printf("Solving LP root relaxation\n"); simplex_solver_settings_t lp_settings = settings_; lp_settings.inside_mip = 1; // RINS/SUBMIP path - if (!is_main_thread()) { - lp_status_t root_status = solve_linear_program_advanced(original_lp_, - exploration_stats_.start_time, - lp_settings, - root_relax_soln_, - root_vstatus_, - edge_norms_); - exploration_stats_.total_lp_iters = root_relax_soln_.iterations; - exploration_stats_.total_lp_solve_time = toc(exploration_stats_.start_time); - - if (root_status == lp_status_t::INFEASIBLE) { - settings_.log.printf("MIP Infeasible\n"); - // FIXME: rarely dual simplex detects infeasible whereas it is feasible. - // to add a small safety net, check if there is a primal solution already. - // Uncomment this if the issue with cost266-UUE is resolved - // if (settings.heuristic_preemption_callback != nullptr) { - // settings.heuristic_preemption_callback(); - // } - return mip_status_t::INFEASIBLE; - } - if (root_status == lp_status_t::UNBOUNDED) { - settings_.log.printf("MIP Unbounded\n"); - if (settings_.heuristic_preemption_callback != nullptr) { - settings_.heuristic_preemption_callback(); - } - return mip_status_t::UNBOUNDED; - } - - if (root_status == lp_status_t::TIME_LIMIT) { - solver_status_ = mip_exploration_status_t::TIME_LIMIT; - return set_final_solution(solution, -inf); - } - } else { - // Root node path - std::future root_status_future; - settings_.log.printf("Async call to dual simplex\n"); - settings_.log.printf("&global_root_concurrent_halt: %p\n", &global_root_concurrent_halt); - root_status_future = std::async(std::launch::async, - &solve_linear_program_advanced, - std::ref(original_lp_), - exploration_stats_.start_time, - std::ref(lp_settings), - std::ref(root_relax_soln_), - std::ref(root_vstatus_), - std::ref(edge_norms_)); - // Wait for the root relaxation solution to be sent by the diversity manager or dual simplex to - // finish - while (!root_crossover_solution_set_.load(std::memory_order_acquire) && - global_root_concurrent_halt == 0) { - continue; + // if (!is_main_thread()) { + lp_status_t root_status = solve_linear_program_advanced(original_lp_, + exploration_stats_.start_time, + lp_settings, + root_relax_soln_, + root_vstatus_, + edge_norms_); + exploration_stats_.total_lp_iters = root_relax_soln_.iterations; + exploration_stats_.total_lp_solve_time = toc(exploration_stats_.start_time); + + if (root_status == lp_status_t::INFEASIBLE) { + settings_.log.printf("MIP Infeasible\n"); + // FIXME: rarely dual simplex detects infeasible whereas it is feasible. + // to add a small safety net, check if there is a primal solution already. + // Uncomment this if the issue with cost266-UUE is resolved + // if (settings.heuristic_preemption_callback != nullptr) { + // settings.heuristic_preemption_callback(); + // } + return mip_status_t::INFEASIBLE; + } + if (root_status == lp_status_t::UNBOUNDED) { + settings_.log.printf("MIP Unbounded\n"); + if (settings_.heuristic_preemption_callback != nullptr) { + settings_.heuristic_preemption_callback(); } + return mip_status_t::UNBOUNDED; + } - if (root_crossover_solution_set_.load(std::memory_order_acquire)) { - exploration_stats_.total_lp_iters = root_crossover_soln_.iterations; - exploration_stats_.total_lp_solve_time = toc(exploration_stats_.start_time); - // Crush the root relaxation solution on converted user problem - std::vector crushed_root_x; - crush_primal_solution( - original_problem_, original_lp_, root_crossover_soln_.x, new_slacks_, crushed_root_x); - std::vector crushed_root_y; - std::vector crushed_root_z; - - f_t dual_res_inf = crush_dual_solution(original_problem_, - original_lp_, - new_slacks_, - root_crossover_soln_.y, - root_crossover_soln_.z, - crushed_root_y, - crushed_root_z); - settings_.log.printf("Dual residual inf: %e\n", dual_res_inf); - - root_crossover_soln_.x = crushed_root_x; - root_crossover_soln_.y = crushed_root_y; - root_crossover_soln_.z = crushed_root_z; - - // Call crossover on the crushed solution - crossover_status_t crossover_status = crossover(original_lp_, - settings_, - root_crossover_soln_, - exploration_stats_.start_time, - root_crossover_soln_, - crossover_vstatus_); - settings_.log.printf("Crossover status: %d\n", crossover_status); - - // Check if crossover was stopped by dual simplex - if (crossover_status == crossover_status_t::CONCURRENT_LIMIT) { - exploration_stats_.total_lp_iters = root_crossover_soln_.iterations; - exploration_stats_.total_lp_solve_time = toc(exploration_stats_.start_time); - auto root_status = root_status_future.get(); - - if (root_status == lp_status_t::INFEASIBLE) { - settings_.log.printf("MIP Infeasible\n"); - return mip_status_t::INFEASIBLE; - } - if (root_status == lp_status_t::UNBOUNDED) { - settings_.log.printf("MIP Unbounded\n"); - if (settings_.heuristic_preemption_callback != nullptr) { - settings_.heuristic_preemption_callback(); - } - return mip_status_t::UNBOUNDED; - } - - if (root_status == lp_status_t::TIME_LIMIT) { - solver_status_ = mip_exploration_status_t::TIME_LIMIT; - return set_final_solution(solution, -inf); - } - } else { - // Solution was found by crossover - if (crossover_status == crossover_status_t::NUMERICAL_ISSUES) { - settings_.log.printf("MIP Infeasible\n"); - // FIXME: rarely dual simplex detects infeasible whereas it is feasible. - // to add a small safety net, check if there is a primal solution already. - // Uncomment this if the issue with cost266-UUE is resolved - // if (settings.heuristic_preemption_callback != nullptr) { - // settings.heuristic_preemption_callback(); - // } - return mip_status_t::INFEASIBLE; - } - if (crossover_status == crossover_status_t::NUMERICAL_ISSUES) { - settings_.log.printf("MIP Unbounded\n"); - if (settings_.heuristic_preemption_callback != nullptr) { - settings_.heuristic_preemption_callback(); - } - return mip_status_t::UNBOUNDED; - } - - if (crossover_status == crossover_status_t::TIME_LIMIT) { - solver_status_ = mip_exploration_status_t::TIME_LIMIT; - return set_final_solution(solution, -inf); - } - } - // Override the root relaxation solution with the crossover solution - root_relax_soln_ = root_crossover_soln_; - root_vstatus_ = crossover_vstatus_; - } + if (root_status == lp_status_t::TIME_LIMIT) { + solver_status_ = mip_exploration_status_t::TIME_LIMIT; + return set_final_solution(solution, -inf); } + // } else { + // // Root node path + // std::future root_status_future; + // settings_.log.printf("Async call to dual simplex\n"); + // settings_.log.printf("&global_root_concurrent_halt: %p\n", &global_root_concurrent_halt); + // root_status_future = std::async(std::launch::async, + // &solve_linear_program_advanced, + // std::ref(original_lp_), + // exploration_stats_.start_time, + // std::ref(lp_settings), + // std::ref(root_relax_soln_), + // std::ref(root_vstatus_), + // std::ref(edge_norms_)); + // // Wait for the root relaxation solution to be sent by the diversity manager or dual simplex + // to + // // finish + // while (!root_crossover_solution_set_.load(std::memory_order_acquire) && + // global_root_concurrent_halt == 0) { + // continue; + // } + + // if (root_crossover_solution_set_.load(std::memory_order_acquire)) { + // exploration_stats_.total_lp_iters = root_crossover_soln_.iterations; + // exploration_stats_.total_lp_solve_time = toc(exploration_stats_.start_time); + // // Crush the root relaxation solution on converted user problem + // std::vector crushed_root_x; + // crush_primal_solution( + // original_problem_, original_lp_, root_crossover_soln_.x, new_slacks_, crushed_root_x); + // std::vector crushed_root_y; + // std::vector crushed_root_z; + + // f_t dual_res_inf = crush_dual_solution(original_problem_, + // original_lp_, + // new_slacks_, + // root_crossover_soln_.y, + // root_crossover_soln_.z, + // crushed_root_y, + // crushed_root_z); + // settings_.log.printf("Dual residual inf: %e\n", dual_res_inf); + + // root_crossover_soln_.x = crushed_root_x; + // root_crossover_soln_.y = crushed_root_y; + // root_crossover_soln_.z = crushed_root_z; + + // // Call crossover on the crushed solution + // crossover_status_t crossover_status = crossover(original_lp_, + // settings_, + // root_crossover_soln_, + // exploration_stats_.start_time, + // root_crossover_soln_, + // crossover_vstatus_); + // settings_.log.printf("Crossover status: %d\n", crossover_status); + + // // Check if crossover was stopped by dual simplex + // if (crossover_status == crossover_status_t::CONCURRENT_LIMIT) { + // exploration_stats_.total_lp_iters = root_crossover_soln_.iterations; + // exploration_stats_.total_lp_solve_time = toc(exploration_stats_.start_time); + // auto root_status = root_status_future.get(); + + // if (root_status == lp_status_t::INFEASIBLE) { + // settings_.log.printf("MIP Infeasible\n"); + // return mip_status_t::INFEASIBLE; + // } + // if (root_status == lp_status_t::UNBOUNDED) { + // settings_.log.printf("MIP Unbounded\n"); + // if (settings_.heuristic_preemption_callback != nullptr) { + // settings_.heuristic_preemption_callback(); + // } + // return mip_status_t::UNBOUNDED; + // } + + // if (root_status == lp_status_t::TIME_LIMIT) { + // solver_status_ = mip_exploration_status_t::TIME_LIMIT; + // return set_final_solution(solution, -inf); + // } + // } else { + // // Solution was found by crossover + // if (crossover_status == crossover_status_t::NUMERICAL_ISSUES) { + // settings_.log.printf("MIP Infeasible\n"); + // // FIXME: rarely dual simplex detects infeasible whereas it is feasible. + // // to add a small safety net, check if there is a primal solution already. + // // Uncomment this if the issue with cost266-UUE is resolved + // // if (settings.heuristic_preemption_callback != nullptr) { + // // settings.heuristic_preemption_callback(); + // // } + // return mip_status_t::INFEASIBLE; + // } + // if (crossover_status == crossover_status_t::NUMERICAL_ISSUES) { + // settings_.log.printf("MIP Unbounded\n"); + // if (settings_.heuristic_preemption_callback != nullptr) { + // settings_.heuristic_preemption_callback(); + // } + // return mip_status_t::UNBOUNDED; + // } + + // if (crossover_status == crossover_status_t::TIME_LIMIT) { + // solver_status_ = mip_exploration_status_t::TIME_LIMIT; + // return set_final_solution(solution, -inf); + // } + // } + // // Override the root relaxation solution with the crossover solution + // root_relax_soln_ = root_crossover_soln_; + // root_vstatus_ = crossover_vstatus_; + // } + // } assert(root_vstatus_.size() == original_lp_.num_cols); set_uninitialized_steepest_edge_norms(edge_norms_); diff --git a/cpp/src/dual_simplex/phase2.cpp b/cpp/src/dual_simplex/phase2.cpp index 6e7b52a7bf..4e70779c68 100644 --- a/cpp/src/dual_simplex/phase2.cpp +++ b/cpp/src/dual_simplex/phase2.cpp @@ -2201,7 +2201,7 @@ dual::status_t dual_phase2(i_t phase, iter, delta_y_steepest_edge); } -#define PRINT_VSTATUS_CHANGES +// #define PRINT_VSTATUS_CHANGES template dual::status_t dual_phase2_with_advanced_basis(i_t phase, @@ -2267,19 +2267,14 @@ dual::status_t dual_phase2_with_advanced_basis(i_t phase, // Solve B'*y = cB ft.b_transpose_solve(c_basic, y); if (toc(start_time) > settings.time_limit) { return dual::status_t::TIME_LIMIT; } - constexpr bool print_norms = true; + constexpr bool print_norms = false; if constexpr (print_norms) { settings.log.printf( "|| y || %e || cB || %e\n", vector_norm_inf(y), vector_norm_inf(c_basic)); } phase2::compute_reduced_costs(objective, lp.A, y, basic_list, nonbasic_list, z); - if constexpr (print_norms) { - settings.log.printf("|| z || %e\n", vector_norm_inf(z)); - // for (i_t j = 0; j < n; ++j) { - // settings.log.printf("z[%d] = %e\n", j, z[j]); - // } - } + if constexpr (print_norms) { settings.log.printf("|| z || %e\n", vector_norm_inf(z)); } #ifdef COMPUTE_DUAL_RESIDUAL std::vector dual_res1; diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index fd678cbff7..ac426e62f6 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -345,39 +345,40 @@ solution_t diversity_manager_t::run_solver() // std::cout // << "Converting greater-than constraints to less-than constraints before calling LP solver" // << std::endl; - convert_greater_to_less(*problem_ptr); - // relaxed_lp_settings_t lp_settings; - // lp_settings.time_limit = lp_time_limit; - // lp_settings.tolerance = context.settings.tolerances.absolute_tolerance; - // lp_settings.return_first_feasible = false; - // lp_settings.save_state = true; - // lp_settings.concurrent_halt = &global_concurrent_halt; - // lp_settings.has_initial_primal = false; - // rmm::device_uvector lp_optimal_solution_copy(lp_optimal_solution.size(), - // problem_ptr->handle_ptr->get_stream()); - // auto lp_result = - // get_relaxed_lp_solution(*problem_ptr, lp_optimal_solution_copy, lp_state, lp_settings); - f_t tolerance_divisor = - problem_ptr->tolerances.absolute_tolerance / problem_ptr->tolerances.relative_tolerance; - if (tolerance_divisor == 0) { tolerance_divisor = 1; } - f_t absolute_tolerance = context.settings.tolerances.absolute_tolerance; - - pdlp_solver_settings_t pdlp_settings{}; - pdlp_settings.tolerances.relative_primal_tolerance = absolute_tolerance / tolerance_divisor; - pdlp_settings.tolerances.relative_dual_tolerance = absolute_tolerance / tolerance_divisor; - pdlp_settings.time_limit = lp_time_limit; - pdlp_settings.first_primal_feasible = false; - pdlp_settings.concurrent_halt = &global_concurrent_halt; - pdlp_settings.method = method_t::Concurrent; - pdlp_settings.pdlp_solver_mode = pdlp_solver_mode_t::Stable2; - + // convert_greater_to_less(*problem_ptr); + relaxed_lp_settings_t lp_settings; + lp_settings.time_limit = lp_time_limit; + lp_settings.tolerance = context.settings.tolerances.absolute_tolerance; + lp_settings.return_first_feasible = false; + lp_settings.save_state = true; + lp_settings.concurrent_halt = &global_concurrent_halt; + lp_settings.has_initial_primal = false; rmm::device_uvector lp_optimal_solution_copy(lp_optimal_solution.size(), problem_ptr->handle_ptr->get_stream()); - timer_t lp_timer(lp_time_limit); - auto const is_batch = false; - auto const inside_mip = true; auto lp_result = - solve_lp_with_method(*problem_ptr, pdlp_settings, lp_timer, is_batch, inside_mip); + get_relaxed_lp_solution(*problem_ptr, lp_optimal_solution_copy, lp_state, lp_settings); + // f_t tolerance_divisor = + // problem_ptr->tolerances.absolute_tolerance / problem_ptr->tolerances.relative_tolerance; + // if (tolerance_divisor == 0) { tolerance_divisor = 1; } + // f_t absolute_tolerance = context.settings.tolerances.absolute_tolerance; + + // pdlp_solver_settings_t pdlp_settings{}; + // pdlp_settings.tolerances.relative_primal_tolerance = absolute_tolerance / tolerance_divisor; + // pdlp_settings.tolerances.relative_dual_tolerance = absolute_tolerance / tolerance_divisor; + // pdlp_settings.time_limit = lp_time_limit; + // pdlp_settings.first_primal_feasible = false; + // pdlp_settings.concurrent_halt = &global_concurrent_halt; + // pdlp_settings.method = method_t::Concurrent; + // pdlp_settings.pdlp_solver_mode = pdlp_solver_mode_t::Stable2; + + // rmm::device_uvector lp_optimal_solution_copy(lp_optimal_solution.size(), + // problem_ptr->handle_ptr->get_stream()); + // timer_t lp_timer(lp_time_limit); + // auto const is_batch = false; + // auto const inside_mip = true; + // auto lp_result = + // solve_lp_with_method(*problem_ptr, pdlp_settings, lp_timer, is_batch, + // inside_mip); CUOPT_LOG_INFO("LP enum: %s", lp_result.get_termination_status_string().c_str()); { @@ -423,40 +424,41 @@ solution_t diversity_manager_t::run_solver() lp_result.get_additional_termination_information().number_of_steps_taken); // Send PDLP relaxed solution to branch and bound before it solves the root node - if (problem_ptr->set_root_relaxation_solution_callback != nullptr) { - auto& d_primal_solution = lp_result.get_primal_solution(); - auto& d_dual_solution = lp_result.get_dual_solution(); - auto& d_reduced_costs = lp_result.get_reduced_cost(); - // cuopt::print("primal_solution", d_primal_solution); - // cuopt::print("dual_solution", d_dual_solution); - // cuopt::print("reduced_costs", d_reduced_costs); - std::vector host_primal(d_primal_solution.size()); - std::vector host_dual(d_dual_solution.size()); - std::vector host_reduced_costs(d_reduced_costs.size()); - raft::copy(host_primal.data(), - d_primal_solution.data(), - d_primal_solution.size(), - problem_ptr->handle_ptr->get_stream()); - raft::copy(host_dual.data(), - d_dual_solution.data(), - d_dual_solution.size(), - problem_ptr->handle_ptr->get_stream()); - raft::copy(host_reduced_costs.data(), - d_reduced_costs.data(), - d_reduced_costs.size(), - problem_ptr->handle_ptr->get_stream()); - problem_ptr->handle_ptr->sync_stream(); - - auto user_obj = problem_ptr->get_user_obj_from_solver_obj(lp_result.get_objective_value()); - auto iterations = lp_result.get_additional_termination_information().number_of_steps_taken; - // Set for the B&B - problem_ptr->set_root_relaxation_solution_callback(host_primal, - host_dual, - host_reduced_costs, - lp_result.get_objective_value(), - user_obj, - iterations); - } + // if (problem_ptr->set_root_relaxation_solution_callback != nullptr) { + // auto& d_primal_solution = lp_result.get_primal_solution(); + // auto& d_dual_solution = lp_result.get_dual_solution(); + // auto& d_reduced_costs = lp_result.get_reduced_cost(); + // // cuopt::print("primal_solution", d_primal_solution); + // // cuopt::print("dual_solution", d_dual_solution); + // // cuopt::print("reduced_costs", d_reduced_costs); + // std::vector host_primal(d_primal_solution.size()); + // std::vector host_dual(d_dual_solution.size()); + // std::vector host_reduced_costs(d_reduced_costs.size()); + // raft::copy(host_primal.data(), + // d_primal_solution.data(), + // d_primal_solution.size(), + // problem_ptr->handle_ptr->get_stream()); + // raft::copy(host_dual.data(), + // d_dual_solution.data(), + // d_dual_solution.size(), + // problem_ptr->handle_ptr->get_stream()); + // raft::copy(host_reduced_costs.data(), + // d_reduced_costs.data(), + // d_reduced_costs.size(), + // problem_ptr->handle_ptr->get_stream()); + // problem_ptr->handle_ptr->sync_stream(); + + // auto user_obj = + // problem_ptr->get_user_obj_from_solver_obj(lp_result.get_objective_value()); auto iterations + // = lp_result.get_additional_termination_information().number_of_steps_taken; + // // Set for the B&B + // problem_ptr->set_root_relaxation_solution_callback(host_primal, + // host_dual, + // host_reduced_costs, + // lp_result.get_objective_value(), + // user_obj, + // iterations); + // } // in case the pdlp returned var boudns that are out of bounds clamp_within_var_bounds(lp_optimal_solution, problem_ptr, problem_ptr->handle_ptr); From b4f2aaa44257d5393df6bb57fd5d18a4b7e2014b Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Tue, 2 Dec 2025 14:47:36 -0800 Subject: [PATCH 30/51] Revert phase 2 tol change --- cpp/src/dual_simplex/phase2.cpp | 20 +++++--------------- cpp/src/mip/diversity/diversity_manager.cu | 11 +++++------ 2 files changed, 10 insertions(+), 21 deletions(-) diff --git a/cpp/src/dual_simplex/phase2.cpp b/cpp/src/dual_simplex/phase2.cpp index 4e70779c68..4932ddae95 100644 --- a/cpp/src/dual_simplex/phase2.cpp +++ b/cpp/src/dual_simplex/phase2.cpp @@ -1910,7 +1910,6 @@ void set_primal_variables_on_bounds(const lp_problem_t& lp, std::vector& x) { const i_t n = lp.num_cols; - settings.log.printf("tolerance %e\n", settings.fixed_tol); for (i_t j = 0; j < n; ++j) { // We set z_j = 0 for basic variables // But we explicitally skip setting basic variables here @@ -1928,19 +1927,11 @@ void set_primal_variables_on_bounds(const lp_problem_t& lp, } x[j] = lp.lower[j]; vstatus[j] = variable_status_t::NONBASIC_FIXED; - // } else if (z[j] == 0 && lp.lower[j] > -inf && vstatus[j] == - // variable_status_t::NONBASIC_LOWER) { - // x[j] = lp.lower[j]; - // } else if (z[j] == 0 && lp.upper[j] < inf && vstatus[j] == - // variable_status_t::NONBASIC_UPPER) { - // x[j] = lp.upper[j]; - } else if (std::abs(z[j]) <= fixed_tolerance && lp.lower[j] > -inf && - vstatus[j] == variable_status_t::NONBASIC_LOWER) { + } else if (z[j] == 0 && lp.lower[j] > -inf && vstatus[j] == variable_status_t::NONBASIC_LOWER) { x[j] = lp.lower[j]; - } else if (std::abs(z[j]) <= fixed_tolerance && lp.upper[j] < inf && - vstatus[j] == variable_status_t::NONBASIC_UPPER) { + } else if (z[j] == 0 && lp.upper[j] < inf && vstatus[j] == variable_status_t::NONBASIC_UPPER) { x[j] = lp.upper[j]; - } else if (z[j] > fixed_tolerance && lp.lower[j] > -inf) { + } else if (z[j] >= 0 && lp.lower[j] > -inf) { if (vstatus[j] != variable_status_t::NONBASIC_LOWER) { settings.log.debug( "Setting nonbasic lower variable (zj %e) %d to %e (current %e). vstatus %d\n", @@ -1952,7 +1943,7 @@ void set_primal_variables_on_bounds(const lp_problem_t& lp, } x[j] = lp.lower[j]; vstatus[j] = variable_status_t::NONBASIC_LOWER; - } else if (z[j] < fixed_tolerance && lp.upper[j] < inf) { + } else if (z[j] <= 0 && lp.upper[j] < inf) { if (vstatus[j] != variable_status_t::NONBASIC_UPPER) { settings.log.debug( "Setting nonbasic upper variable (zj %e) %d to %e (current %e). vstatus %d\n", @@ -1993,7 +1984,7 @@ void set_primal_variables_on_bounds(const lp_problem_t& lp, "Setting free variable %d to %e. vstatus %d\n", j, 0, static_cast(vstatus[j])); } vstatus[j] = variable_status_t::NONBASIC_FREE; - settings.log.debug("Setting free variable %d as nonbasic at 0\n", j); + settings.log.printf("Setting free variable %d as nonbasic at 0\n", j); } else { assert(1 == 0); } @@ -2201,7 +2192,6 @@ dual::status_t dual_phase2(i_t phase, iter, delta_y_steepest_edge); } -// #define PRINT_VSTATUS_CHANGES template dual::status_t dual_phase2_with_advanced_basis(i_t phase, diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index ac426e62f6..afc05f313d 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -380,7 +380,6 @@ solution_t diversity_manager_t::run_solver() // solve_lp_with_method(*problem_ptr, pdlp_settings, lp_timer, is_batch, // inside_mip); - CUOPT_LOG_INFO("LP enum: %s", lp_result.get_termination_status_string().c_str()); { std::lock_guard guard(relaxed_solution_mutex); if (!simplex_solution_exists.load()) { @@ -417,11 +416,11 @@ solution_t diversity_manager_t::run_solver() // to bring variables within the bounds } - CUOPT_LOG_INFO( - "Optimal solution exists: %i, PDLP relaxed solution: objective %f, iterations %d", - ls.lp_optimal_exists, - lp_result.get_objective_value(), - lp_result.get_additional_termination_information().number_of_steps_taken); + // CUOPT_LOG_INFO( + // "Optimal solution exists: %i, PDLP relaxed solution: objective %f, iterations %d", + // ls.lp_optimal_exists, + // lp_result.get_objective_value(), + // lp_result.get_additional_termination_information().number_of_steps_taken); // Send PDLP relaxed solution to branch and bound before it solves the root node // if (problem_ptr->set_root_relaxation_solution_callback != nullptr) { From a18b852094c47c94847cae03e4ca14d8deec85e4 Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Tue, 2 Dec 2025 15:01:50 -0800 Subject: [PATCH 31/51] Wip debugging --- cpp/src/dual_simplex/branch_and_bound.cpp | 206 ++++++++++------------ cpp/src/dual_simplex/phase2.cpp | 1 + 2 files changed, 94 insertions(+), 113 deletions(-) diff --git a/cpp/src/dual_simplex/branch_and_bound.cpp b/cpp/src/dual_simplex/branch_and_bound.cpp index 987ac4e935..ba7e9f35bb 100644 --- a/cpp/src/dual_simplex/branch_and_bound.cpp +++ b/cpp/src/dual_simplex/branch_and_bound.cpp @@ -1237,15 +1237,100 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut settings_.log.printf("Solving LP root relaxation\n"); simplex_solver_settings_t lp_settings = settings_; - lp_settings.inside_mip = 1; + lp_status_t root_status; + lp_settings.inside_mip = 1; // RINS/SUBMIP path - // if (!is_main_thread()) { - lp_status_t root_status = solve_linear_program_advanced(original_lp_, - exploration_stats_.start_time, - lp_settings, - root_relax_soln_, - root_vstatus_, - edge_norms_); + if (!is_main_thread()) { + root_status = solve_linear_program_advanced(original_lp_, + exploration_stats_.start_time, + lp_settings, + root_relax_soln_, + root_vstatus_, + edge_norms_); + + } else { + // Root node path + std::future root_status_future; + root_status_future = std::async(std::launch::async, + &solve_linear_program_advanced, + std::ref(original_lp_), + exploration_stats_.start_time, + std::ref(lp_settings), + std::ref(root_relax_soln_), + std::ref(root_vstatus_), + std::ref(edge_norms_)); + // Wait for the root relaxation solution to be sent by the diversity manager or dual simplex + // to finish + while (!root_crossover_solution_set_.load(std::memory_order_acquire) && + global_root_concurrent_halt == 0) { + continue; + } + + // if (root_crossover_solution_set_.load(std::memory_order_acquire)) { + // exploration_stats_.total_lp_iters = root_crossover_soln_.iterations; + // exploration_stats_.total_lp_solve_time = toc(exploration_stats_.start_time); + // // Crush the root relaxation solution on converted user problem + // std::vector crushed_root_x; + // crush_primal_solution( + // original_problem_, original_lp_, root_crossover_soln_.x, new_slacks_, crushed_root_x); + // std::vector crushed_root_y; + // std::vector crushed_root_z; + + // f_t dual_res_inf = crush_dual_solution(original_problem_, + // original_lp_, + // new_slacks_, + // root_crossover_soln_.y, + // root_crossover_soln_.z, + // crushed_root_y, + // crushed_root_z); + // settings_.log.printf("Dual residual inf: %e\n", dual_res_inf); + + // root_crossover_soln_.x = crushed_root_x; + // root_crossover_soln_.y = crushed_root_y; + // root_crossover_soln_.z = crushed_root_z; + + // // Call crossover on the crushed solution + // crossover_status_t crossover_status = crossover(original_lp_, + // settings_, + // root_crossover_soln_, + // exploration_stats_.start_time, + // root_crossover_soln_, + // crossover_vstatus_); + // settings_.log.printf("Crossover status: %d\n", crossover_status); + + // // Check if crossover was stopped by dual simplex + // if (crossover_status == crossover_status_t::CONCURRENT_LIMIT) { + // auto root_status = root_status_future.get(); + // } else { + // // Solution was found by crossover + // if (crossover_status == crossover_status_t::NUMERICAL_ISSUES) { + // settings_.log.printf("MIP Infeasible\n"); + // // FIXME: rarely dual simplex detects infeasible whereas it is feasible. + // // to add a small safety net, check if there is a primal solution already. + // // Uncomment this if the issue with cost266-UUE is resolved + // // if (settings.heuristic_preemption_callback != nullptr) { + // // settings.heuristic_preemption_callback(); + // // } + // return mip_status_t::INFEASIBLE; + // } + // if (crossover_status == crossover_status_t::NUMERICAL_ISSUES) { + // settings_.log.printf("MIP Unbounded\n"); + // if (settings_.heuristic_preemption_callback != nullptr) { + // settings_.heuristic_preemption_callback(); + // } + // return mip_status_t::UNBOUNDED; + // } + + // if (crossover_status == crossover_status_t::TIME_LIMIT) { + // solver_status_ = mip_exploration_status_t::TIME_LIMIT; + // return set_final_solution(solution, -inf); + // } + // } + // // Override the root relaxation solution with the crossover solution + // root_relax_soln_ = root_crossover_soln_; + // root_vstatus_ = crossover_vstatus_; + // } + } exploration_stats_.total_lp_iters = root_relax_soln_.iterations; exploration_stats_.total_lp_solve_time = toc(exploration_stats_.start_time); @@ -1271,111 +1356,6 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut solver_status_ = mip_exploration_status_t::TIME_LIMIT; return set_final_solution(solution, -inf); } - // } else { - // // Root node path - // std::future root_status_future; - // settings_.log.printf("Async call to dual simplex\n"); - // settings_.log.printf("&global_root_concurrent_halt: %p\n", &global_root_concurrent_halt); - // root_status_future = std::async(std::launch::async, - // &solve_linear_program_advanced, - // std::ref(original_lp_), - // exploration_stats_.start_time, - // std::ref(lp_settings), - // std::ref(root_relax_soln_), - // std::ref(root_vstatus_), - // std::ref(edge_norms_)); - // // Wait for the root relaxation solution to be sent by the diversity manager or dual simplex - // to - // // finish - // while (!root_crossover_solution_set_.load(std::memory_order_acquire) && - // global_root_concurrent_halt == 0) { - // continue; - // } - - // if (root_crossover_solution_set_.load(std::memory_order_acquire)) { - // exploration_stats_.total_lp_iters = root_crossover_soln_.iterations; - // exploration_stats_.total_lp_solve_time = toc(exploration_stats_.start_time); - // // Crush the root relaxation solution on converted user problem - // std::vector crushed_root_x; - // crush_primal_solution( - // original_problem_, original_lp_, root_crossover_soln_.x, new_slacks_, crushed_root_x); - // std::vector crushed_root_y; - // std::vector crushed_root_z; - - // f_t dual_res_inf = crush_dual_solution(original_problem_, - // original_lp_, - // new_slacks_, - // root_crossover_soln_.y, - // root_crossover_soln_.z, - // crushed_root_y, - // crushed_root_z); - // settings_.log.printf("Dual residual inf: %e\n", dual_res_inf); - - // root_crossover_soln_.x = crushed_root_x; - // root_crossover_soln_.y = crushed_root_y; - // root_crossover_soln_.z = crushed_root_z; - - // // Call crossover on the crushed solution - // crossover_status_t crossover_status = crossover(original_lp_, - // settings_, - // root_crossover_soln_, - // exploration_stats_.start_time, - // root_crossover_soln_, - // crossover_vstatus_); - // settings_.log.printf("Crossover status: %d\n", crossover_status); - - // // Check if crossover was stopped by dual simplex - // if (crossover_status == crossover_status_t::CONCURRENT_LIMIT) { - // exploration_stats_.total_lp_iters = root_crossover_soln_.iterations; - // exploration_stats_.total_lp_solve_time = toc(exploration_stats_.start_time); - // auto root_status = root_status_future.get(); - - // if (root_status == lp_status_t::INFEASIBLE) { - // settings_.log.printf("MIP Infeasible\n"); - // return mip_status_t::INFEASIBLE; - // } - // if (root_status == lp_status_t::UNBOUNDED) { - // settings_.log.printf("MIP Unbounded\n"); - // if (settings_.heuristic_preemption_callback != nullptr) { - // settings_.heuristic_preemption_callback(); - // } - // return mip_status_t::UNBOUNDED; - // } - - // if (root_status == lp_status_t::TIME_LIMIT) { - // solver_status_ = mip_exploration_status_t::TIME_LIMIT; - // return set_final_solution(solution, -inf); - // } - // } else { - // // Solution was found by crossover - // if (crossover_status == crossover_status_t::NUMERICAL_ISSUES) { - // settings_.log.printf("MIP Infeasible\n"); - // // FIXME: rarely dual simplex detects infeasible whereas it is feasible. - // // to add a small safety net, check if there is a primal solution already. - // // Uncomment this if the issue with cost266-UUE is resolved - // // if (settings.heuristic_preemption_callback != nullptr) { - // // settings.heuristic_preemption_callback(); - // // } - // return mip_status_t::INFEASIBLE; - // } - // if (crossover_status == crossover_status_t::NUMERICAL_ISSUES) { - // settings_.log.printf("MIP Unbounded\n"); - // if (settings_.heuristic_preemption_callback != nullptr) { - // settings_.heuristic_preemption_callback(); - // } - // return mip_status_t::UNBOUNDED; - // } - - // if (crossover_status == crossover_status_t::TIME_LIMIT) { - // solver_status_ = mip_exploration_status_t::TIME_LIMIT; - // return set_final_solution(solution, -inf); - // } - // } - // // Override the root relaxation solution with the crossover solution - // root_relax_soln_ = root_crossover_soln_; - // root_vstatus_ = crossover_vstatus_; - // } - // } assert(root_vstatus_.size() == original_lp_.num_cols); set_uninitialized_steepest_edge_norms(edge_norms_); diff --git a/cpp/src/dual_simplex/phase2.cpp b/cpp/src/dual_simplex/phase2.cpp index 4932ddae95..ceea3e1e9a 100644 --- a/cpp/src/dual_simplex/phase2.cpp +++ b/cpp/src/dual_simplex/phase2.cpp @@ -2074,6 +2074,7 @@ void prepare_optimality(const lp_problem_t& lp, settings.log.printf("Dual infeasibility (abs): %.2e\n", dual_infeas); settings.log.printf("Perturbation: %.2e\n", perturbation); } else { + *settings.concurrent_halt = 1; settings.log.printf("\n"); settings.log.printf( "Root relaxation solution found in %d iterations and %.2fs\n", iter, toc(start_time)); From 0158c41a81283e1a2e92f2b81656714c9f6e14a8 Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Tue, 2 Dec 2025 15:11:46 -0800 Subject: [PATCH 32/51] Check for nullptr --- cpp/src/dual_simplex/branch_and_bound.cpp | 183 +++++++++++----------- cpp/src/dual_simplex/phase2.cpp | 2 +- 2 files changed, 93 insertions(+), 92 deletions(-) diff --git a/cpp/src/dual_simplex/branch_and_bound.cpp b/cpp/src/dual_simplex/branch_and_bound.cpp index ba7e9f35bb..514991ef93 100644 --- a/cpp/src/dual_simplex/branch_and_bound.cpp +++ b/cpp/src/dual_simplex/branch_and_bound.cpp @@ -1240,97 +1240,98 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut lp_status_t root_status; lp_settings.inside_mip = 1; // RINS/SUBMIP path - if (!is_main_thread()) { - root_status = solve_linear_program_advanced(original_lp_, - exploration_stats_.start_time, - lp_settings, - root_relax_soln_, - root_vstatus_, - edge_norms_); - - } else { - // Root node path - std::future root_status_future; - root_status_future = std::async(std::launch::async, - &solve_linear_program_advanced, - std::ref(original_lp_), - exploration_stats_.start_time, - std::ref(lp_settings), - std::ref(root_relax_soln_), - std::ref(root_vstatus_), - std::ref(edge_norms_)); - // Wait for the root relaxation solution to be sent by the diversity manager or dual simplex - // to finish - while (!root_crossover_solution_set_.load(std::memory_order_acquire) && - global_root_concurrent_halt == 0) { - continue; - } - - // if (root_crossover_solution_set_.load(std::memory_order_acquire)) { - // exploration_stats_.total_lp_iters = root_crossover_soln_.iterations; - // exploration_stats_.total_lp_solve_time = toc(exploration_stats_.start_time); - // // Crush the root relaxation solution on converted user problem - // std::vector crushed_root_x; - // crush_primal_solution( - // original_problem_, original_lp_, root_crossover_soln_.x, new_slacks_, crushed_root_x); - // std::vector crushed_root_y; - // std::vector crushed_root_z; - - // f_t dual_res_inf = crush_dual_solution(original_problem_, - // original_lp_, - // new_slacks_, - // root_crossover_soln_.y, - // root_crossover_soln_.z, - // crushed_root_y, - // crushed_root_z); - // settings_.log.printf("Dual residual inf: %e\n", dual_res_inf); - - // root_crossover_soln_.x = crushed_root_x; - // root_crossover_soln_.y = crushed_root_y; - // root_crossover_soln_.z = crushed_root_z; - - // // Call crossover on the crushed solution - // crossover_status_t crossover_status = crossover(original_lp_, - // settings_, - // root_crossover_soln_, - // exploration_stats_.start_time, - // root_crossover_soln_, - // crossover_vstatus_); - // settings_.log.printf("Crossover status: %d\n", crossover_status); - - // // Check if crossover was stopped by dual simplex - // if (crossover_status == crossover_status_t::CONCURRENT_LIMIT) { - // auto root_status = root_status_future.get(); - // } else { - // // Solution was found by crossover - // if (crossover_status == crossover_status_t::NUMERICAL_ISSUES) { - // settings_.log.printf("MIP Infeasible\n"); - // // FIXME: rarely dual simplex detects infeasible whereas it is feasible. - // // to add a small safety net, check if there is a primal solution already. - // // Uncomment this if the issue with cost266-UUE is resolved - // // if (settings.heuristic_preemption_callback != nullptr) { - // // settings.heuristic_preemption_callback(); - // // } - // return mip_status_t::INFEASIBLE; - // } - // if (crossover_status == crossover_status_t::NUMERICAL_ISSUES) { - // settings_.log.printf("MIP Unbounded\n"); - // if (settings_.heuristic_preemption_callback != nullptr) { - // settings_.heuristic_preemption_callback(); - // } - // return mip_status_t::UNBOUNDED; - // } - - // if (crossover_status == crossover_status_t::TIME_LIMIT) { - // solver_status_ = mip_exploration_status_t::TIME_LIMIT; - // return set_final_solution(solution, -inf); - // } - // } - // // Override the root relaxation solution with the crossover solution - // root_relax_soln_ = root_crossover_soln_; - // root_vstatus_ = crossover_vstatus_; - // } - } + // if (!is_main_thread()) { + root_status = solve_linear_program_advanced(original_lp_, + exploration_stats_.start_time, + lp_settings, + root_relax_soln_, + root_vstatus_, + edge_norms_); + + // } else { + // // Root node path + // std::future root_status_future; + // root_status_future = std::async(std::launch::async, + // &solve_linear_program_advanced, + // std::ref(original_lp_), + // exploration_stats_.start_time, + // std::ref(lp_settings), + // std::ref(root_relax_soln_), + // std::ref(root_vstatus_), + // std::ref(edge_norms_)); + // // Wait for the root relaxation solution to be sent by the diversity manager or dual simplex + // // to finish + // while (!root_crossover_solution_set_.load(std::memory_order_acquire) && + // global_root_concurrent_halt == 0) { + // continue; + // } + + // // if (root_crossover_solution_set_.load(std::memory_order_acquire)) { + // // exploration_stats_.total_lp_iters = root_crossover_soln_.iterations; + // // exploration_stats_.total_lp_solve_time = toc(exploration_stats_.start_time); + // // // Crush the root relaxation solution on converted user problem + // // std::vector crushed_root_x; + // // crush_primal_solution( + // // original_problem_, original_lp_, root_crossover_soln_.x, new_slacks_, crushed_root_x); + // // std::vector crushed_root_y; + // // std::vector crushed_root_z; + + // // f_t dual_res_inf = crush_dual_solution(original_problem_, + // // original_lp_, + // // new_slacks_, + // // root_crossover_soln_.y, + // // root_crossover_soln_.z, + // // crushed_root_y, + // // crushed_root_z); + // // settings_.log.printf("Dual residual inf: %e\n", dual_res_inf); + + // // root_crossover_soln_.x = crushed_root_x; + // // root_crossover_soln_.y = crushed_root_y; + // // root_crossover_soln_.z = crushed_root_z; + + // // // Call crossover on the crushed solution + // // crossover_status_t crossover_status = crossover(original_lp_, + // // settings_, + // // root_crossover_soln_, + // // exploration_stats_.start_time, + // // root_crossover_soln_, + // // crossover_vstatus_); + // // settings_.log.printf("Crossover status: %d\n", crossover_status); + + // // // Check if crossover was stopped by dual simplex + // // if (crossover_status == crossover_status_t::CONCURRENT_LIMIT) { + // // auto root_status = root_status_future.get(); + // // } else { + // // // Solution was found by crossover + // // if (crossover_status == crossover_status_t::NUMERICAL_ISSUES) { + // // settings_.log.printf("MIP Infeasible\n"); + // // // FIXME: rarely dual simplex detects infeasible whereas it is feasible. + // // // to add a small safety net, check if there is a primal solution already. + // // // Uncomment this if the issue with cost266-UUE is resolved + // // // if (settings.heuristic_preemption_callback != nullptr) { + // // // settings.heuristic_preemption_callback(); + // // // } + // // return mip_status_t::INFEASIBLE; + // // } + // // if (crossover_status == crossover_status_t::NUMERICAL_ISSUES) { + // // settings_.log.printf("MIP Unbounded\n"); + // // if (settings_.heuristic_preemption_callback != nullptr) { + // // settings_.heuristic_preemption_callback(); + // // } + // // return mip_status_t::UNBOUNDED; + // // } + + // // if (crossover_status == crossover_status_t::TIME_LIMIT) { + // // solver_status_ = mip_exploration_status_t::TIME_LIMIT; + // // return set_final_solution(solution, -inf); + // // } + // // } + // // // Override the root relaxation solution with the crossover solution + // // root_relax_soln_ = root_crossover_soln_; + // // root_vstatus_ = crossover_vstatus_; + // // } + // root_status = root_status_future.get(); + // } exploration_stats_.total_lp_iters = root_relax_soln_.iterations; exploration_stats_.total_lp_solve_time = toc(exploration_stats_.start_time); diff --git a/cpp/src/dual_simplex/phase2.cpp b/cpp/src/dual_simplex/phase2.cpp index ceea3e1e9a..635c2c470e 100644 --- a/cpp/src/dual_simplex/phase2.cpp +++ b/cpp/src/dual_simplex/phase2.cpp @@ -2074,7 +2074,7 @@ void prepare_optimality(const lp_problem_t& lp, settings.log.printf("Dual infeasibility (abs): %.2e\n", dual_infeas); settings.log.printf("Perturbation: %.2e\n", perturbation); } else { - *settings.concurrent_halt = 1; + if (settings.concurrent_halt != nullptr) { *settings.concurrent_halt = 1; } settings.log.printf("\n"); settings.log.printf( "Root relaxation solution found in %d iterations and %.2fs\n", iter, toc(start_time)); From 628d2af387f2cbc483618f7cea1b8d526a28ed71 Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Tue, 2 Dec 2025 15:27:46 -0800 Subject: [PATCH 33/51] Fixing issues in future termination and halt --- cpp/src/dual_simplex/branch_and_bound.cpp | 187 +++++++++++----------- 1 file changed, 95 insertions(+), 92 deletions(-) diff --git a/cpp/src/dual_simplex/branch_and_bound.cpp b/cpp/src/dual_simplex/branch_and_bound.cpp index 514991ef93..c00887db8d 100644 --- a/cpp/src/dual_simplex/branch_and_bound.cpp +++ b/cpp/src/dual_simplex/branch_and_bound.cpp @@ -1240,98 +1240,101 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut lp_status_t root_status; lp_settings.inside_mip = 1; // RINS/SUBMIP path - // if (!is_main_thread()) { - root_status = solve_linear_program_advanced(original_lp_, - exploration_stats_.start_time, - lp_settings, - root_relax_soln_, - root_vstatus_, - edge_norms_); - - // } else { - // // Root node path - // std::future root_status_future; - // root_status_future = std::async(std::launch::async, - // &solve_linear_program_advanced, - // std::ref(original_lp_), - // exploration_stats_.start_time, - // std::ref(lp_settings), - // std::ref(root_relax_soln_), - // std::ref(root_vstatus_), - // std::ref(edge_norms_)); - // // Wait for the root relaxation solution to be sent by the diversity manager or dual simplex - // // to finish - // while (!root_crossover_solution_set_.load(std::memory_order_acquire) && - // global_root_concurrent_halt == 0) { - // continue; - // } - - // // if (root_crossover_solution_set_.load(std::memory_order_acquire)) { - // // exploration_stats_.total_lp_iters = root_crossover_soln_.iterations; - // // exploration_stats_.total_lp_solve_time = toc(exploration_stats_.start_time); - // // // Crush the root relaxation solution on converted user problem - // // std::vector crushed_root_x; - // // crush_primal_solution( - // // original_problem_, original_lp_, root_crossover_soln_.x, new_slacks_, crushed_root_x); - // // std::vector crushed_root_y; - // // std::vector crushed_root_z; - - // // f_t dual_res_inf = crush_dual_solution(original_problem_, - // // original_lp_, - // // new_slacks_, - // // root_crossover_soln_.y, - // // root_crossover_soln_.z, - // // crushed_root_y, - // // crushed_root_z); - // // settings_.log.printf("Dual residual inf: %e\n", dual_res_inf); - - // // root_crossover_soln_.x = crushed_root_x; - // // root_crossover_soln_.y = crushed_root_y; - // // root_crossover_soln_.z = crushed_root_z; - - // // // Call crossover on the crushed solution - // // crossover_status_t crossover_status = crossover(original_lp_, - // // settings_, - // // root_crossover_soln_, - // // exploration_stats_.start_time, - // // root_crossover_soln_, - // // crossover_vstatus_); - // // settings_.log.printf("Crossover status: %d\n", crossover_status); - - // // // Check if crossover was stopped by dual simplex - // // if (crossover_status == crossover_status_t::CONCURRENT_LIMIT) { - // // auto root_status = root_status_future.get(); - // // } else { - // // // Solution was found by crossover - // // if (crossover_status == crossover_status_t::NUMERICAL_ISSUES) { - // // settings_.log.printf("MIP Infeasible\n"); - // // // FIXME: rarely dual simplex detects infeasible whereas it is feasible. - // // // to add a small safety net, check if there is a primal solution already. - // // // Uncomment this if the issue with cost266-UUE is resolved - // // // if (settings.heuristic_preemption_callback != nullptr) { - // // // settings.heuristic_preemption_callback(); - // // // } - // // return mip_status_t::INFEASIBLE; - // // } - // // if (crossover_status == crossover_status_t::NUMERICAL_ISSUES) { - // // settings_.log.printf("MIP Unbounded\n"); - // // if (settings_.heuristic_preemption_callback != nullptr) { - // // settings_.heuristic_preemption_callback(); - // // } - // // return mip_status_t::UNBOUNDED; - // // } - - // // if (crossover_status == crossover_status_t::TIME_LIMIT) { - // // solver_status_ = mip_exploration_status_t::TIME_LIMIT; - // // return set_final_solution(solution, -inf); - // // } - // // } - // // // Override the root relaxation solution with the crossover solution - // // root_relax_soln_ = root_crossover_soln_; - // // root_vstatus_ = crossover_vstatus_; - // // } - // root_status = root_status_future.get(); - // } + if (!is_main_thread()) { + root_status = solve_linear_program_advanced(original_lp_, + exploration_stats_.start_time, + lp_settings, + root_relax_soln_, + root_vstatus_, + edge_norms_); + + } else { + // Root node path + std::future root_status_future; + root_status_future = std::async(std::launch::async, + &solve_linear_program_advanced, + std::ref(original_lp_), + exploration_stats_.start_time, + std::ref(lp_settings), + std::ref(root_relax_soln_), + std::ref(root_vstatus_), + std::ref(edge_norms_)); + // Wait for the root relaxation solution to be sent by the diversity manager or dual simplex + // to finish + while (!root_crossover_solution_set_.load(std::memory_order_acquire) && + global_root_concurrent_halt == 0) { + continue; + } + + if (root_crossover_solution_set_.load(std::memory_order_acquire)) { + // Crush the root relaxation solution on converted user problem + std::vector crushed_root_x; + crush_primal_solution( + original_problem_, original_lp_, root_crossover_soln_.x, new_slacks_, crushed_root_x); + std::vector crushed_root_y; + std::vector crushed_root_z; + + f_t dual_res_inf = crush_dual_solution(original_problem_, + original_lp_, + new_slacks_, + root_crossover_soln_.y, + root_crossover_soln_.z, + crushed_root_y, + crushed_root_z); + settings_.log.printf("Dual residual inf: %e\n", dual_res_inf); + + root_crossover_soln_.x = crushed_root_x; + root_crossover_soln_.y = crushed_root_y; + root_crossover_soln_.z = crushed_root_z; + + // Call crossover on the crushed solution + crossover_status_t crossover_status = crossover(original_lp_, + settings_, + root_crossover_soln_, + exploration_stats_.start_time, + root_crossover_soln_, + crossover_vstatus_); + settings_.log.printf("Crossover status: %d\n", crossover_status); + exploration_stats_.total_lp_iters = root_crossover_soln_.iterations; + exploration_stats_.total_lp_solve_time = toc(exploration_stats_.start_time); + + // Check if crossover was stopped by dual simplex + if (crossover_status == crossover_status_t::CONCURRENT_LIMIT) { + root_status = root_status_future.get(); + } else { + // Solution was found by crossover + if (crossover_status == crossover_status_t::NUMERICAL_ISSUES) { + settings_.log.printf("MIP Infeasible\n"); + // FIXME: rarely dual simplex detects infeasible whereas it is feasible. + // to add a small safety net, check if there is a primal solution already. + // Uncomment this if the issue with cost266-UUE is resolved + // if (settings.heuristic_preemption_callback != nullptr) { + // settings.heuristic_preemption_callback(); + // } + return mip_status_t::INFEASIBLE; + } + if (crossover_status == crossover_status_t::NUMERICAL_ISSUES) { + settings_.log.printf("MIP Unbounded\n"); + if (settings_.heuristic_preemption_callback != nullptr) { + settings_.heuristic_preemption_callback(); + } + return mip_status_t::UNBOUNDED; + } + + if (crossover_status == crossover_status_t::TIME_LIMIT) { + solver_status_ = mip_exploration_status_t::TIME_LIMIT; + return set_final_solution(solution, -inf); + } + global_root_concurrent_halt = 1; // Stop dual simplex + } + // Override the root relaxation solution with the crossover solution + root_relax_soln_ = root_crossover_soln_; + root_vstatus_ = crossover_vstatus_; + } else { + root_status = root_status_future.get(); + } + } + exploration_stats_.total_lp_iters = root_relax_soln_.iterations; exploration_stats_.total_lp_solve_time = toc(exploration_stats_.start_time); From 951719c0fdb122ee4f0122ec08fda95e6dfdde97 Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Tue, 2 Dec 2025 15:29:41 -0800 Subject: [PATCH 34/51] Fix issue in crossover status --- cpp/src/dual_simplex/branch_and_bound.cpp | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/cpp/src/dual_simplex/branch_and_bound.cpp b/cpp/src/dual_simplex/branch_and_bound.cpp index c00887db8d..8e1281d91e 100644 --- a/cpp/src/dual_simplex/branch_and_bound.cpp +++ b/cpp/src/dual_simplex/branch_and_bound.cpp @@ -1304,23 +1304,8 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut } else { // Solution was found by crossover if (crossover_status == crossover_status_t::NUMERICAL_ISSUES) { - settings_.log.printf("MIP Infeasible\n"); - // FIXME: rarely dual simplex detects infeasible whereas it is feasible. - // to add a small safety net, check if there is a primal solution already. - // Uncomment this if the issue with cost266-UUE is resolved - // if (settings.heuristic_preemption_callback != nullptr) { - // settings.heuristic_preemption_callback(); - // } return mip_status_t::INFEASIBLE; } - if (crossover_status == crossover_status_t::NUMERICAL_ISSUES) { - settings_.log.printf("MIP Unbounded\n"); - if (settings_.heuristic_preemption_callback != nullptr) { - settings_.heuristic_preemption_callback(); - } - return mip_status_t::UNBOUNDED; - } - if (crossover_status == crossover_status_t::TIME_LIMIT) { solver_status_ = mip_exploration_status_t::TIME_LIMIT; return set_final_solution(solution, -inf); From b819417d28d1067fa1d6e8b575865678c41d064b Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Tue, 2 Dec 2025 15:33:08 -0800 Subject: [PATCH 35/51] Add ability to halt dual simplex --- cpp/src/dual_simplex/crossover.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cpp/src/dual_simplex/crossover.cpp b/cpp/src/dual_simplex/crossover.cpp index f5517c01ec..23d9a0e8e0 100644 --- a/cpp/src/dual_simplex/crossover.cpp +++ b/cpp/src/dual_simplex/crossover.cpp @@ -1387,7 +1387,10 @@ crossover_status_t crossover(const lp_problem_t& lp, crossover_status_t status = crossover_status_t::NUMERICAL_ISSUES; if (dual_feasible) { status = crossover_status_t::DUAL_FEASIBLE; } if (primal_feasible) { status = crossover_status_t::PRIMAL_FEASIBLE; } - if (primal_feasible && dual_feasible) { status = crossover_status_t::OPTIMAL; } + if (primal_feasible && dual_feasible) { + status = crossover_status_t::OPTIMAL; + if (settings.concurrent_halt != nullptr) { *settings.concurrent_halt = 1; } + } return status; } From 27db29a45f511f0c3f0c84698175d6d8e32ed5f6 Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Tue, 2 Dec 2025 16:13:10 -0800 Subject: [PATCH 36/51] Use local halting mechanism in bb --- cpp/src/dual_simplex/branch_and_bound.cpp | 12 +++++++++--- cpp/src/dual_simplex/branch_and_bound.hpp | 2 -- cpp/src/mip/solver.cu | 4 ---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cpp/src/dual_simplex/branch_and_bound.cpp b/cpp/src/dual_simplex/branch_and_bound.cpp index 8e1281d91e..0ce76b2393 100644 --- a/cpp/src/dual_simplex/branch_and_bound.cpp +++ b/cpp/src/dual_simplex/branch_and_bound.cpp @@ -33,6 +33,8 @@ namespace cuopt::linear_programming::dual_simplex { +volatile int global_root_concurrent_halt; + namespace { template @@ -1238,7 +1240,9 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut settings_.log.printf("Solving LP root relaxation\n"); simplex_solver_settings_t lp_settings = settings_; lp_status_t root_status; - lp_settings.inside_mip = 1; + lp_settings.inside_mip = 1; + global_root_concurrent_halt = 0; + lp_settings.concurrent_halt = &global_root_concurrent_halt; // RINS/SUBMIP path if (!is_main_thread()) { root_status = solve_linear_program_advanced(original_lp_, @@ -1288,8 +1292,10 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut root_crossover_soln_.z = crushed_root_z; // Call crossover on the crushed solution - crossover_status_t crossover_status = crossover(original_lp_, - settings_, + auto root_crossover_settings = settings_; + root_crossover_settings.concurrent_halt = &global_root_concurrent_halt; + crossover_status_t crossover_status = crossover(original_lp_, + root_crossover_settings, root_crossover_soln_, exploration_stats_.start_time, root_crossover_soln_, diff --git a/cpp/src/dual_simplex/branch_and_bound.hpp b/cpp/src/dual_simplex/branch_and_bound.hpp index b5d4d33616..fbc9e8700c 100644 --- a/cpp/src/dual_simplex/branch_and_bound.hpp +++ b/cpp/src/dual_simplex/branch_and_bound.hpp @@ -61,8 +61,6 @@ enum class thread_type_t { DIVING = 1, }; -extern volatile int global_root_concurrent_halt; - template class bounds_strengthening_t; diff --git a/cpp/src/mip/solver.cu b/cpp/src/mip/solver.cu index d46941c621..40eda98346 100644 --- a/cpp/src/mip/solver.cu +++ b/cpp/src/mip/solver.cu @@ -27,8 +27,6 @@ #include #include -volatile int cuopt::linear_programming::dual_simplex::global_root_concurrent_halt = 0; - namespace cuopt::linear_programming::detail { // This serves as both a warm up but also a mandatory initial call to setup cuSparse and cuBLAS @@ -152,8 +150,6 @@ solution_t mip_solver_t::run_solver() std::future branch_and_bound_status_future; dual_simplex::user_problem_t branch_and_bound_problem(context.problem_ptr->handle_ptr); dual_simplex::simplex_solver_settings_t branch_and_bound_settings; - dual_simplex::global_root_concurrent_halt = 0; - branch_and_bound_settings.concurrent_halt = &dual_simplex::global_root_concurrent_halt; std::unique_ptr> branch_and_bound; branch_and_bound_solution_helper_t solution_helper(&dm, branch_and_bound_settings); dual_simplex::mip_solution_t branch_and_bound_solution(1); From eb50ddacefd0defce55f9225ab3836e6912fe6de Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Tue, 2 Dec 2025 16:29:34 -0800 Subject: [PATCH 37/51] Revert concurrent solve and add new inside_mip setting instead of arg --- .../pdlp/solver_settings.hpp | 1 + .../cuopt/linear_programming/solve.hpp | 6 +- cpp/src/linear_programming/solve.cu | 58 ++++---- cpp/src/linear_programming/solve.cuh | 3 +- cpp/src/mip/diversity/diversity_manager.cu | 132 +++++++----------- cpp/src/mip/problem/problem_helpers.cuh | 8 -- 6 files changed, 83 insertions(+), 125 deletions(-) diff --git a/cpp/include/cuopt/linear_programming/pdlp/solver_settings.hpp b/cpp/include/cuopt/linear_programming/pdlp/solver_settings.hpp index 2ee74b4a1f..a268bd6c1d 100644 --- a/cpp/include/cuopt/linear_programming/pdlp/solver_settings.hpp +++ b/cpp/include/cuopt/linear_programming/pdlp/solver_settings.hpp @@ -211,6 +211,7 @@ class pdlp_solver_settings_t { bool presolve{false}; bool dual_postsolve{true}; method_t method{method_t::Concurrent}; + bool inside_mip{false}; // For concurrent termination volatile int* concurrent_halt{nullptr}; static constexpr f_t minimal_absolute_tolerance = 1.0e-12; diff --git a/cpp/include/cuopt/linear_programming/solve.hpp b/cpp/include/cuopt/linear_programming/solve.hpp index 041ada2e2f..364fee30aa 100644 --- a/cpp/include/cuopt/linear_programming/solve.hpp +++ b/cpp/include/cuopt/linear_programming/solve.hpp @@ -43,8 +43,7 @@ optimization_problem_solution_t solve_lp( pdlp_solver_settings_t const& settings = pdlp_solver_settings_t{}, bool problem_checking = true, bool use_pdlp_solver_mode = true, - bool is_batch_mode = false, - bool inside_mip = false); + bool is_batch_mode = false); /** * @brief Linear programming solve function. @@ -70,8 +69,7 @@ optimization_problem_solution_t solve_lp( const cuopt::mps_parser::mps_data_model_t& mps_data_model, pdlp_solver_settings_t const& settings = pdlp_solver_settings_t{}, bool problem_checking = true, - bool use_pdlp_solver_mode = true, - bool inside_mip = false); + bool use_pdlp_solver_mode = true); /** * @brief Mixed integer programming solve function. diff --git a/cpp/src/linear_programming/solve.cu b/cpp/src/linear_programming/solve.cu index d05c15067b..625d3aa22f 100644 --- a/cpp/src/linear_programming/solve.cu +++ b/cpp/src/linear_programming/solve.cu @@ -645,8 +645,7 @@ optimization_problem_solution_t run_concurrent( detail::problem_t& problem, pdlp_solver_settings_t const& settings, const timer_t& timer, - bool is_batch_mode, - bool inside_mip) + bool is_batch_mode) { CUOPT_LOG_INFO("Running concurrent\n"); timer_t timer_concurrent(timer.remaining_time()); @@ -655,10 +654,9 @@ optimization_problem_solution_t run_concurrent( pdlp_solver_settings_t settings_pdlp(settings, problem.handle_ptr->get_stream()); // Set the concurrent halt pointer - global_concurrent_halt = 0; - if (settings.concurrent_halt == nullptr) { - settings_pdlp.concurrent_halt = &global_concurrent_halt; - } + global_concurrent_halt = 0; + settings_pdlp.concurrent_halt = &global_concurrent_halt; + // Initialize the dual simplex structures before we run PDLP. // Otherwise, CUDA API calls to the problem stream may occur in both threads and throw graph // capture off @@ -674,7 +672,7 @@ optimization_problem_solution_t run_concurrent( std::tuple, dual_simplex::lp_status_t, f_t, f_t, f_t>> sol_dual_simplex_ptr; std::thread dual_simplex_thread; - if (!inside_mip) { + if (!settings.inside_mip) { dual_simplex_thread = std::thread(run_dual_simplex_thread, std::ref(dual_simplex_problem), std::ref(settings_pdlp), @@ -696,7 +694,7 @@ optimization_problem_solution_t run_concurrent( auto sol_pdlp = run_pdlp(problem, settings_pdlp, timer, is_batch_mode); // Wait for dual simplex thread to finish - if (!inside_mip) { dual_simplex_thread.join(); } + if (!settings.inside_mip) { dual_simplex_thread.join(); } // Wait for barrier thread to finish barrier_handle.sync_stream(); @@ -704,15 +702,16 @@ optimization_problem_solution_t run_concurrent( // copy the dual simplex solution to the device auto sol_dual_simplex = - !inside_mip ? convert_dual_simplex_sol(problem, - std::get<0>(*sol_dual_simplex_ptr), - std::get<1>(*sol_dual_simplex_ptr), - std::get<2>(*sol_dual_simplex_ptr), - std::get<3>(*sol_dual_simplex_ptr), - std::get<4>(*sol_dual_simplex_ptr), - 0) - : optimization_problem_solution_t{ - pdlp_termination_status_t::ConcurrentLimit, problem.handle_ptr->get_stream()}; + !settings.inside_mip + ? convert_dual_simplex_sol(problem, + std::get<0>(*sol_dual_simplex_ptr), + std::get<1>(*sol_dual_simplex_ptr), + std::get<2>(*sol_dual_simplex_ptr), + std::get<3>(*sol_dual_simplex_ptr), + std::get<4>(*sol_dual_simplex_ptr), + 0) + : optimization_problem_solution_t{pdlp_termination_status_t::ConcurrentLimit, + problem.handle_ptr->get_stream()}; // copy the barrier solution to the device auto sol_barrier = convert_dual_simplex_sol(problem, @@ -727,7 +726,7 @@ optimization_problem_solution_t run_concurrent( CUOPT_LOG_INFO( "Concurrent time: %.3fs, total time %.3fs", timer_concurrent.elapsed_time(), end_time); // Check status to see if we should return the pdlp solution or the dual simplex solution - if (!inside_mip && + if (!settings.inside_mip && (sol_dual_simplex.get_termination_status() == pdlp_termination_status_t::Optimal || sol_dual_simplex.get_termination_status() == pdlp_termination_status_t::PrimalInfeasible || sol_dual_simplex.get_termination_status() == pdlp_termination_status_t::DualInfeasible)) { @@ -767,15 +766,14 @@ optimization_problem_solution_t solve_lp_with_method( detail::problem_t& problem, pdlp_solver_settings_t const& settings, const timer_t& timer, - bool is_batch_mode, - bool inside_mip) + bool is_batch_mode) { if (settings.method == method_t::DualSimplex) { return run_dual_simplex(problem, settings, timer); } else if (settings.method == method_t::Barrier) { return run_barrier(problem, settings, timer); } else if (settings.method == method_t::Concurrent) { - return run_concurrent(problem, settings, timer, is_batch_mode, inside_mip); + return run_concurrent(problem, settings, timer, is_batch_mode); } else { return run_pdlp(problem, settings, timer, is_batch_mode); } @@ -786,8 +784,7 @@ optimization_problem_solution_t solve_lp(optimization_problem_t const& settings, bool problem_checking, bool use_pdlp_solver_mode, - bool is_batch_mode, - bool inside_mip) + bool is_batch_mode) { try { // Create log stream for file logging and add it to default logger @@ -868,7 +865,7 @@ optimization_problem_solution_t solve_lp(optimization_problem_tget_stream()); - auto solution = solve_lp_with_method(problem, settings, lp_timer, is_batch_mode, inside_mip); + auto solution = solve_lp_with_method(problem, settings, lp_timer, is_batch_mode); if (run_presolve) { auto primal_solution = cuopt::device_copy(solution.get_primal_solution(), @@ -1003,10 +1000,10 @@ optimization_problem_solution_t solve_lp( pdlp_solver_settings_t const& settings, bool problem_checking, bool use_pdlp_solver_mode, - bool inside_mip) + bool is_batch_mode) { auto op_problem = mps_data_model_to_optimization_problem(handle_ptr, mps_data_model); - return solve_lp(op_problem, settings, problem_checking, use_pdlp_solver_mode, inside_mip); + return solve_lp(op_problem, settings, problem_checking, use_pdlp_solver_mode, is_batch_mode); } #define INSTANTIATE(F_TYPE) \ @@ -1015,23 +1012,20 @@ optimization_problem_solution_t solve_lp( pdlp_solver_settings_t const& settings, \ bool problem_checking, \ bool use_pdlp_solver_mode, \ - bool is_batch_mode, \ - bool inside_mip); \ + bool is_batch_mode); \ \ template optimization_problem_solution_t solve_lp( \ raft::handle_t const* handle_ptr, \ const cuopt::mps_parser::mps_data_model_t& mps_data_model, \ pdlp_solver_settings_t const& settings, \ bool problem_checking, \ - bool use_pdlp_solver_mode, \ - bool inside_mip); \ + bool use_pdlp_solver_mode); \ \ template optimization_problem_solution_t solve_lp_with_method( \ detail::problem_t& problem, \ pdlp_solver_settings_t const& settings, \ const timer_t& timer, \ - bool is_batch_mode, \ - bool inside_mip); \ + bool is_batch_mode); \ \ template optimization_problem_t mps_data_model_to_optimization_problem( \ raft::handle_t const* handle_ptr, \ diff --git a/cpp/src/linear_programming/solve.cuh b/cpp/src/linear_programming/solve.cuh index ab29c6f9e6..a3c3240f40 100644 --- a/cpp/src/linear_programming/solve.cuh +++ b/cpp/src/linear_programming/solve.cuh @@ -25,8 +25,7 @@ cuopt::linear_programming::optimization_problem_solution_t solve_lp_wi detail::problem_t& problem, pdlp_solver_settings_t const& settings, const timer_t& timer, - bool is_batch_mode = false, - bool inside_mip = false); + bool is_batch_mode = false); template void set_pdlp_solver_mode(pdlp_solver_settings_t const& settings); diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index afc05f313d..46a99ea45c 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -339,46 +339,27 @@ solution_t diversity_manager_t::run_solver() if (bb_thread_solution_exists) { ls.lp_optimal_exists = true; } else if (!fj_only_run) { - // Convert greater-than constraints to less-than constraints before calling LP solver - // This standardizes the constraint representation: a^T x >= lb becomes -a^T x <= -lb - // Note: This modifies the problem in-place, so all subsequent operations will use <= form - // std::cout - // << "Converting greater-than constraints to less-than constraints before calling LP solver" - // << std::endl; - // convert_greater_to_less(*problem_ptr); - relaxed_lp_settings_t lp_settings; - lp_settings.time_limit = lp_time_limit; - lp_settings.tolerance = context.settings.tolerances.absolute_tolerance; - lp_settings.return_first_feasible = false; - lp_settings.save_state = true; - lp_settings.concurrent_halt = &global_concurrent_halt; - lp_settings.has_initial_primal = false; + convert_greater_to_less(*problem_ptr); + + f_t tolerance_divisor = + problem_ptr->tolerances.absolute_tolerance / problem_ptr->tolerances.relative_tolerance; + if (tolerance_divisor == 0) { tolerance_divisor = 1; } + f_t absolute_tolerance = context.settings.tolerances.absolute_tolerance; + + pdlp_solver_settings_t pdlp_settings{}; + pdlp_settings.tolerances.relative_primal_tolerance = absolute_tolerance / tolerance_divisor; + pdlp_settings.tolerances.relative_dual_tolerance = absolute_tolerance / tolerance_divisor; + pdlp_settings.time_limit = lp_time_limit; + pdlp_settings.first_primal_feasible = false; + pdlp_settings.concurrent_halt = &global_concurrent_halt; + pdlp_settings.method = method_t::Concurrent; + pdlp_settings.inside_mip = true; + pdlp_settings.pdlp_solver_mode = pdlp_solver_mode_t::Stable2; + rmm::device_uvector lp_optimal_solution_copy(lp_optimal_solution.size(), problem_ptr->handle_ptr->get_stream()); - auto lp_result = - get_relaxed_lp_solution(*problem_ptr, lp_optimal_solution_copy, lp_state, lp_settings); - // f_t tolerance_divisor = - // problem_ptr->tolerances.absolute_tolerance / problem_ptr->tolerances.relative_tolerance; - // if (tolerance_divisor == 0) { tolerance_divisor = 1; } - // f_t absolute_tolerance = context.settings.tolerances.absolute_tolerance; - - // pdlp_solver_settings_t pdlp_settings{}; - // pdlp_settings.tolerances.relative_primal_tolerance = absolute_tolerance / tolerance_divisor; - // pdlp_settings.tolerances.relative_dual_tolerance = absolute_tolerance / tolerance_divisor; - // pdlp_settings.time_limit = lp_time_limit; - // pdlp_settings.first_primal_feasible = false; - // pdlp_settings.concurrent_halt = &global_concurrent_halt; - // pdlp_settings.method = method_t::Concurrent; - // pdlp_settings.pdlp_solver_mode = pdlp_solver_mode_t::Stable2; - - // rmm::device_uvector lp_optimal_solution_copy(lp_optimal_solution.size(), - // problem_ptr->handle_ptr->get_stream()); - // timer_t lp_timer(lp_time_limit); - // auto const is_batch = false; - // auto const inside_mip = true; - // auto lp_result = - // solve_lp_with_method(*problem_ptr, pdlp_settings, lp_timer, is_batch, - // inside_mip); + timer_t lp_timer(lp_time_limit); + auto lp_result = solve_lp_with_method(*problem_ptr, pdlp_settings, lp_timer); { std::lock_guard guard(relaxed_solution_mutex); @@ -416,48 +397,41 @@ solution_t diversity_manager_t::run_solver() // to bring variables within the bounds } - // CUOPT_LOG_INFO( - // "Optimal solution exists: %i, PDLP relaxed solution: objective %f, iterations %d", - // ls.lp_optimal_exists, - // lp_result.get_objective_value(), - // lp_result.get_additional_termination_information().number_of_steps_taken); - // Send PDLP relaxed solution to branch and bound before it solves the root node - // if (problem_ptr->set_root_relaxation_solution_callback != nullptr) { - // auto& d_primal_solution = lp_result.get_primal_solution(); - // auto& d_dual_solution = lp_result.get_dual_solution(); - // auto& d_reduced_costs = lp_result.get_reduced_cost(); - // // cuopt::print("primal_solution", d_primal_solution); - // // cuopt::print("dual_solution", d_dual_solution); - // // cuopt::print("reduced_costs", d_reduced_costs); - // std::vector host_primal(d_primal_solution.size()); - // std::vector host_dual(d_dual_solution.size()); - // std::vector host_reduced_costs(d_reduced_costs.size()); - // raft::copy(host_primal.data(), - // d_primal_solution.data(), - // d_primal_solution.size(), - // problem_ptr->handle_ptr->get_stream()); - // raft::copy(host_dual.data(), - // d_dual_solution.data(), - // d_dual_solution.size(), - // problem_ptr->handle_ptr->get_stream()); - // raft::copy(host_reduced_costs.data(), - // d_reduced_costs.data(), - // d_reduced_costs.size(), - // problem_ptr->handle_ptr->get_stream()); - // problem_ptr->handle_ptr->sync_stream(); - - // auto user_obj = - // problem_ptr->get_user_obj_from_solver_obj(lp_result.get_objective_value()); auto iterations - // = lp_result.get_additional_termination_information().number_of_steps_taken; - // // Set for the B&B - // problem_ptr->set_root_relaxation_solution_callback(host_primal, - // host_dual, - // host_reduced_costs, - // lp_result.get_objective_value(), - // user_obj, - // iterations); - // } + if (problem_ptr->set_root_relaxation_solution_callback != nullptr) { + auto& d_primal_solution = lp_result.get_primal_solution(); + auto& d_dual_solution = lp_result.get_dual_solution(); + auto& d_reduced_costs = lp_result.get_reduced_cost(); + // cuopt::print("primal_solution", d_primal_solution); + // cuopt::print("dual_solution", d_dual_solution); + // cuopt::print("reduced_costs", d_reduced_costs); + std::vector host_primal(d_primal_solution.size()); + std::vector host_dual(d_dual_solution.size()); + std::vector host_reduced_costs(d_reduced_costs.size()); + raft::copy(host_primal.data(), + d_primal_solution.data(), + d_primal_solution.size(), + problem_ptr->handle_ptr->get_stream()); + raft::copy(host_dual.data(), + d_dual_solution.data(), + d_dual_solution.size(), + problem_ptr->handle_ptr->get_stream()); + raft::copy(host_reduced_costs.data(), + d_reduced_costs.data(), + d_reduced_costs.size(), + problem_ptr->handle_ptr->get_stream()); + problem_ptr->handle_ptr->sync_stream(); + + auto user_obj = problem_ptr->get_user_obj_from_solver_obj(lp_result.get_objective_value()); + auto iterations = lp_result.get_additional_termination_information().number_of_steps_taken; + // Set for the B&B + problem_ptr->set_root_relaxation_solution_callback(host_primal, + host_dual, + host_reduced_costs, + lp_result.get_objective_value(), + user_obj, + iterations); + } // in case the pdlp returned var boudns that are out of bounds clamp_within_var_bounds(lp_optimal_solution, problem_ptr, problem_ptr->handle_ptr); diff --git a/cpp/src/mip/problem/problem_helpers.cuh b/cpp/src/mip/problem/problem_helpers.cuh index 1f97d51ec8..517b52a1d9 100644 --- a/cpp/src/mip/problem/problem_helpers.cuh +++ b/cpp/src/mip/problem/problem_helpers.cuh @@ -312,7 +312,6 @@ static bool check_bounds_sanity(const detail::problem_t& problem) check_constraint_bounds_sanity(problem); } -// Kernel to negate coefficients for greater-than constraints template __global__ void kernel_convert_greater_to_less(raft::device_span coefficients, raft::device_span offsets, @@ -324,27 +323,22 @@ __global__ void kernel_convert_greater_to_less(raft::device_span coefficien const f_t lb = constraint_lower_bounds[constraint_id]; const f_t ub = constraint_upper_bounds[constraint_id]; - // Check if this is a greater-than constraint (lb is finite and ub is infinite) if (!isfinite(lb) || isfinite(ub)) return; auto row_start = offsets[constraint_id]; auto row_end = offsets[constraint_id + 1]; auto row_size = row_end - row_start; - // Negate all coefficients in this constraint row for (i_t tid = threadIdx.x; tid < row_size; tid += blockDim.x) { coefficients[row_start + tid] = -coefficients[row_start + tid]; } - // Negate the bounds for this constraint if (threadIdx.x == 0) { constraint_lower_bounds[constraint_id] = -ub; constraint_upper_bounds[constraint_id] = -lb; } } -// Function to convert greater-than constraints to less-than constraints -// This modifies the problem in-place by negating coefficient matrices and swapping bounds template static void convert_greater_to_less(detail::problem_t& problem) { @@ -352,7 +346,6 @@ static void convert_greater_to_less(detail::problem_t& problem) auto* handle_ptr = problem.handle_ptr; - // Negate coefficients and bounds for all greater-than constraints constexpr i_t TPB = 256; kernel_convert_greater_to_less <<get_stream()>>>( @@ -364,7 +357,6 @@ static void convert_greater_to_less(detail::problem_t& problem) problem.constraint_upper_bounds.size())); RAFT_CHECK_CUDA(handle_ptr->get_stream()); - // Recompute the transpose since we modified the coefficients problem.compute_transpose_of_problem(); handle_ptr->sync_stream(); From f89562e9cfe4f35d78b980851a884bced7f929e4 Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Tue, 2 Dec 2025 16:30:41 -0800 Subject: [PATCH 38/51] Add back dual res inf norm assert --- cpp/src/dual_simplex/presolve.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/dual_simplex/presolve.cpp b/cpp/src/dual_simplex/presolve.cpp index 7b2634c70b..0b200ee041 100644 --- a/cpp/src/dual_simplex/presolve.cpp +++ b/cpp/src/dual_simplex/presolve.cpp @@ -1119,7 +1119,7 @@ f_t crush_dual_solution(const user_problem_t& user_problem, } } const f_t dual_res_inf = vector_norm_inf(dual_residual); - // assert(dual_res_inf < 1e-4); + assert(dual_res_inf < 1e-6); return dual_res_inf; } From 8772731b20e773415fef898d6d07a9b5a5c3f326 Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Tue, 2 Dec 2025 16:41:42 -0800 Subject: [PATCH 39/51] Fix compile error --- cpp/src/linear_programming/solve.cu | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cpp/src/linear_programming/solve.cu b/cpp/src/linear_programming/solve.cu index 625d3aa22f..2a1e03f451 100644 --- a/cpp/src/linear_programming/solve.cu +++ b/cpp/src/linear_programming/solve.cu @@ -999,11 +999,10 @@ optimization_problem_solution_t solve_lp( const cuopt::mps_parser::mps_data_model_t& mps_data_model, pdlp_solver_settings_t const& settings, bool problem_checking, - bool use_pdlp_solver_mode, - bool is_batch_mode) + bool use_pdlp_solver_mode) { auto op_problem = mps_data_model_to_optimization_problem(handle_ptr, mps_data_model); - return solve_lp(op_problem, settings, problem_checking, use_pdlp_solver_mode, is_batch_mode); + return solve_lp(op_problem, settings, problem_checking, use_pdlp_solver_mode); } #define INSTANTIATE(F_TYPE) \ From d33bcc6cf4ff2f5ffd1e9495ca2ad90457b329f0 Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Tue, 2 Dec 2025 16:57:56 -0800 Subject: [PATCH 40/51] Fix bug incorrectly setting crossover root sol --- cpp/src/dual_simplex/branch_and_bound.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/cpp/src/dual_simplex/branch_and_bound.cpp b/cpp/src/dual_simplex/branch_and_bound.cpp index d98de8d660..30b4fa1215 100644 --- a/cpp/src/dual_simplex/branch_and_bound.cpp +++ b/cpp/src/dual_simplex/branch_and_bound.cpp @@ -1306,9 +1306,10 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut // Check if crossover was stopped by dual simplex if (crossover_status == crossover_status_t::CONCURRENT_LIMIT) { + settings_.log.printf("Crossover stopped by dual simplex\n"); root_status = root_status_future.get(); } else { - // Solution was found by crossover + // Crossover finished before dual simplex if (crossover_status == crossover_status_t::NUMERICAL_ISSUES) { return mip_status_t::INFEASIBLE; } @@ -1317,10 +1318,11 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut return set_final_solution(solution, -inf); } global_root_concurrent_halt = 1; // Stop dual simplex + settings_.log.printf("Dual simplex stopped by crossover, crossover found a basis\n"); + // Override the root relaxation solution with the crossover solution + root_relax_soln_ = root_crossover_soln_; + root_vstatus_ = crossover_vstatus_; } - // Override the root relaxation solution with the crossover solution - root_relax_soln_ = root_crossover_soln_; - root_vstatus_ = crossover_vstatus_; } else { root_status = root_status_future.get(); } From a6770c169171d8c91484b610893bbc118ca9c50e Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Tue, 2 Dec 2025 17:05:53 -0800 Subject: [PATCH 41/51] Revert test --- cpp/tests/mip/miplib_test.cu | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cpp/tests/mip/miplib_test.cu b/cpp/tests/mip/miplib_test.cu index 9e3debdbee..f72874d494 100644 --- a/cpp/tests/mip/miplib_test.cu +++ b/cpp/tests/mip/miplib_test.cu @@ -64,10 +64,7 @@ TEST(mip_solve, run_small_tests) { mip_solver_settings_t settings; std::vector test_instances = { - {"mip/50v-10.mps", 11311031.}, - // {"mip/neos5.mps", 15.}, - // {"mip/swath1.mps", 1300.} - }; + {"mip/50v-10.mps", 11311031.}, {"mip/neos5.mps", 15.}, {"mip/swath1.mps", 1300.}}; for (const auto& test_instance : test_instances) { test_miplib_file(test_instance, settings); } From deab57a5a018a0414516eafea994ef12c88800ad Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Tue, 2 Dec 2025 17:51:41 -0800 Subject: [PATCH 42/51] Handle remaining crossover status --- cpp/src/dual_simplex/branch_and_bound.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cpp/src/dual_simplex/branch_and_bound.cpp b/cpp/src/dual_simplex/branch_and_bound.cpp index 30b4fa1215..fc52cb07bd 100644 --- a/cpp/src/dual_simplex/branch_and_bound.cpp +++ b/cpp/src/dual_simplex/branch_and_bound.cpp @@ -1310,7 +1310,9 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut root_status = root_status_future.get(); } else { // Crossover finished before dual simplex - if (crossover_status == crossover_status_t::NUMERICAL_ISSUES) { + if (crossover_status == crossover_status_t::NUMERICAL_ISSUES || + crossover_status == crossover_status_t::PRIMAL_FEASIBLE || + crossover_status == crossover_status_t::DUAL_FEASIBLE) { return mip_status_t::INFEASIBLE; } if (crossover_status == crossover_status_t::TIME_LIMIT) { @@ -1322,6 +1324,7 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut // Override the root relaxation solution with the crossover solution root_relax_soln_ = root_crossover_soln_; root_vstatus_ = crossover_vstatus_; + root_status = lp_status_t::OPTIMAL; } } else { root_status = root_status_future.get(); From 3ff3150f11588ee1cf4c921ba5a3099142eaa8ca Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Wed, 3 Dec 2025 17:04:52 -0800 Subject: [PATCH 43/51] Add mg option for mip root solve --- cpp/include/cuopt/linear_programming/mip/solver_settings.hpp | 1 + cpp/src/linear_programming/solve.cu | 2 +- cpp/src/math_optimization/solver_settings.cu | 3 ++- cpp/src/mip/diversity/diversity_manager.cu | 5 ++--- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/cpp/include/cuopt/linear_programming/mip/solver_settings.hpp b/cpp/include/cuopt/linear_programming/mip/solver_settings.hpp index 2c62f1b443..4f6320752a 100644 --- a/cpp/include/cuopt/linear_programming/mip/solver_settings.hpp +++ b/cpp/include/cuopt/linear_programming/mip/solver_settings.hpp @@ -81,6 +81,7 @@ class mip_solver_settings_t { f_t time_limit = std::numeric_limits::infinity(); bool heuristics_only = false; i_t num_cpu_threads = -1; // -1 means use default number of threads in branch and bound + i_t num_gpus = 1; bool log_to_console = true; std::string log_file; std::string sol_file; diff --git a/cpp/src/linear_programming/solve.cu b/cpp/src/linear_programming/solve.cu index a3c6357f59..31f4976608 100644 --- a/cpp/src/linear_programming/solve.cu +++ b/cpp/src/linear_programming/solve.cu @@ -665,8 +665,8 @@ optimization_problem_solution_t run_concurrent( // Make sure allocations are done on the original stream problem.handle_ptr->sync_stream(); - int device_count = raft::device_setter::get_device_count(); if (settings.num_gpus > 1) { + int device_count = raft::device_setter::get_device_count(); CUOPT_LOG_INFO("Running PDLP and Barrier on %d GPUs", device_count); cuopt_expects( device_count > 1, error_type_t::RuntimeError, "Multi-GPU mode requires at least 2 GPUs"); diff --git a/cpp/src/math_optimization/solver_settings.cu b/cpp/src/math_optimization/solver_settings.cu index a8cdf9c7a2..4e3dc64650 100644 --- a/cpp/src/math_optimization/solver_settings.cu +++ b/cpp/src/math_optimization/solver_settings.cu @@ -87,7 +87,8 @@ solver_settings_t::solver_settings_t() : pdlp_settings(), mip_settings {CUOPT_DUALIZE, &pdlp_settings.dualize, -1, 1, -1}, {CUOPT_ORDERING, &pdlp_settings.ordering, -1, 1, -1}, {CUOPT_BARRIER_DUAL_INITIAL_POINT, &pdlp_settings.barrier_dual_initial_point, -1, 1, -1}, - {CUOPT_NUM_GPUS, &pdlp_settings.num_gpus, 1, 2, 1} + {CUOPT_NUM_GPUS, &pdlp_settings.num_gpus, 1, 2, 1}, + {CUOPT_NUM_GPUS, &mip_settings.num_gpus, 1, 2, 1} }; // Bool parameters diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index 46a99ea45c..8eaf5c804b 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -355,6 +355,7 @@ solution_t diversity_manager_t::run_solver() pdlp_settings.method = method_t::Concurrent; pdlp_settings.inside_mip = true; pdlp_settings.pdlp_solver_mode = pdlp_solver_mode_t::Stable2; + pdlp_settings.num_gpus = context.settings.num_gpus; rmm::device_uvector lp_optimal_solution_copy(lp_optimal_solution.size(), problem_ptr->handle_ptr->get_stream()); @@ -402,9 +403,7 @@ solution_t diversity_manager_t::run_solver() auto& d_primal_solution = lp_result.get_primal_solution(); auto& d_dual_solution = lp_result.get_dual_solution(); auto& d_reduced_costs = lp_result.get_reduced_cost(); - // cuopt::print("primal_solution", d_primal_solution); - // cuopt::print("dual_solution", d_dual_solution); - // cuopt::print("reduced_costs", d_reduced_costs); + std::vector host_primal(d_primal_solution.size()); std::vector host_dual(d_dual_solution.size()); std::vector host_reduced_costs(d_reduced_costs.size()); From 86dd5c2417ded0a8cf367f91e465372f6129c1c5 Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Wed, 3 Dec 2025 17:15:07 -0800 Subject: [PATCH 44/51] Address review comments --- cpp/src/dual_simplex/branch_and_bound.cpp | 4 +--- cpp/src/dual_simplex/branch_and_bound.hpp | 6 +++--- cpp/src/linear_programming/solve.cu | 3 ++- cpp/src/mip/solver.cu | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/cpp/src/dual_simplex/branch_and_bound.cpp b/cpp/src/dual_simplex/branch_and_bound.cpp index fc52cb07bd..ea082c532a 100644 --- a/cpp/src/dual_simplex/branch_and_bound.cpp +++ b/cpp/src/dual_simplex/branch_and_bound.cpp @@ -1244,7 +1244,7 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut global_root_concurrent_halt = 0; lp_settings.concurrent_halt = &global_root_concurrent_halt; // RINS/SUBMIP path - if (!is_main_thread()) { + if (!enable_concurrent_lp_root_solve()) { root_status = solve_linear_program_advanced(original_lp_, exploration_stats_.start_time, lp_settings, @@ -1301,8 +1301,6 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut root_crossover_soln_, crossover_vstatus_); settings_.log.printf("Crossover status: %d\n", crossover_status); - exploration_stats_.total_lp_iters = root_crossover_soln_.iterations; - exploration_stats_.total_lp_solve_time = toc(exploration_stats_.start_time); // Check if crossover was stopped by dual simplex if (crossover_status == crossover_status_t::CONCURRENT_LIMIT) { diff --git a/cpp/src/dual_simplex/branch_and_bound.hpp b/cpp/src/dual_simplex/branch_and_bound.hpp index 11d523d7ad..f3dbddb014 100644 --- a/cpp/src/dual_simplex/branch_and_bound.hpp +++ b/cpp/src/dual_simplex/branch_and_bound.hpp @@ -100,7 +100,7 @@ class branch_and_bound_t { // Set a solution based on the user problem during the course of the solve void set_new_solution(const std::vector& solution); - void set_main_thread(bool main_thread) { main_thread_ = main_thread; } + void set_concurrent_lp_root_solve(bool enable) { enable_concurrent_lp_root_solve_ = enable; } // Repair a low-quality solution from the heuristics. bool repair_solution(const std::vector& leaf_edge_norms, @@ -111,7 +111,7 @@ class branch_and_bound_t { f_t get_upper_bound(); f_t get_lower_bound(); i_t get_heap_size(); - bool is_main_thread() const { return main_thread_; } + bool enable_concurrent_lp_root_solve() const { return enable_concurrent_lp_root_solve_; } // The main entry routine. Returns the solver status and populates solution with the incumbent. mip_status_t solve(mip_solution_t& solution); @@ -165,7 +165,7 @@ class branch_and_bound_t { lp_solution_t root_crossover_soln_; std::vector edge_norms_; std::atomic root_crossover_solution_set_{false}; - bool main_thread_{false}; + bool enable_concurrent_lp_root_solve_{false}; // Pseudocosts pseudo_costs_t pc_; diff --git a/cpp/src/linear_programming/solve.cu b/cpp/src/linear_programming/solve.cu index 31f4976608..0ba6e9286a 100644 --- a/cpp/src/linear_programming/solve.cu +++ b/cpp/src/linear_programming/solve.cu @@ -780,7 +780,8 @@ optimization_problem_solution_t run_concurrent( } else if (sol_pdlp.get_termination_status() == pdlp_termination_status_t::Optimal) { CUOPT_LOG_INFO("Solved with PDLP"); return sol_pdlp; - } else if (sol_pdlp.get_termination_status() == pdlp_termination_status_t::ConcurrentLimit) { + } else if (!settings.inside_mip && + sol_pdlp.get_termination_status() == pdlp_termination_status_t::ConcurrentLimit) { CUOPT_LOG_INFO("Using dual simplex solve info"); return sol_dual_simplex; } else { diff --git a/cpp/src/mip/solver.cu b/cpp/src/mip/solver.cu index 03cb50bca2..0da4c6398f 100644 --- a/cpp/src/mip/solver.cu +++ b/cpp/src/mip/solver.cu @@ -206,7 +206,7 @@ solution_t mip_solver_t::run_solver() branch_and_bound = std::make_unique>( branch_and_bound_problem, branch_and_bound_settings); context.branch_and_bound_ptr = branch_and_bound.get(); - branch_and_bound->set_main_thread(true); + branch_and_bound->set_concurrent_lp_root_solve(true); // Set the primal heuristics -> branch and bound callback context.problem_ptr->branch_and_bound_callback = From ceb5862e9038dbcca9d2f8c598ae35684ea6ee3e Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Thu, 4 Dec 2025 15:23:37 -0800 Subject: [PATCH 45/51] Add more prints --- cpp/src/dual_simplex/phase2.cpp | 5 ++++- cpp/src/linear_programming/pdlp.cu | 5 +---- cpp/src/mip/diversity/diversity_manager.cu | 1 + 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/cpp/src/dual_simplex/phase2.cpp b/cpp/src/dual_simplex/phase2.cpp index 635c2c470e..556c7ab921 100644 --- a/cpp/src/dual_simplex/phase2.cpp +++ b/cpp/src/dual_simplex/phase2.cpp @@ -2074,7 +2074,10 @@ void prepare_optimality(const lp_problem_t& lp, settings.log.printf("Dual infeasibility (abs): %.2e\n", dual_infeas); settings.log.printf("Perturbation: %.2e\n", perturbation); } else { - if (settings.concurrent_halt != nullptr) { *settings.concurrent_halt = 1; } + if (settings.concurrent_halt != nullptr) { + *settings.concurrent_halt = 1; + settings.log.printf("Setting concurrent halt in Dual Simplex Phase 2\n"); + } settings.log.printf("\n"); settings.log.printf( "Root relaxation solution found in %d iterations and %.2fs\n", iter, toc(start_time)); diff --git a/cpp/src/linear_programming/pdlp.cu b/cpp/src/linear_programming/pdlp.cu index 5d982bcc96..86bfa00047 100644 --- a/cpp/src/linear_programming/pdlp.cu +++ b/cpp/src/linear_programming/pdlp.cu @@ -314,10 +314,7 @@ std::optional> pdlp_solver_t // Check for concurrent limit if (settings_.concurrent_halt != nullptr && *settings_.concurrent_halt == 1) { -#ifdef PDLP_VERBOSE_MODE - RAFT_CUDA_TRY(cudaDeviceSynchronize()); - std::cout << "Concurrent Limit reached, returning current solution" << std::endl; -#endif + CUOPT_LOG_INFO("PDLP Concurrent Limit reached, returning current solution"); return current_termination_strategy_.fill_return_problem_solution( internal_solver_iterations_, pdhg_solver_, diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index 9c2c5a9636..209f48bddb 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -788,6 +788,7 @@ void diversity_manager_t::set_simplex_solution(const std::vector& std::lock_guard lock(relaxed_solution_mutex); simplex_solution_exists.store(true, std::memory_order_release); global_concurrent_halt = 1; + CUOPT_LOG_INFO("Setting concurrent halt for PDLP inside diversity manager"); // global_concurrent_halt.store(1, std::memory_order_release); // it is safe to use lp_optimal_solution while executing the copy operation // the operations are ordered as long as they are on the same stream From 98c92f20b0eeef5de0813249623d0ebb31f29e2d Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Fri, 5 Dec 2025 10:04:21 -0800 Subject: [PATCH 46/51] Wait for future in every case --- cpp/src/dual_simplex/branch_and_bound.cpp | 22 ++++++---------------- cpp/src/dual_simplex/phase2.cpp | 2 +- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/cpp/src/dual_simplex/branch_and_bound.cpp b/cpp/src/dual_simplex/branch_and_bound.cpp index ea082c532a..9688ac2a2d 100644 --- a/cpp/src/dual_simplex/branch_and_bound.cpp +++ b/cpp/src/dual_simplex/branch_and_bound.cpp @@ -1285,7 +1285,6 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut root_crossover_soln_.z, crushed_root_y, crushed_root_z); - settings_.log.printf("Dual residual inf: %e\n", dual_res_inf); root_crossover_soln_.x = crushed_root_x; root_crossover_soln_.y = crushed_root_y; @@ -1303,26 +1302,17 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut settings_.log.printf("Crossover status: %d\n", crossover_status); // Check if crossover was stopped by dual simplex - if (crossover_status == crossover_status_t::CONCURRENT_LIMIT) { - settings_.log.printf("Crossover stopped by dual simplex\n"); - root_status = root_status_future.get(); - } else { - // Crossover finished before dual simplex - if (crossover_status == crossover_status_t::NUMERICAL_ISSUES || - crossover_status == crossover_status_t::PRIMAL_FEASIBLE || - crossover_status == crossover_status_t::DUAL_FEASIBLE) { - return mip_status_t::INFEASIBLE; - } - if (crossover_status == crossover_status_t::TIME_LIMIT) { - solver_status_ = mip_exploration_status_t::TIME_LIMIT; - return set_final_solution(solution, -inf); - } - global_root_concurrent_halt = 1; // Stop dual simplex + if (crossover_status == crossover_status_t::OPTIMAL) { settings_.log.printf("Dual simplex stopped by crossover, crossover found a basis\n"); + global_root_concurrent_halt = 1; // Stop dual simplex + root_status = root_status_future.get(); // Override the root relaxation solution with the crossover solution root_relax_soln_ = root_crossover_soln_; root_vstatus_ = crossover_vstatus_; root_status = lp_status_t::OPTIMAL; + } else { + settings_.log.printf("Dual simplex finished\n"); + root_status = root_status_future.get(); } } else { root_status = root_status_future.get(); diff --git a/cpp/src/dual_simplex/phase2.cpp b/cpp/src/dual_simplex/phase2.cpp index 556c7ab921..ccf304b2fa 100644 --- a/cpp/src/dual_simplex/phase2.cpp +++ b/cpp/src/dual_simplex/phase2.cpp @@ -2075,8 +2075,8 @@ void prepare_optimality(const lp_problem_t& lp, settings.log.printf("Perturbation: %.2e\n", perturbation); } else { if (settings.concurrent_halt != nullptr) { - *settings.concurrent_halt = 1; settings.log.printf("Setting concurrent halt in Dual Simplex Phase 2\n"); + *settings.concurrent_halt = 1; } settings.log.printf("\n"); settings.log.printf( From 65e655b4e0fd0b7d0982ec4e7b34d150ca94013e Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Fri, 5 Dec 2025 20:16:45 -0800 Subject: [PATCH 47/51] Disable pdlp logs --- cpp/src/dual_simplex/phase2.cpp | 8 ++++---- cpp/src/linear_programming/solve.cu | 1 + cpp/src/linear_programming/solver_settings.cu | 3 ++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/cpp/src/dual_simplex/phase2.cpp b/cpp/src/dual_simplex/phase2.cpp index ccf304b2fa..125be9dd53 100644 --- a/cpp/src/dual_simplex/phase2.cpp +++ b/cpp/src/dual_simplex/phase2.cpp @@ -2074,10 +2074,6 @@ void prepare_optimality(const lp_problem_t& lp, settings.log.printf("Dual infeasibility (abs): %.2e\n", dual_infeas); settings.log.printf("Perturbation: %.2e\n", perturbation); } else { - if (settings.concurrent_halt != nullptr) { - settings.log.printf("Setting concurrent halt in Dual Simplex Phase 2\n"); - *settings.concurrent_halt = 1; - } settings.log.printf("\n"); settings.log.printf( "Root relaxation solution found in %d iterations and %.2fs\n", iter, toc(start_time)); @@ -2985,6 +2981,10 @@ dual::status_t dual_phase2_with_advanced_basis(i_t phase, 100.0 * dense_delta_z / (sparse_delta_z + dense_delta_z)); ft.print_stats(); } + if (settings.inside_mip && settings.concurrent_halt != nullptr) { + settings.log.printf("Setting concurrent halt in Dual Simplex Phase 2\n"); + *settings.concurrent_halt = 1; + } } return status; } diff --git a/cpp/src/linear_programming/solve.cu b/cpp/src/linear_programming/solve.cu index 0ba6e9286a..bea8b93657 100644 --- a/cpp/src/linear_programming/solve.cu +++ b/cpp/src/linear_programming/solve.cu @@ -535,6 +535,7 @@ static optimization_problem_solution_t run_pdlp_solver( problem.handle_ptr->get_stream()}; } detail::pdlp_solver_t solver(problem, settings, is_batch_mode); + if (settings.inside_mip) { solver.set_inside_mip(true); } return solver.run_solver(timer); } diff --git a/cpp/src/linear_programming/solver_settings.cu b/cpp/src/linear_programming/solver_settings.cu index 154b5cce37..777dc84ebc 100644 --- a/cpp/src/linear_programming/solver_settings.cu +++ b/cpp/src/linear_programming/solver_settings.cu @@ -46,7 +46,8 @@ pdlp_solver_settings_t::pdlp_solver_settings_t(const pdlp_solver_setti first_primal_feasible(other.first_primal_feasible), pdlp_warm_start_data_(other.pdlp_warm_start_data_, stream_view), concurrent_halt(other.concurrent_halt), - num_gpus(other.num_gpus) + num_gpus(other.num_gpus), + inside_mip(other.inside_mip) { } From 606ccaf3b4264322c012af6fb58a74dc57edf0c4 Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Fri, 5 Dec 2025 21:55:15 -0800 Subject: [PATCH 48/51] Add small sleep --- cpp/src/dual_simplex/branch_and_bound.cpp | 21 ++++++++++----------- cpp/src/dual_simplex/branch_and_bound.hpp | 3 +++ 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/cpp/src/dual_simplex/branch_and_bound.cpp b/cpp/src/dual_simplex/branch_and_bound.cpp index 9688ac2a2d..5d44273a2e 100644 --- a/cpp/src/dual_simplex/branch_and_bound.cpp +++ b/cpp/src/dual_simplex/branch_and_bound.cpp @@ -33,8 +33,6 @@ namespace cuopt::linear_programming::dual_simplex { -volatile int global_root_concurrent_halt; - namespace { template @@ -1241,8 +1239,7 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut simplex_solver_settings_t lp_settings = settings_; lp_status_t root_status; lp_settings.inside_mip = 1; - global_root_concurrent_halt = 0; - lp_settings.concurrent_halt = &global_root_concurrent_halt; + lp_settings.concurrent_halt = get_global_root_concurrent_halt(); // RINS/SUBMIP path if (!enable_concurrent_lp_root_solve()) { root_status = solve_linear_program_advanced(original_lp_, @@ -1266,7 +1263,8 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut // Wait for the root relaxation solution to be sent by the diversity manager or dual simplex // to finish while (!root_crossover_solution_set_.load(std::memory_order_acquire) && - global_root_concurrent_halt == 0) { + *get_global_root_concurrent_halt() == 0) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); continue; } @@ -1292,26 +1290,27 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut // Call crossover on the crushed solution auto root_crossover_settings = settings_; - root_crossover_settings.concurrent_halt = &global_root_concurrent_halt; + root_crossover_settings.concurrent_halt = get_global_root_concurrent_halt(); crossover_status_t crossover_status = crossover(original_lp_, root_crossover_settings, root_crossover_soln_, exploration_stats_.start_time, root_crossover_soln_, crossover_vstatus_); - settings_.log.printf("Crossover status: %d\n", crossover_status); + + if (crossover_status != crossover_status_t::CONCURRENT_LIMIT) { + settings_.log.printf("Crossover status: %d\n", crossover_status); + } // Check if crossover was stopped by dual simplex if (crossover_status == crossover_status_t::OPTIMAL) { - settings_.log.printf("Dual simplex stopped by crossover, crossover found a basis\n"); - global_root_concurrent_halt = 1; // Stop dual simplex - root_status = root_status_future.get(); + set_global_root_concurrent_halt(1); // Stop dual simplex + root_status = root_status_future.get(); // Override the root relaxation solution with the crossover solution root_relax_soln_ = root_crossover_soln_; root_vstatus_ = crossover_vstatus_; root_status = lp_status_t::OPTIMAL; } else { - settings_.log.printf("Dual simplex finished\n"); root_status = root_status_future.get(); } } else { diff --git a/cpp/src/dual_simplex/branch_and_bound.hpp b/cpp/src/dual_simplex/branch_and_bound.hpp index f3dbddb014..8a3cb9577d 100644 --- a/cpp/src/dual_simplex/branch_and_bound.hpp +++ b/cpp/src/dual_simplex/branch_and_bound.hpp @@ -112,6 +112,8 @@ class branch_and_bound_t { f_t get_lower_bound(); i_t get_heap_size(); bool enable_concurrent_lp_root_solve() const { return enable_concurrent_lp_root_solve_; } + volatile int* get_global_root_concurrent_halt() { return &global_root_concurrent_halt_; } + void set_global_root_concurrent_halt(int value) { global_root_concurrent_halt_ = value; } // The main entry routine. Returns the solver status and populates solution with the incumbent. mip_status_t solve(mip_solution_t& solution); @@ -166,6 +168,7 @@ class branch_and_bound_t { std::vector edge_norms_; std::atomic root_crossover_solution_set_{false}; bool enable_concurrent_lp_root_solve_{false}; + volatile int global_root_concurrent_halt_{0}; // Pseudocosts pseudo_costs_t pc_; From b960154b8ae39a61030c5384836283e673b3a758 Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Mon, 8 Dec 2025 14:08:04 -0800 Subject: [PATCH 49/51] Address review comments --- cpp/src/dual_simplex/branch_and_bound.cpp | 8 ++++---- cpp/src/dual_simplex/branch_and_bound.hpp | 6 +++--- cpp/src/dual_simplex/phase2.cpp | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cpp/src/dual_simplex/branch_and_bound.cpp b/cpp/src/dual_simplex/branch_and_bound.cpp index 5d44273a2e..a8fc8559d8 100644 --- a/cpp/src/dual_simplex/branch_and_bound.cpp +++ b/cpp/src/dual_simplex/branch_and_bound.cpp @@ -1239,7 +1239,7 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut simplex_solver_settings_t lp_settings = settings_; lp_status_t root_status; lp_settings.inside_mip = 1; - lp_settings.concurrent_halt = get_global_root_concurrent_halt(); + lp_settings.concurrent_halt = get_root_concurrent_halt(); // RINS/SUBMIP path if (!enable_concurrent_lp_root_solve()) { root_status = solve_linear_program_advanced(original_lp_, @@ -1263,7 +1263,7 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut // Wait for the root relaxation solution to be sent by the diversity manager or dual simplex // to finish while (!root_crossover_solution_set_.load(std::memory_order_acquire) && - *get_global_root_concurrent_halt() == 0) { + *get_root_concurrent_halt() == 0) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); continue; } @@ -1290,7 +1290,7 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut // Call crossover on the crushed solution auto root_crossover_settings = settings_; - root_crossover_settings.concurrent_halt = get_global_root_concurrent_halt(); + root_crossover_settings.concurrent_halt = get_root_concurrent_halt(); crossover_status_t crossover_status = crossover(original_lp_, root_crossover_settings, root_crossover_soln_, @@ -1304,7 +1304,7 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut // Check if crossover was stopped by dual simplex if (crossover_status == crossover_status_t::OPTIMAL) { - set_global_root_concurrent_halt(1); // Stop dual simplex + set_root_concurrent_halt(1); // Stop dual simplex root_status = root_status_future.get(); // Override the root relaxation solution with the crossover solution root_relax_soln_ = root_crossover_soln_; diff --git a/cpp/src/dual_simplex/branch_and_bound.hpp b/cpp/src/dual_simplex/branch_and_bound.hpp index 8a3cb9577d..7547662232 100644 --- a/cpp/src/dual_simplex/branch_and_bound.hpp +++ b/cpp/src/dual_simplex/branch_and_bound.hpp @@ -112,8 +112,8 @@ class branch_and_bound_t { f_t get_lower_bound(); i_t get_heap_size(); bool enable_concurrent_lp_root_solve() const { return enable_concurrent_lp_root_solve_; } - volatile int* get_global_root_concurrent_halt() { return &global_root_concurrent_halt_; } - void set_global_root_concurrent_halt(int value) { global_root_concurrent_halt_ = value; } + volatile int* get_root_concurrent_halt() { return &root_concurrent_halt_; } + void set_root_concurrent_halt(int value) { root_concurrent_halt_ = value; } // The main entry routine. Returns the solver status and populates solution with the incumbent. mip_status_t solve(mip_solution_t& solution); @@ -168,7 +168,7 @@ class branch_and_bound_t { std::vector edge_norms_; std::atomic root_crossover_solution_set_{false}; bool enable_concurrent_lp_root_solve_{false}; - volatile int global_root_concurrent_halt_{0}; + volatile int root_concurrent_halt_{0}; // Pseudocosts pseudo_costs_t pc_; diff --git a/cpp/src/dual_simplex/phase2.cpp b/cpp/src/dual_simplex/phase2.cpp index 125be9dd53..56298ef4dd 100644 --- a/cpp/src/dual_simplex/phase2.cpp +++ b/cpp/src/dual_simplex/phase2.cpp @@ -2982,7 +2982,7 @@ dual::status_t dual_phase2_with_advanced_basis(i_t phase, ft.print_stats(); } if (settings.inside_mip && settings.concurrent_halt != nullptr) { - settings.log.printf("Setting concurrent halt in Dual Simplex Phase 2\n"); + settings.log.debug("Setting concurrent halt in Dual Simplex Phase 2\n"); *settings.concurrent_halt = 1; } } From a8125c7aea95b7309fa07061dbb65479161189c8 Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Mon, 8 Dec 2025 17:22:47 -0800 Subject: [PATCH 50/51] Disable crossover logs --- cpp/src/dual_simplex/branch_and_bound.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/cpp/src/dual_simplex/branch_and_bound.cpp b/cpp/src/dual_simplex/branch_and_bound.cpp index a8fc8559d8..9e549741f5 100644 --- a/cpp/src/dual_simplex/branch_and_bound.cpp +++ b/cpp/src/dual_simplex/branch_and_bound.cpp @@ -1290,6 +1290,7 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut // Call crossover on the crushed solution auto root_crossover_settings = settings_; + root_crossover_settings.log.log = false; root_crossover_settings.concurrent_halt = get_root_concurrent_halt(); crossover_status_t crossover_status = crossover(original_lp_, root_crossover_settings, From cc0b3b8e584315c19f4808c16e2c7f185c551d3a Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Mon, 8 Dec 2025 17:35:45 -0800 Subject: [PATCH 51/51] Encapsulate in a function --- cpp/src/dual_simplex/branch_and_bound.cpp | 151 +++++++++++---------- cpp/src/dual_simplex/branch_and_bound.hpp | 2 + cpp/src/mip/diversity/diversity_manager.cu | 2 +- 3 files changed, 83 insertions(+), 72 deletions(-) diff --git a/cpp/src/dual_simplex/branch_and_bound.cpp b/cpp/src/dual_simplex/branch_and_bound.cpp index 9e549741f5..77acca8f7d 100644 --- a/cpp/src/dual_simplex/branch_and_bound.cpp +++ b/cpp/src/dual_simplex/branch_and_bound.cpp @@ -17,7 +17,6 @@ #include #include #include -#include #include #include @@ -1206,6 +1205,81 @@ void branch_and_bound_t::diving_thread(const csr_matrix_t& A } } +template +lp_status_t branch_and_bound_t::solve_root_relaxation( + simplex_solver_settings_t const& lp_settings) +{ + // Root node path + lp_status_t root_status; + std::future root_status_future; + root_status_future = std::async(std::launch::async, + &solve_linear_program_advanced, + std::ref(original_lp_), + exploration_stats_.start_time, + std::ref(lp_settings), + std::ref(root_relax_soln_), + std::ref(root_vstatus_), + std::ref(edge_norms_)); + // Wait for the root relaxation solution to be sent by the diversity manager or dual simplex + // to finish + while (!root_crossover_solution_set_.load(std::memory_order_acquire) && + *get_root_concurrent_halt() == 0) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + continue; + } + + if (root_crossover_solution_set_.load(std::memory_order_acquire)) { + // Crush the root relaxation solution on converted user problem + std::vector crushed_root_x; + crush_primal_solution( + original_problem_, original_lp_, root_crossover_soln_.x, new_slacks_, crushed_root_x); + std::vector crushed_root_y; + std::vector crushed_root_z; + + f_t dual_res_inf = crush_dual_solution(original_problem_, + original_lp_, + new_slacks_, + root_crossover_soln_.y, + root_crossover_soln_.z, + crushed_root_y, + crushed_root_z); + + root_crossover_soln_.x = crushed_root_x; + root_crossover_soln_.y = crushed_root_y; + root_crossover_soln_.z = crushed_root_z; + + // Call crossover on the crushed solution + auto root_crossover_settings = settings_; + root_crossover_settings.log.log = false; + root_crossover_settings.concurrent_halt = get_root_concurrent_halt(); + crossover_status_t crossover_status = crossover(original_lp_, + root_crossover_settings, + root_crossover_soln_, + exploration_stats_.start_time, + root_crossover_soln_, + crossover_vstatus_); + + if (crossover_status == crossover_status_t::OPTIMAL) { + settings_.log.printf("Crossover status: %d\n", crossover_status); + } + + // Check if crossover was stopped by dual simplex + if (crossover_status == crossover_status_t::OPTIMAL) { + set_root_concurrent_halt(1); // Stop dual simplex + root_status = root_status_future.get(); + // Override the root relaxation solution with the crossover solution + root_relax_soln_ = root_crossover_soln_; + root_vstatus_ = crossover_vstatus_; + root_status = lp_status_t::OPTIMAL; + } else { + root_status = root_status_future.get(); + } + } else { + root_status = root_status_future.get(); + } + return root_status; +} + template mip_status_t branch_and_bound_t::solve(mip_solution_t& solution) { @@ -1236,10 +1310,11 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut root_relax_soln_.resize(original_lp_.num_rows, original_lp_.num_cols); settings_.log.printf("Solving LP root relaxation\n"); - simplex_solver_settings_t lp_settings = settings_; + lp_status_t root_status; - lp_settings.inside_mip = 1; - lp_settings.concurrent_halt = get_root_concurrent_halt(); + simplex_solver_settings_t lp_settings = settings_; + lp_settings.inside_mip = 1; + lp_settings.concurrent_halt = get_root_concurrent_halt(); // RINS/SUBMIP path if (!enable_concurrent_lp_root_solve()) { root_status = solve_linear_program_advanced(original_lp_, @@ -1250,73 +1325,7 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut edge_norms_); } else { - // Root node path - std::future root_status_future; - root_status_future = std::async(std::launch::async, - &solve_linear_program_advanced, - std::ref(original_lp_), - exploration_stats_.start_time, - std::ref(lp_settings), - std::ref(root_relax_soln_), - std::ref(root_vstatus_), - std::ref(edge_norms_)); - // Wait for the root relaxation solution to be sent by the diversity manager or dual simplex - // to finish - while (!root_crossover_solution_set_.load(std::memory_order_acquire) && - *get_root_concurrent_halt() == 0) { - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - continue; - } - - if (root_crossover_solution_set_.load(std::memory_order_acquire)) { - // Crush the root relaxation solution on converted user problem - std::vector crushed_root_x; - crush_primal_solution( - original_problem_, original_lp_, root_crossover_soln_.x, new_slacks_, crushed_root_x); - std::vector crushed_root_y; - std::vector crushed_root_z; - - f_t dual_res_inf = crush_dual_solution(original_problem_, - original_lp_, - new_slacks_, - root_crossover_soln_.y, - root_crossover_soln_.z, - crushed_root_y, - crushed_root_z); - - root_crossover_soln_.x = crushed_root_x; - root_crossover_soln_.y = crushed_root_y; - root_crossover_soln_.z = crushed_root_z; - - // Call crossover on the crushed solution - auto root_crossover_settings = settings_; - root_crossover_settings.log.log = false; - root_crossover_settings.concurrent_halt = get_root_concurrent_halt(); - crossover_status_t crossover_status = crossover(original_lp_, - root_crossover_settings, - root_crossover_soln_, - exploration_stats_.start_time, - root_crossover_soln_, - crossover_vstatus_); - - if (crossover_status != crossover_status_t::CONCURRENT_LIMIT) { - settings_.log.printf("Crossover status: %d\n", crossover_status); - } - - // Check if crossover was stopped by dual simplex - if (crossover_status == crossover_status_t::OPTIMAL) { - set_root_concurrent_halt(1); // Stop dual simplex - root_status = root_status_future.get(); - // Override the root relaxation solution with the crossover solution - root_relax_soln_ = root_crossover_soln_; - root_vstatus_ = crossover_vstatus_; - root_status = lp_status_t::OPTIMAL; - } else { - root_status = root_status_future.get(); - } - } else { - root_status = root_status_future.get(); - } + root_status = solve_root_relaxation(lp_settings); } exploration_stats_.total_lp_iters = root_relax_soln_.iterations; diff --git a/cpp/src/dual_simplex/branch_and_bound.hpp b/cpp/src/dual_simplex/branch_and_bound.hpp index 7547662232..7891711f75 100644 --- a/cpp/src/dual_simplex/branch_and_bound.hpp +++ b/cpp/src/dual_simplex/branch_and_bound.hpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -114,6 +115,7 @@ class branch_and_bound_t { bool enable_concurrent_lp_root_solve() const { return enable_concurrent_lp_root_solve_; } volatile int* get_root_concurrent_halt() { return &root_concurrent_halt_; } void set_root_concurrent_halt(int value) { root_concurrent_halt_ = value; } + lp_status_t solve_root_relaxation(simplex_solver_settings_t const& lp_settings); // The main entry routine. Returns the solver status and populates solution with the incumbent. mip_status_t solve(mip_solution_t& solution); diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index 209f48bddb..686951ebba 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -788,7 +788,7 @@ void diversity_manager_t::set_simplex_solution(const std::vector& std::lock_guard lock(relaxed_solution_mutex); simplex_solution_exists.store(true, std::memory_order_release); global_concurrent_halt = 1; - CUOPT_LOG_INFO("Setting concurrent halt for PDLP inside diversity manager"); + CUOPT_LOG_DEBUG("Setting concurrent halt for PDLP inside diversity manager"); // global_concurrent_halt.store(1, std::memory_order_release); // it is safe to use lp_optimal_solution while executing the copy operation // the operations are ordered as long as they are on the same stream