From b758b9f3424e6599987b93a140e2513c9ea76a9b Mon Sep 17 00:00:00 2001 From: akifcorduk Date: Wed, 11 Feb 2026 04:12:07 -0800 Subject: [PATCH 01/15] fix timer issues --- cpp/src/dual_simplex/branch_and_bound.cpp | 90 +++++++---- cpp/src/dual_simplex/cuts.cpp | 142 ++++++++++++------ cpp/src/dual_simplex/cuts.hpp | 49 +++--- cpp/src/dual_simplex/pseudo_costs.cpp | 15 +- cpp/src/mip/solver.cu | 2 + .../unit_tests/presolve_test.cu | 2 - 6 files changed, 204 insertions(+), 96 deletions(-) diff --git a/cpp/src/dual_simplex/branch_and_bound.cpp b/cpp/src/dual_simplex/branch_and_bound.cpp index e74f69d13..176ff719d 100644 --- a/cpp/src/dual_simplex/branch_and_bound.cpp +++ b/cpp/src/dual_simplex/branch_and_bound.cpp @@ -1989,12 +1989,9 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut 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(); - // } + if (settings_.heuristic_preemption_callback != nullptr) { + settings_.heuristic_preemption_callback(); + } return mip_status_t::INFEASIBLE; } if (root_status == lp_status_t::UNBOUNDED) { @@ -2074,9 +2071,19 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut f_t last_upper_bound = std::numeric_limits::infinity(); f_t last_objective = root_objective_; f_t root_relax_objective = root_objective_; + auto stop_for_time_limit = [&]() -> bool { + const f_t elapsed = toc(exploration_stats_.start_time); + if (elapsed > settings_.time_limit) { + solver_status_ = mip_status_t::TIME_LIMIT; + set_final_solution(solution, root_objective_); + return true; + } + return false; + }; i_t cut_pool_size = 0; for (i_t cut_pass = 0; cut_pass < settings_.max_cut_passes; cut_pass++) { + if (stop_for_time_limit()) { return solver_status_; } if (num_fractional == 0) { set_solution_at_root(solution, cut_info); return mip_status_t::OPTIMAL; @@ -2094,6 +2101,7 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut #endif // Generate cuts and add them to the cut pool + if (stop_for_time_limit()) { return solver_status_; } f_t cut_start_time = tic(); cut_generation.generate_cuts(original_lp_, settings_, @@ -2103,21 +2111,27 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut basis_update, root_relax_soln_.x, basic_list, - nonbasic_list); + nonbasic_list, + exploration_stats_.start_time); + if (stop_for_time_limit()) { return solver_status_; } f_t cut_generation_time = toc(cut_start_time); if (cut_generation_time > 1.0) { settings_.log.debug("Cut generation time %.2f seconds\n", cut_generation_time); } // Score the cuts + if (stop_for_time_limit()) { return solver_status_; } f_t score_start_time = tic(); - cut_pool.score_cuts(root_relax_soln_.x); + cut_pool.score_cuts(root_relax_soln_.x, exploration_stats_.start_time); + if (stop_for_time_limit()) { return solver_status_; } f_t score_time = toc(score_start_time); if (score_time > 1.0) { settings_.log.debug("Cut scoring time %.2f seconds\n", score_time); } // Get the best cuts from the cut pool csr_matrix_t cuts_to_add(0, original_lp_.num_cols, 0); std::vector cut_rhs; std::vector cut_types; - i_t num_cuts = cut_pool.get_best_cuts(cuts_to_add, cut_rhs, cut_types); + i_t num_cuts = + cut_pool.get_best_cuts(cuts_to_add, cut_rhs, cut_types, exploration_stats_.start_time); + if (stop_for_time_limit()) { return solver_status_; } if (num_cuts == 0) { break; } cut_info.record_cut_types(cut_types); #ifdef PRINT_CUT_POOL_TYPES @@ -2150,6 +2164,7 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut cuts_to_add.m + original_lp_.num_rows); lp_settings.log.log = false; + if (stop_for_time_limit()) { return solver_status_; } f_t add_cuts_start_time = tic(); mutex_original_lp_.lock(); i_t add_cuts_status = add_cuts(settings_, @@ -2162,14 +2177,20 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut basic_list, nonbasic_list, root_vstatus_, - edge_norms_); + edge_norms_, + exploration_stats_.start_time); var_types_.resize(original_lp_.num_cols, variable_type_t::CONTINUOUS); mutex_original_lp_.unlock(); + if (stop_for_time_limit()) { return solver_status_; } f_t add_cuts_time = toc(add_cuts_start_time); if (add_cuts_time > 1.0) { settings_.log.debug("Add cuts time %.2f seconds\n", add_cuts_time); } - if (add_cuts_status != 0) { + if (add_cuts_status == -2) { + solver_status_ = mip_status_t::TIME_LIMIT; + set_final_solution(solution, root_objective_); + return solver_status_; + } else if (add_cuts_status != 0) { settings_.log.printf("Failed to add cuts\n"); return mip_status_t::NUMERICAL; } @@ -2196,6 +2217,7 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut #endif original_lp_.A.to_compressed_row(Arow_); + if (stop_for_time_limit()) { return solver_status_; } f_t node_presolve_start_time = tic(); bounds_strengthening_t node_presolve(original_lp_, Arow_, row_sense, var_types_); std::vector new_lower = original_lp_.lower; @@ -2206,6 +2228,7 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut original_lp_.lower = new_lower; original_lp_.upper = new_upper; mutex_original_lp_.unlock(); + if (stop_for_time_limit()) { return solver_status_; } f_t node_presolve_time = toc(node_presolve_start_time); if (node_presolve_time > 1.0) { settings_.log.debug("Node presolve time %.2f seconds\n", node_presolve_time); @@ -2218,8 +2241,9 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut i_t iter = 0; bool initialize_basis = false; lp_settings.concurrent_halt = NULL; - f_t dual_phase2_start_time = tic(); - dual::status_t cut_status = dual_phase2_with_advanced_basis(2, + if (stop_for_time_limit()) { return solver_status_; } + f_t dual_phase2_start_time = tic(); + dual::status_t cut_status = dual_phase2_with_advanced_basis(2, 0, initialize_basis, exploration_stats_.start_time, @@ -2238,6 +2262,7 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut if (dual_phase2_time > 1.0) { settings_.log.debug("Dual phase2 time %.2f seconds\n", dual_phase2_time); } + if (stop_for_time_limit()) { return solver_status_; } if (cut_status == dual::status_t::TIME_LIMIT) { solver_status_ = mip_status_t::TIME_LIMIT; set_final_solution(solution, root_objective_); @@ -2245,6 +2270,7 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut } if (cut_status != dual::status_t::OPTIMAL) { + if (stop_for_time_limit()) { return solver_status_; } settings_.log.printf("Numerical issue at root node. Resolving from scratch\n"); lp_status_t scratch_status = solve_linear_program_with_advanced_basis(original_lp_, @@ -2256,6 +2282,11 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut nonbasic_list, root_vstatus_, edge_norms_); + if (stop_for_time_limit() || scratch_status == lp_status_t::TIME_LIMIT) { + solver_status_ = mip_status_t::TIME_LIMIT; + set_final_solution(solution, root_objective_); + return solver_status_; + } if (scratch_status == lp_status_t::OPTIMAL) { // We recovered cut_status = convert_lp_status_to_dual_status(scratch_status); @@ -2267,23 +2298,26 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut } } + if (stop_for_time_limit()) { return solver_status_; } f_t remove_cuts_start_time = tic(); mutex_original_lp_.lock(); - remove_cuts(original_lp_, - settings_, - Arow_, - new_slacks_, - original_rows, - var_types_, - root_vstatus_, - edge_norms_, - root_relax_soln_.x, - root_relax_soln_.y, - root_relax_soln_.z, - basic_list, - nonbasic_list, - basis_update); + i_t remove_cuts_status = remove_cuts(original_lp_, + settings_, + Arow_, + new_slacks_, + original_rows, + var_types_, + root_vstatus_, + edge_norms_, + root_relax_soln_.x, + root_relax_soln_.y, + root_relax_soln_.z, + basic_list, + nonbasic_list, + basis_update, + exploration_stats_.start_time); mutex_original_lp_.unlock(); + if (stop_for_time_limit()) { return solver_status_; } f_t remove_cuts_time = toc(remove_cuts_start_time); if (remove_cuts_time > 1.0) { settings_.log.debug("Remove cuts time %.2f seconds\n", remove_cuts_time); @@ -2335,7 +2369,7 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut set_uninitialized_steepest_edge_norms(original_lp_, basic_list, edge_norms_); pc_.resize(original_lp_.num_cols); - { + if (toc(exploration_stats_.start_time) < settings_.time_limit) { raft::common::nvtx::range scope_sb("BB::strong_branching"); strong_branching(original_problem_, original_lp_, diff --git a/cpp/src/dual_simplex/cuts.cpp b/cpp/src/dual_simplex/cuts.cpp index 0bb4e1744..ced10be89 100644 --- a/cpp/src/dual_simplex/cuts.cpp +++ b/cpp/src/dual_simplex/cuts.cpp @@ -95,7 +95,7 @@ f_t cut_pool_t::cut_orthogonality(i_t i, i_t j) } template -void cut_pool_t::score_cuts(std::vector& x_relax) +void cut_pool_t::score_cuts(std::vector& x_relax, f_t start_time) { const f_t min_cut_distance = 1e-4; cut_distances_.resize(cut_storage_.m, 0.0); @@ -103,6 +103,11 @@ void cut_pool_t::score_cuts(std::vector& x_relax) const bool verbose = false; for (i_t i = 0; i < cut_storage_.m; i++) { + if (toc(start_time) >= settings_.time_limit) { + best_cuts_.clear(); + scored_cuts_ = 0; + return; + } f_t violation; f_t cut_dist = cut_distance(i, x_relax, violation, cut_norms_[i]); cut_distances_[i] = cut_dist <= min_cut_distance ? 0.0 : cut_dist; @@ -133,6 +138,7 @@ void cut_pool_t::score_cuts(std::vector& x_relax) } while (scored_cuts_ < max_cuts && !sorted_indices.empty()) { + if (toc(start_time) >= settings_.time_limit) { return; } const i_t i = sorted_indices.back(); sorted_indices.pop_back(); @@ -141,6 +147,7 @@ void cut_pool_t::score_cuts(std::vector& x_relax) f_t cut_ortho = 1.0; const i_t best_cuts_size = best_cuts_.size(); for (i_t k = 0; k < best_cuts_size; k++) { + if (toc(start_time) >= settings_.time_limit) { return; } const i_t j = best_cuts_[k]; cut_ortho = std::min(cut_ortho, cut_orthogonality(i, j)); } @@ -154,7 +161,8 @@ void cut_pool_t::score_cuts(std::vector& x_relax) template i_t cut_pool_t::get_best_cuts(csr_matrix_t& best_cuts, std::vector& best_rhs, - std::vector& best_cut_types) + std::vector& best_cut_types, + f_t start_time) { best_cuts.m = 0; best_cuts.n = original_vars_; @@ -168,7 +176,9 @@ i_t cut_pool_t::get_best_cuts(csr_matrix_t& best_cuts, best_cut_types.clear(); best_cut_types.reserve(scored_cuts_); - for (i_t i : best_cuts_) { + for (i_t k = 0; k < static_cast(best_cuts_.size()); ++k) { + if (toc(start_time) >= settings_.time_limit) { break; } + const i_t i = best_cuts_[k]; sparse_vector_t cut(cut_storage_, i); cut.negate(); best_cuts.append_row(cut); @@ -559,33 +569,46 @@ void cut_generation_t::generate_cuts(const lp_problem_t& lp, basis_update_mpf_t& basis_update, const std::vector& xstar, const std::vector& basic_list, - const std::vector& nonbasic_list) + const std::vector& nonbasic_list, + f_t start_time) { + if (toc(start_time) >= settings.time_limit) { return; } + // Generate Gomory and CG Cuts if (settings.mixed_integer_gomory_cuts != 0 || settings.strong_chvatal_gomory_cuts != 0) { f_t cut_start_time = tic(); - generate_gomory_cuts( - lp, settings, Arow, new_slacks, var_types, basis_update, xstar, basic_list, nonbasic_list); + generate_gomory_cuts(lp, + settings, + Arow, + new_slacks, + var_types, + basis_update, + xstar, + basic_list, + nonbasic_list, + start_time); f_t cut_generation_time = toc(cut_start_time); if (cut_generation_time > 1.0) { settings.log.debug("Gomory and CG cut generation time %.2f seconds\n", cut_generation_time); } } + if (toc(start_time) >= settings.time_limit) { return; } // Generate Knapsack cuts if (settings.knapsack_cuts != 0) { f_t cut_start_time = tic(); - generate_knapsack_cuts(lp, settings, Arow, new_slacks, var_types, xstar); + generate_knapsack_cuts(lp, settings, Arow, new_slacks, var_types, xstar, start_time); f_t cut_generation_time = toc(cut_start_time); if (cut_generation_time > 1.0) { settings.log.debug("Knapsack cut generation time %.2f seconds\n", cut_generation_time); } } + if (toc(start_time) >= settings.time_limit) { return; } // Generate MIR and CG cuts if (settings.mir_cuts != 0 || settings.strong_chvatal_gomory_cuts != 0) { f_t cut_start_time = tic(); - generate_mir_cuts(lp, settings, Arow, new_slacks, var_types, xstar); + generate_mir_cuts(lp, settings, Arow, new_slacks, var_types, xstar, start_time); f_t cut_generation_time = toc(cut_start_time); if (cut_generation_time > 1.0) { settings.log.debug("MIR and CG cut generation time %.2f seconds\n", cut_generation_time); @@ -600,10 +623,12 @@ void cut_generation_t::generate_knapsack_cuts( csr_matrix_t& Arow, const std::vector& new_slacks, const std::vector& var_types, - const std::vector& xstar) + const std::vector& xstar, + f_t start_time) { if (knapsack_generation_.num_knapsack_constraints() > 0) { for (i_t knapsack_row : knapsack_generation_.get_knapsack_constraints()) { + if (toc(start_time) >= settings.time_limit) { return; } sparse_vector_t cut(lp.num_cols, 0); f_t cut_rhs; i_t knapsack_status = knapsack_generation_.generate_knapsack_cuts( @@ -620,8 +645,10 @@ void cut_generation_t::generate_mir_cuts( csr_matrix_t& Arow, const std::vector& new_slacks, const std::vector& var_types, - const std::vector& xstar) + const std::vector& xstar, + f_t start_time) { + if (toc(start_time) >= settings.time_limit) { return; } f_t mir_start_time = tic(); mixed_integer_rounding_cut_t mir(lp, settings, new_slacks, xstar); strong_cg_cut_t cg(lp, var_types, xstar); @@ -639,6 +666,7 @@ void cut_generation_t::generate_mir_cuts( // Compute initial scores for all rows std::vector score(lp.num_rows, 0.0); for (i_t i = 0; i < lp.num_rows; i++) { + if (toc(start_time) >= settings.time_limit) { return; } const i_t row_start = Arow.row_start[i]; const i_t row_end = Arow.row_start[i + 1]; @@ -688,6 +716,7 @@ void cut_generation_t::generate_mir_cuts( const i_t max_cuts = std::min(lp.num_rows, 1000); f_t work_estimate = 0.0; for (i_t h = 0; h < max_cuts; h++) { + if (toc(start_time) >= settings.time_limit) { return; } // Get the row with the highest score const i_t i = sorted_indices.back(); sorted_indices.pop_back(); @@ -768,6 +797,7 @@ void cut_generation_t::generate_mir_cuts( work_estimate += lp.num_cols; while (!add_cut && num_aggregated < max_aggregated) { + if (toc(start_time) >= settings.time_limit) { return; } sparse_vector_t transformed_inequality; inequality.squeeze(transformed_inequality); f_t transformed_rhs = inequality_rhs; @@ -979,13 +1009,15 @@ void cut_generation_t::generate_gomory_cuts( basis_update_mpf_t& basis_update, const std::vector& xstar, const std::vector& basic_list, - const std::vector& nonbasic_list) + const std::vector& nonbasic_list, + f_t start_time) { tableau_equality_t tableau(lp, basis_update, nonbasic_list); mixed_integer_rounding_cut_t mir(lp, settings, new_slacks, xstar); strong_cg_cut_t cg(lp, var_types, xstar); for (i_t i = 0; i < lp.num_rows; i++) { + if (toc(start_time) >= settings.time_limit) { return; } sparse_vector_t inequality(lp.num_cols, 0); f_t inequality_rhs; const i_t j = basic_list[i]; @@ -2312,9 +2344,13 @@ i_t add_cuts(const simplex_solver_settings_t& settings, std::vector& basic_list, std::vector& nonbasic_list, std::vector& vstatus, - std::vector& edge_norms) + std::vector& edge_norms, + f_t start_time) { + constexpr i_t time_limit_status = -2; + if (toc(start_time) >= settings.time_limit) { return time_limit_status; } + // Given a set of cuts: C*x <= d that are currently violated // by the current solution x* (i.e. C*x* > d), this function // adds the cuts into the LP and solves again. @@ -2342,6 +2378,7 @@ i_t add_cuts(const simplex_solver_settings_t& settings, settings.log.debug("Original lp rows %d\n", lp.num_rows); settings.log.debug("Original lp cols %d\n", lp.num_cols); + if (toc(start_time) >= settings.time_limit) { return time_limit_status; } csr_matrix_t new_A_row(lp.num_rows, lp.num_cols, 1); lp.A.to_compressed_row(new_A_row); @@ -2351,6 +2388,7 @@ i_t add_cuts(const simplex_solver_settings_t& settings, assert(append_status == 0); } + if (toc(start_time) >= settings.time_limit) { return time_limit_status; } csc_matrix_t new_A_col(lp.num_rows + p, lp.num_cols, 1); new_A_row.to_compressed_col(new_A_col); @@ -2405,6 +2443,7 @@ i_t add_cuts(const simplex_solver_settings_t& settings, settings.log.debug("Done adding rhs\n"); // Construct C_B = C(:, basic_list) + if (toc(start_time) >= settings.time_limit) { return time_limit_status; } std::vector C_col_degree(lp.num_cols, 0); i_t cuts_nz = cuts.row_start[p]; for (i_t q = 0; q < cuts_nz; q++) { @@ -2434,6 +2473,7 @@ i_t add_cuts(const simplex_solver_settings_t& settings, } settings.log.debug("Done estimating C_B_nz\n"); + if (toc(start_time) >= settings.time_limit) { return time_limit_status; } csr_matrix_t C_B(p, num_basic, C_B_nz); nz = 0; for (i_t i = 0; i < p; i++) { @@ -2458,6 +2498,7 @@ i_t add_cuts(const simplex_solver_settings_t& settings, settings.log.debug("C_B rows %d cols %d nz %d\n", C_B.m, C_B.n, nz); // Adjust the basis update to include the new cuts + if (toc(start_time) >= settings.time_limit) { return time_limit_status; } basis_update.append_cuts(C_B); basic_list.resize(lp.num_rows, 0); @@ -2495,25 +2536,30 @@ i_t add_cuts(const simplex_solver_settings_t& settings, solution.y.resize(lp.num_rows, 0.0); solution.z.resize(lp.num_cols, 0.0); + if (toc(start_time) >= settings.time_limit) { return time_limit_status; } return 0; } template -void remove_cuts(lp_problem_t& lp, - const simplex_solver_settings_t& settings, - csr_matrix_t& Arow, - std::vector& new_slacks, - i_t original_rows, - std::vector& var_types, - std::vector& vstatus, - std::vector& edge_norms, - std::vector& x, - std::vector& y, - std::vector& z, - std::vector& basic_list, - std::vector& nonbasic_list, - basis_update_mpf_t& basis_update) +i_t remove_cuts(lp_problem_t& lp, + const simplex_solver_settings_t& settings, + csr_matrix_t& Arow, + std::vector& new_slacks, + i_t original_rows, + std::vector& var_types, + std::vector& vstatus, + std::vector& edge_norms, + std::vector& x, + std::vector& y, + std::vector& z, + std::vector& basic_list, + std::vector& nonbasic_list, + basis_update_mpf_t& basis_update, + f_t start_time) { + constexpr i_t time_limit_status = -2; + if (toc(start_time) >= settings.time_limit) { return time_limit_status; } + std::vector cuts_to_remove; cuts_to_remove.reserve(lp.num_rows - original_rows); std::vector slacks_to_remove; @@ -2522,6 +2568,7 @@ void remove_cuts(lp_problem_t& lp, std::vector is_slack(lp.num_cols, 0); for (i_t j : new_slacks) { + if (toc(start_time) >= settings.time_limit) { return time_limit_status; } is_slack[j] = 1; #ifdef CHECK_SLACKS // Check that slack column length is 1 @@ -2536,12 +2583,14 @@ void remove_cuts(lp_problem_t& lp, } for (i_t k = original_rows; k < lp.num_rows; k++) { + if (toc(start_time) >= settings.time_limit) { return time_limit_status; } if (std::abs(y[k]) < dual_tol) { const i_t row_start = Arow.row_start[k]; const i_t row_end = Arow.row_start[k + 1]; i_t last_slack = -1; const f_t slack_tol = 1e-3; for (i_t p = row_start; p < row_end; p++) { + if (toc(start_time) >= settings.time_limit) { return time_limit_status; } const i_t j = Arow.j[p]; if (is_slack[j]) { if (vstatus[j] == variable_status_t::BASIC && x[j] > slack_tol) { last_slack = j; } @@ -2555,6 +2604,7 @@ void remove_cuts(lp_problem_t& lp, } if (cuts_to_remove.size() > 0) { + if (toc(start_time) >= settings.time_limit) { return time_limit_status; } std::vector marked_rows(lp.num_rows, 0); for (i_t i : cuts_to_remove) { marked_rows[i] = 1; @@ -2568,6 +2618,7 @@ void remove_cuts(lp_problem_t& lp, std::vector new_solution_y(lp.num_rows - cuts_to_remove.size()); i_t h = 0; for (i_t i = 0; i < lp.num_rows; i++) { + if (toc(start_time) >= settings.time_limit) { return time_limit_status; } if (!marked_rows[i]) { new_rhs[h] = lp.rhs[i]; new_solution_y[h] = y[i]; @@ -2594,6 +2645,7 @@ void remove_cuts(lp_problem_t& lp, std::vector new_is_slacks(lp.num_cols - slacks_to_remove.size(), 0); h = 0; for (i_t k = 0; k < lp.num_cols; k++) { + if (toc(start_time) >= settings.time_limit) { return time_limit_status; } if (!marked_cols[k]) { new_objective[h] = lp.objective[k]; new_lower[h] = lp.lower[k]; @@ -2641,10 +2693,14 @@ void remove_cuts(lp_problem_t& lp, lp.num_cols, lp.A.col_start[lp.A.n]); + if (toc(start_time) >= settings.time_limit) { return time_limit_status; } basis_update.resize(lp.num_rows); basis_update.refactor_basis( lp.A, settings, lp.lower, lp.upper, basic_list, nonbasic_list, vstatus); } + + if (toc(start_time) >= settings.time_limit) { return time_limit_status; } + return 0; } template @@ -2789,22 +2845,24 @@ template int add_cuts(const simplex_solver_settings_t& settings, std::vector& basic_list, std::vector& nonbasic_list, std::vector& vstatus, - std::vector& edge_norms); - -template void remove_cuts(lp_problem_t& lp, - const simplex_solver_settings_t& settings, - csr_matrix_t& Arow, - std::vector& new_slacks, - int original_rows, - std::vector& var_types, - std::vector& vstatus, - std::vector& edge_norms, - std::vector& x, - std::vector& y, - std::vector& z, - std::vector& basic_list, - std::vector& nonbasic_list, - basis_update_mpf_t& basis_update); + std::vector& edge_norms, + double start_time); + +template int remove_cuts(lp_problem_t& lp, + const simplex_solver_settings_t& settings, + csr_matrix_t& Arow, + std::vector& new_slacks, + int original_rows, + std::vector& var_types, + std::vector& vstatus, + std::vector& edge_norms, + std::vector& x, + std::vector& y, + std::vector& z, + std::vector& basic_list, + std::vector& nonbasic_list, + basis_update_mpf_t& basis_update, + double start_time); template void read_saved_solution_for_cut_verification( const lp_problem_t& lp, diff --git a/cpp/src/dual_simplex/cuts.hpp b/cpp/src/dual_simplex/cuts.hpp index a4a36d75b..17f5e032a 100644 --- a/cpp/src/dual_simplex/cuts.hpp +++ b/cpp/src/dual_simplex/cuts.hpp @@ -138,12 +138,13 @@ class cut_pool_t { // cut'*xstart < rhs void add_cut(cut_type_t cut_type, const sparse_vector_t& cut, f_t rhs); - void score_cuts(std::vector& x_relax); + void score_cuts(std::vector& x_relax, f_t start_time); // We return the cuts in the form best_cuts*x <= best_rhs i_t get_best_cuts(csr_matrix_t& best_cuts, std::vector& best_rhs, - std::vector& best_cut_types); + std::vector& best_cut_types, + f_t start_time); void age_cuts(); @@ -239,7 +240,8 @@ class cut_generation_t { basis_update_mpf_t& basis_update, const std::vector& xstar, const std::vector& basic_list, - const std::vector& nonbasic_list); + const std::vector& nonbasic_list, + f_t start_time); private: // Generate all mixed integer gomory cuts @@ -251,7 +253,8 @@ class cut_generation_t { basis_update_mpf_t& basis_update, const std::vector& xstar, const std::vector& basic_list, - const std::vector& nonbasic_list); + const std::vector& nonbasic_list, + f_t start_time); // Generate all mixed integer rounding cuts void generate_mir_cuts(const lp_problem_t& lp, @@ -259,7 +262,8 @@ class cut_generation_t { csr_matrix_t& Arow, const std::vector& new_slacks, const std::vector& var_types, - const std::vector& xstar); + const std::vector& xstar, + f_t start_time); // Generate all knapsack cuts void generate_knapsack_cuts(const lp_problem_t& lp, @@ -267,7 +271,8 @@ class cut_generation_t { csr_matrix_t& Arow, const std::vector& new_slacks, const std::vector& var_types, - const std::vector& xstar); + const std::vector& xstar, + f_t start_time); cut_pool_t& cut_pool_; knapsack_generation_t knapsack_generation_; @@ -458,22 +463,24 @@ i_t add_cuts(const simplex_solver_settings_t& settings, std::vector& basic_list, std::vector& nonbasic_list, std::vector& vstatus, - std::vector& edge_norms); + std::vector& edge_norms, + f_t start_time); template -void remove_cuts(lp_problem_t& lp, - const simplex_solver_settings_t& settings, - csr_matrix_t& Arow, - std::vector& new_slacks, - i_t original_rows, - std::vector& var_types, - std::vector& vstatus, - std::vector& edge_norms, - std::vector& x, - std::vector& y, - std::vector& z, - std::vector& basic_list, - std::vector& nonbasic_list, - basis_update_mpf_t& basis_update); +i_t remove_cuts(lp_problem_t& lp, + const simplex_solver_settings_t& settings, + csr_matrix_t& Arow, + std::vector& new_slacks, + i_t original_rows, + std::vector& var_types, + std::vector& vstatus, + std::vector& edge_norms, + std::vector& x, + std::vector& y, + std::vector& z, + std::vector& basic_list, + std::vector& nonbasic_list, + basis_update_mpf_t& basis_update, + f_t start_time); } // namespace cuopt::linear_programming::dual_simplex diff --git a/cpp/src/dual_simplex/pseudo_costs.cpp b/cpp/src/dual_simplex/pseudo_costs.cpp index de8994e80..40949391e 100644 --- a/cpp/src/dual_simplex/pseudo_costs.cpp +++ b/cpp/src/dual_simplex/pseudo_costs.cpp @@ -313,6 +313,9 @@ void strong_branching(const user_problem_t& original_problem, pc.strong_branch_up.assign(fractional.size(), 0); pc.num_strong_branches_completed = 0; + const f_t elapsed_time = toc(start_time); + if (elapsed_time > settings.time_limit) { return; } + if (settings.mip_batch_pdlp_strong_branching) { settings.log.printf("Batch PDLP strong branching enabled\n"); @@ -333,9 +336,15 @@ void strong_branching(const user_problem_t& original_problem, fraction_values.push_back(original_root_soln_x[j]); } - const auto mps_model = simplex_problem_to_mps_data_model(original_problem); - const auto solutions = - batch_pdlp_solve(original_problem.handle_ptr, mps_model, fractional, fraction_values); + const auto mps_model = simplex_problem_to_mps_data_model(original_problem); + const f_t batch_elapsed_time = toc(start_time); + const f_t batch_remaining_time = + std::max(static_cast(0.0), settings.time_limit - batch_elapsed_time); + if (batch_remaining_time <= 0.0) { return; } + pdlp_solver_settings_t pdlp_settings; + pdlp_settings.time_limit = batch_remaining_time; + const auto solutions = batch_pdlp_solve( + original_problem.handle_ptr, mps_model, fractional, fraction_values, pdlp_settings); f_t batch_pdlp_strong_branching_time = toc(start_batch); // Find max iteration on how many are done accross the batch diff --git a/cpp/src/mip/solver.cu b/cpp/src/mip/solver.cu index 21fa93bfa..201cf3b62 100644 --- a/cpp/src/mip/solver.cu +++ b/cpp/src/mip/solver.cu @@ -310,9 +310,11 @@ solution_t mip_solver_t::run_solver() if (!is_feasible.value(sol.handle_ptr->get_stream())) { CUOPT_LOG_ERROR( "Solution is not feasible due to variable bounds, returning infeasible solution!"); + context.stats.total_solve_time = timer_.elapsed_time(); context.problem_ptr->post_process_solution(sol); return sol; } + context.stats.total_solve_time = timer_.elapsed_time(); context.problem_ptr->post_process_solution(sol); dm.rins.stop_rins(); return sol; diff --git a/cpp/tests/linear_programming/unit_tests/presolve_test.cu b/cpp/tests/linear_programming/unit_tests/presolve_test.cu index 3fd05d97f..77a26cff7 100644 --- a/cpp/tests/linear_programming/unit_tests/presolve_test.cu +++ b/cpp/tests/linear_programming/unit_tests/presolve_test.cu @@ -166,7 +166,6 @@ TEST(pslp_presolve, postsolve_accuracy_afiro) TEST(pslp_presolve, postsolve_dual_accuracy_afiro) { const raft::handle_t handle_{}; - constexpr double tolerance = 1e-5; auto path = make_path_absolute("linear_programming/afiro_original.mps"); auto mps_data_model = cuopt::mps_parser::parse_mps(path, true); @@ -351,7 +350,6 @@ TEST(pslp_presolve, postsolve_reduced_costs) TEST(pslp_presolve, postsolve_multiple_problems) { const raft::handle_t handle_{}; - constexpr double tolerance = 1e-4; std::vector> instances{ {"afiro_original", -464.75314}, From 0392a6247a4402e0fca3ce3c82e97bff9f6456ae Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Wed, 11 Feb 2026 15:30:43 +0000 Subject: [PATCH 02/15] disable jobserver flag when not actually using jobserver --- build.sh | 10 +++++++++- cpp/CMakeLists.txt | 4 ++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/build.sh b/build.sh index b5c35f510..f790ae16d 100755 --- a/build.sh +++ b/build.sh @@ -360,6 +360,13 @@ fi ################################################################################ # Configure, build, and install libcuopt if buildAll || hasArg libcuopt; then + # Enable nvcc --jobserver only when building through the Makefile, + # which starts a GNU Make jobserver that nvcc can participate in. + if hasArg -n; then + USE_NVCC_JOBSERVER=ON + else + USE_NVCC_JOBSERVER=OFF + fi mkdir -p "${LIBCUOPT_BUILD_DIR}" cd "${LIBCUOPT_BUILD_DIR}" cmake -DDEFINE_ASSERT=${DEFINE_ASSERT} \ @@ -381,12 +388,13 @@ if buildAll || hasArg libcuopt; then -DWRITE_FATBIN=${WRITE_FATBIN} \ -DHOST_LINEINFO=${HOST_LINEINFO} \ -DPARALLEL_LEVEL="${PARALLEL_LEVEL}" \ + -DUSE_NVCC_JOBSERVER="${USE_NVCC_JOBSERVER}" \ -DINSTALL_TARGET="${INSTALL_TARGET}" \ "${CACHE_ARGS[@]}" \ "${EXTRA_CMAKE_ARGS[@]}" \ "${REPODIR}"/cpp JFLAG="${PARALLEL_LEVEL:+-j${PARALLEL_LEVEL}}" - if hasArg -n; then + if [ "${USE_NVCC_JOBSERVER}" = "ON" ]; then # Manual make invocation to start its jobserver make ${JFLAG} -C "${REPODIR}/cpp" LIBCUOPT_BUILD_DIR="${LIBCUOPT_BUILD_DIR}" VERBOSE_FLAG="${VERBOSE_FLAG}" PARALLEL_LEVEL="${PARALLEL_LEVEL}" ninja-build else diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index af7b7c1c3..aa377b315 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -153,11 +153,11 @@ if(CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL 12.9 AND CMAKE_CUDA_COMPILE endif() list(APPEND CUOPT_CUDA_FLAGS -fopenmp) -# Add jobserver flags for parallel compilation if PARALLEL_LEVEL is set +# Add parallel compilation flags if PARALLEL_LEVEL is set if(PARALLEL_LEVEL AND NOT "${PARALLEL_LEVEL}" STREQUAL "") message(STATUS "Enabling nvcc parallel compilation support") list(APPEND CUOPT_CUDA_FLAGS --threads=0 --split-compile=0) - if(CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL 13.0) + if(USE_NVCC_JOBSERVER AND CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL 13.0) message(STATUS "Enabling nvcc jobserver support (NVCC >= 13.0)") list(APPEND CUOPT_CUDA_FLAGS --jobserver) endif() From ee544770a1cfbf7642e527957ffdea40664c2da1 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Wed, 11 Feb 2026 15:58:10 +0000 Subject: [PATCH 03/15] disable jobserver unless explicitely requested --- build.sh | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/build.sh b/build.sh index f790ae16d..927acd664 100755 --- a/build.sh +++ b/build.sh @@ -362,11 +362,7 @@ fi if buildAll || hasArg libcuopt; then # Enable nvcc --jobserver only when building through the Makefile, # which starts a GNU Make jobserver that nvcc can participate in. - if hasArg -n; then - USE_NVCC_JOBSERVER=ON - else - USE_NVCC_JOBSERVER=OFF - fi + USE_NVCC_JOBSERVER=${USE_NVCC_JOBSERVER:-OFF} mkdir -p "${LIBCUOPT_BUILD_DIR}" cd "${LIBCUOPT_BUILD_DIR}" cmake -DDEFINE_ASSERT=${DEFINE_ASSERT} \ From f876fc09db2fe5b6c03612d8a85a863a470b2838 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Wed, 11 Feb 2026 16:23:22 +0000 Subject: [PATCH 04/15] better workaround fix build --- build.sh | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/build.sh b/build.sh index 927acd664..b5c35f510 100755 --- a/build.sh +++ b/build.sh @@ -360,9 +360,6 @@ fi ################################################################################ # Configure, build, and install libcuopt if buildAll || hasArg libcuopt; then - # Enable nvcc --jobserver only when building through the Makefile, - # which starts a GNU Make jobserver that nvcc can participate in. - USE_NVCC_JOBSERVER=${USE_NVCC_JOBSERVER:-OFF} mkdir -p "${LIBCUOPT_BUILD_DIR}" cd "${LIBCUOPT_BUILD_DIR}" cmake -DDEFINE_ASSERT=${DEFINE_ASSERT} \ @@ -384,13 +381,12 @@ if buildAll || hasArg libcuopt; then -DWRITE_FATBIN=${WRITE_FATBIN} \ -DHOST_LINEINFO=${HOST_LINEINFO} \ -DPARALLEL_LEVEL="${PARALLEL_LEVEL}" \ - -DUSE_NVCC_JOBSERVER="${USE_NVCC_JOBSERVER}" \ -DINSTALL_TARGET="${INSTALL_TARGET}" \ "${CACHE_ARGS[@]}" \ "${EXTRA_CMAKE_ARGS[@]}" \ "${REPODIR}"/cpp JFLAG="${PARALLEL_LEVEL:+-j${PARALLEL_LEVEL}}" - if [ "${USE_NVCC_JOBSERVER}" = "ON" ]; then + if hasArg -n; then # Manual make invocation to start its jobserver make ${JFLAG} -C "${REPODIR}/cpp" LIBCUOPT_BUILD_DIR="${LIBCUOPT_BUILD_DIR}" VERBOSE_FLAG="${VERBOSE_FLAG}" PARALLEL_LEVEL="${PARALLEL_LEVEL}" ninja-build else From 7de08d2dfd124bf5fba7faa3ea391c3cf08c85f8 Mon Sep 17 00:00:00 2001 From: akifcorduk Date: Tue, 17 Feb 2026 02:45:54 -0800 Subject: [PATCH 05/15] add timers to right_looking_lu and refactoring the basis --- cpp/src/branch_and_bound/branch_and_bound.cpp | 18 +++++- cpp/src/cuts/cuts.cpp | 6 +- cpp/src/dual_simplex/basis_solves.cpp | 20 ++++-- cpp/src/dual_simplex/basis_solves.hpp | 3 +- cpp/src/dual_simplex/basis_updates.cpp | 11 +++- cpp/src/dual_simplex/basis_updates.hpp | 3 +- cpp/src/dual_simplex/crossover.cpp | 61 +++++++++++-------- cpp/src/dual_simplex/phase2.cpp | 31 +++++++--- cpp/src/dual_simplex/presolve.cpp | 2 + cpp/src/dual_simplex/primal.cpp | 19 ++++-- cpp/src/dual_simplex/right_looking_lu.cpp | 20 +++--- cpp/src/dual_simplex/right_looking_lu.hpp | 3 +- cpp/src/dual_simplex/solve.cpp | 2 + cpp/src/dual_simplex/types.hpp | 2 + 14 files changed, 134 insertions(+), 67 deletions(-) diff --git a/cpp/src/branch_and_bound/branch_and_bound.cpp b/cpp/src/branch_and_bound/branch_and_bound.cpp index 842845b0d..bd06dd3a0 100644 --- a/cpp/src/branch_and_bound/branch_and_bound.cpp +++ b/cpp/src/branch_and_bound/branch_and_bound.cpp @@ -1888,8 +1888,13 @@ lp_status_t branch_and_bound_t::solve_root_relaxation( original_lp_.upper, basic_list, nonbasic_list, - crossover_vstatus_); - if (refactor_status != 0) { + crossover_vstatus_, + exploration_stats_.start_time); + if (refactor_status == TIME_LIMIT_RETURN) { + root_status = lp_status_t::TIME_LIMIT; + } else if (refactor_status == CONCURRENT_HALT_RETURN) { + root_status = lp_status_t::TIME_LIMIT; + } else if (refactor_status != 0) { settings_.log.printf("Failed to refactor basis. %d deficient columns.\n", refactor_status); assert(refactor_status == 0); root_status = lp_status_t::NUMERICAL_ISSUES; @@ -1901,6 +1906,15 @@ lp_status_t branch_and_bound_t::solve_root_relaxation( user_objective = root_crossover_soln_.user_objective; iter = root_crossover_soln_.iterations; solver_name = "Barrier/PDLP and Crossover"; + } else if (crossover_status == crossover_status_t::TIME_LIMIT || + toc(exploration_stats_.start_time) > settings_.time_limit) { + set_root_concurrent_halt(1); + root_status = root_status_future.get(); + set_root_concurrent_halt(0); + root_status = lp_status_t::TIME_LIMIT; + user_objective = root_relax_soln_.user_objective; + iter = root_relax_soln_.iterations; + solver_name = "Dual Simplex"; } else { root_status = root_status_future.get(); user_objective = root_relax_soln_.user_objective; diff --git a/cpp/src/cuts/cuts.cpp b/cpp/src/cuts/cuts.cpp index aae893b22..2d98badb8 100644 --- a/cpp/src/cuts/cuts.cpp +++ b/cpp/src/cuts/cuts.cpp @@ -2697,8 +2697,10 @@ i_t remove_cuts(lp_problem_t& lp, if (toc(start_time) >= settings.time_limit) { return time_limit_status; } basis_update.resize(lp.num_rows); - basis_update.refactor_basis( - lp.A, settings, lp.lower, lp.upper, basic_list, nonbasic_list, vstatus); + i_t refactor_status = basis_update.refactor_basis( + lp.A, settings, lp.lower, lp.upper, basic_list, nonbasic_list, vstatus, start_time); + if (refactor_status == CONCURRENT_HALT_RETURN) { return CONCURRENT_HALT_RETURN; } + if (refactor_status == TIME_LIMIT_RETURN) { return time_limit_status; } } if (toc(start_time) >= settings.time_limit) { return time_limit_status; } diff --git a/cpp/src/dual_simplex/basis_solves.cpp b/cpp/src/dual_simplex/basis_solves.cpp index 17f997f4a..9a8cfc143 100644 --- a/cpp/src/dual_simplex/basis_solves.cpp +++ b/cpp/src/dual_simplex/basis_solves.cpp @@ -165,7 +165,8 @@ i_t factorize_basis(const csc_matrix_t& A, std::vector& pinv, std::vector& q, std::vector& deficient, - std::vector& slacks_needed) + std::vector& slacks_needed, + f_t start_time) { raft::common::nvtx::range scope("LU::factorize_basis"); const i_t m = basic_list.size(); @@ -363,11 +364,12 @@ i_t factorize_basis(const csc_matrix_t& A, S_col_perm, SL, SU, - S_perm_inv); + S_perm_inv, + start_time); if (settings.concurrent_halt != nullptr && *settings.concurrent_halt == 1) { - settings.log.printf("Concurrent halt\n"); return CONCURRENT_HALT_RETURN; } + if (Srank < 0) { return Srank; } if (Srank != Sdim) { // Get the rank deficient columns deficient.clear(); @@ -568,7 +570,13 @@ i_t factorize_basis(const csc_matrix_t& A, } q.resize(m); f_t fact_start = tic(); - rank = right_looking_lu(A, settings, medium_tol, basic_list, q, L, U, pinv); + rank = right_looking_lu(A, settings, medium_tol, basic_list, q, L, U, pinv, start_time); + if (rank < 0) { + if (settings.concurrent_halt != nullptr && *settings.concurrent_halt == 1) { + return CONCURRENT_HALT_RETURN; + } + return rank; + } inverse_permutation(pinv, p); if (rank != m) { // Get the rank deficient columns @@ -584,7 +592,6 @@ i_t factorize_basis(const csc_matrix_t& A, } } if (settings.concurrent_halt != nullptr && *settings.concurrent_halt == 1) { - settings.log.printf("Concurrent halt\n"); return CONCURRENT_HALT_RETURN; } if (verbose) { @@ -874,7 +881,8 @@ template int factorize_basis(const csc_matrix_t& A, std::vector& pinv, std::vector& q, std::vector& deficient, - std::vector& slacks_needed); + std::vector& slacks_needed, + double start_time); template int basis_repair(const csc_matrix_t& A, const simplex_solver_settings_t& settings, diff --git a/cpp/src/dual_simplex/basis_solves.hpp b/cpp/src/dual_simplex/basis_solves.hpp index 59b4725e4..13227a832 100644 --- a/cpp/src/dual_simplex/basis_solves.hpp +++ b/cpp/src/dual_simplex/basis_solves.hpp @@ -36,7 +36,8 @@ i_t factorize_basis(const csc_matrix_t& A, std::vector& pinv, std::vector& q, std::vector& deficient, - std::vector& slacks_need); + std::vector& slacks_need, + f_t start_time); // Repair the basis by bringing in slacks template diff --git a/cpp/src/dual_simplex/basis_updates.cpp b/cpp/src/dual_simplex/basis_updates.cpp index 71dce2e39..5f8ef4934 100644 --- a/cpp/src/dual_simplex/basis_updates.cpp +++ b/cpp/src/dual_simplex/basis_updates.cpp @@ -2264,7 +2264,8 @@ int basis_update_mpf_t::refactor_basis( const std::vector& upper, std::vector& basic_list, std::vector& nonbasic_list, - std::vector& vstatus) + std::vector& vstatus, + f_t start_time) { raft::common::nvtx::range scope("LU::refactor_basis"); std::vector deficient; @@ -2282,8 +2283,10 @@ int basis_update_mpf_t::refactor_basis( inverse_row_permutation_, q, deficient, - slacks_needed); + slacks_needed, + start_time); if (status == CONCURRENT_HALT_RETURN) { return CONCURRENT_HALT_RETURN; } + if (status == TIME_LIMIT_RETURN) { return TIME_LIMIT_RETURN; } if (status == -1) { settings.log.debug("Initial factorization failed\n"); basis_repair(A, @@ -2323,8 +2326,10 @@ int basis_update_mpf_t::refactor_basis( inverse_row_permutation_, q, deficient, - slacks_needed); + slacks_needed, + start_time); if (status == CONCURRENT_HALT_RETURN) { return CONCURRENT_HALT_RETURN; } + if (status == TIME_LIMIT_RETURN) { return TIME_LIMIT_RETURN; } if (status == -1) { #ifdef CHECK_L_FACTOR if (L0_.check_matrix() == -1) { settings.log.printf("Bad L after basis repair\n"); } diff --git a/cpp/src/dual_simplex/basis_updates.hpp b/cpp/src/dual_simplex/basis_updates.hpp index d9783053f..6fd7b13f3 100644 --- a/cpp/src/dual_simplex/basis_updates.hpp +++ b/cpp/src/dual_simplex/basis_updates.hpp @@ -382,7 +382,8 @@ class basis_update_mpf_t { const std::vector& upper, std::vector& basic_list, std::vector& nonbasic_list, - std::vector& vstatus); + std::vector& vstatus, + f_t start_time); void set_refactor_frequency(i_t new_frequency) { refactor_frequency_ = new_frequency; } diff --git a/cpp/src/dual_simplex/crossover.cpp b/cpp/src/dual_simplex/crossover.cpp index 597628e73..65da8d45c 100644 --- a/cpp/src/dual_simplex/crossover.cpp +++ b/cpp/src/dual_simplex/crossover.cpp @@ -25,7 +25,7 @@ namespace { crossover_status_t return_to_status(int status) { - if (status == -1) { + if (status == TIME_LIMIT_RETURN) { return crossover_status_t::TIME_LIMIT; } else if (status == CONCURRENT_HALT_RETURN) { return crossover_status_t::CONCURRENT_LIMIT; @@ -505,10 +505,12 @@ i_t dual_push(const lp_problem_t& lp, std::vector q(m); std::vector deficient; std::vector slacks_needed; - i_t rank = - factorize_basis(lp.A, settings, basic_list, L, U, p, pinv, q, deficient, slacks_needed); + i_t rank = factorize_basis( + lp.A, settings, basic_list, L, U, p, pinv, q, deficient, slacks_needed, start_time); if (rank == CONCURRENT_HALT_RETURN) { return CONCURRENT_HALT_RETURN; + } else if (rank < 0) { + return rank; } else if (rank != m) { settings.log.printf("Failed to factorize basis. rank %d m %d\n", rank, m); basis_repair(lp.A, @@ -521,13 +523,12 @@ i_t dual_push(const lp_problem_t& lp, nonbasic_list, superbasic_list, vstatus); - rank = - factorize_basis(lp.A, settings, basic_list, L, U, p, pinv, q, deficient, slacks_needed); + rank = factorize_basis( + lp.A, settings, basic_list, L, U, p, pinv, q, deficient, slacks_needed, start_time); if (rank == CONCURRENT_HALT_RETURN) { return CONCURRENT_HALT_RETURN; - } else if (rank == -1) { - settings.log.printf("Failed to factorize basis after repair. rank %d m %d\n", rank, m); - return -1; + } else if (rank < 0) { + return rank; } else { settings.log.printf("Basis repaired\n"); } @@ -560,7 +561,7 @@ i_t dual_push(const lp_problem_t& lp, } if (toc(start_time) > settings.time_limit) { settings.log.printf("Crossover time exceeded\n"); - return -1; + return TIME_LIMIT_RETURN; } if (settings.concurrent_halt != nullptr && *settings.concurrent_halt == 1) { settings.log.printf("Concurrent halt\n"); @@ -860,10 +861,12 @@ i_t primal_push(const lp_problem_t& lp, std::vector q(m); std::vector deficient; std::vector slacks_needed; - i_t rank = - factorize_basis(lp.A, settings, basic_list, L, U, p, pinv, q, deficient, slacks_needed); + i_t rank = factorize_basis( + lp.A, settings, basic_list, L, U, p, pinv, q, deficient, slacks_needed, start_time); if (rank == CONCURRENT_HALT_RETURN) { return CONCURRENT_HALT_RETURN; + } else if (rank < 0) { + return rank; } else if (rank != m) { settings.log.debug("Failed to factorize basis. rank %d m %d\n", rank, m); basis_repair(lp.A, @@ -879,13 +882,12 @@ i_t primal_push(const lp_problem_t& lp, // We need to be careful. As basis_repair may have changed the superbasic list find_primal_superbasic_variables( lp, settings, solution, solution, vstatus, nonbasic_list, superbasic_list); - rank = - factorize_basis(lp.A, settings, basic_list, L, U, p, pinv, q, deficient, slacks_needed); + rank = factorize_basis( + lp.A, settings, basic_list, L, U, p, pinv, q, deficient, slacks_needed, start_time); if (rank == CONCURRENT_HALT_RETURN) { return CONCURRENT_HALT_RETURN; - } else if (rank == -1) { - settings.log.printf("Failed to factorize basis after repair. rank %d m %d\n", rank, m); - return -1; + } else if (rank < 0) { + return rank; } else { settings.log.debug("Basis repaired\n"); } @@ -915,7 +917,7 @@ i_t primal_push(const lp_problem_t& lp, if (toc(start_time) > settings.time_limit) { settings.log.printf("Crossover time limit exceeded\n"); - return -1; + return TIME_LIMIT_RETURN; } if (settings.concurrent_halt != nullptr && *settings.concurrent_halt == 1) { settings.log.printf("Concurrent halt\n"); @@ -1223,8 +1225,10 @@ crossover_status_t crossover(const lp_problem_t& lp, std::vector deficient; std::vector slacks_needed; - rank = factorize_basis(lp.A, settings, basic_list, L, U, p, pinv, q, deficient, slacks_needed); + rank = factorize_basis( + lp.A, settings, basic_list, L, U, p, pinv, q, deficient, slacks_needed, start_time); if (rank == CONCURRENT_HALT_RETURN) { return crossover_status_t::CONCURRENT_LIMIT; } + if (rank < 0) { return return_to_status(rank); } if (rank != m) { settings.log.debug("Failed to factorize basis. rank %d m %d\n", rank, m); basis_repair(lp.A, @@ -1237,12 +1241,13 @@ crossover_status_t crossover(const lp_problem_t& lp, nonbasic_list, superbasic_list, vstatus); - rank = factorize_basis(lp.A, settings, basic_list, L, U, p, pinv, q, deficient, slacks_needed); + rank = factorize_basis( + lp.A, settings, basic_list, L, U, p, pinv, q, deficient, slacks_needed, start_time); if (rank == CONCURRENT_HALT_RETURN) { return crossover_status_t::CONCURRENT_LIMIT; - } else if (rank == -1) { + } else if (rank < 0) { settings.log.printf("Failed to factorize basis after repair. rank %d m %d\n", rank, m); - return crossover_status_t::NUMERICAL_ISSUES; + return return_to_status(rank); } else { settings.log.debug("Basis repaired\n"); } @@ -1392,10 +1397,12 @@ crossover_status_t crossover(const lp_problem_t& lp, nonbasic_list.clear(); superbasic_list.clear(); get_basis_from_vstatus(m, vstatus, basic_list, nonbasic_list, superbasic_list); - rank = - factorize_basis(lp.A, settings, basic_list, L, U, p, pinv, q, deficient, slacks_needed); + rank = factorize_basis( + lp.A, settings, basic_list, L, U, p, pinv, q, deficient, slacks_needed, start_time); if (rank == CONCURRENT_HALT_RETURN) { return crossover_status_t::CONCURRENT_LIMIT; + } else if (rank < 0) { + return return_to_status(rank); } else if (rank != m) { settings.log.debug("Failed to factorize basis. rank %d m %d\n", rank, m); basis_repair(lp.A, @@ -1408,13 +1415,13 @@ crossover_status_t crossover(const lp_problem_t& lp, nonbasic_list, superbasic_list, vstatus); - rank = - factorize_basis(lp.A, settings, basic_list, L, U, p, pinv, q, deficient, slacks_needed); + rank = factorize_basis( + lp.A, settings, basic_list, L, U, p, pinv, q, deficient, slacks_needed, start_time); if (rank == CONCURRENT_HALT_RETURN) { return crossover_status_t::CONCURRENT_LIMIT; - } else if (rank == -1) { + } else if (rank < 0) { settings.log.printf("Failed to factorize basis after repair. rank %d m %d\n", rank, m); - return crossover_status_t::NUMERICAL_ISSUES; + return return_to_status(rank); } else { settings.log.debug("Basis repaired\n"); } diff --git a/cpp/src/dual_simplex/phase2.cpp b/cpp/src/dual_simplex/phase2.cpp index 1e057a16b..25d1c5fbe 100644 --- a/cpp/src/dual_simplex/phase2.cpp +++ b/cpp/src/dual_simplex/phase2.cpp @@ -2504,10 +2504,11 @@ dual::status_t dual_phase2_with_advanced_basis(i_t phase, assert(superbasic_list.size() == 0); assert(nonbasic_list.size() == n - m); - if (ft.refactor_basis(lp.A, settings, lp.lower, lp.upper, basic_list, nonbasic_list, vstatus) > - 0) { - return dual::status_t::NUMERICAL; - } + i_t refactor_status = ft.refactor_basis( + lp.A, settings, lp.lower, lp.upper, basic_list, nonbasic_list, vstatus, start_time); + if (refactor_status == CONCURRENT_HALT_RETURN) { return dual::status_t::CONCURRENT_LIMIT; } + if (refactor_status == TIME_LIMIT_RETURN) { return dual::status_t::TIME_LIMIT; } + if (refactor_status > 0) { return dual::status_t::NUMERICAL; } if (toc(start_time) > settings.time_limit) { return dual::status_t::TIME_LIMIT; } } @@ -3371,15 +3372,24 @@ dual::status_t dual_phase2_with_advanced_basis(i_t phase, PHASE2_NVTX_RANGE("DualSimplex::refactorization"); num_refactors++; bool should_recompute_x = false; - if (ft.refactor_basis( - lp.A, settings, lp.lower, lp.upper, basic_list, nonbasic_list, vstatus) > 0) { + i_t refactor_status = ft.refactor_basis( + lp.A, settings, lp.lower, lp.upper, basic_list, nonbasic_list, vstatus, start_time); + if (refactor_status == CONCURRENT_HALT_RETURN) { return dual::status_t::CONCURRENT_LIMIT; } + if (refactor_status == TIME_LIMIT_RETURN) { return dual::status_t::TIME_LIMIT; } + if (refactor_status > 0) { should_recompute_x = true; settings.log.printf("Failed to factorize basis. Iteration %d\n", iter); if (toc(start_time) > settings.time_limit) { return dual::status_t::TIME_LIMIT; } - i_t count = 0; - i_t deficient_size; - while ((deficient_size = ft.refactor_basis( - lp.A, settings, lp.lower, lp.upper, basic_list, nonbasic_list, vstatus)) > 0) { + i_t count = 0; + i_t deficient_size = 0; + while (true) { + deficient_size = ft.refactor_basis( + lp.A, settings, lp.lower, lp.upper, basic_list, nonbasic_list, vstatus, start_time); + if (deficient_size == CONCURRENT_HALT_RETURN) { + return dual::status_t::CONCURRENT_LIMIT; + } + if (deficient_size == TIME_LIMIT_RETURN) { return dual::status_t::TIME_LIMIT; } + if (deficient_size <= 0) { break; } settings.log.printf("Failed to repair basis. Iteration %d. %d deficient columns.\n", iter, static_cast(deficient_size)); @@ -3390,6 +3400,7 @@ dual::status_t dual_phase2_with_advanced_basis(i_t phase, count++; if (count > 10) { return dual::status_t::NUMERICAL; } } + if (deficient_size < 0) { return dual::status_t::NUMERICAL; } settings.log.printf("Successfully repaired basis. Iteration %d\n", iter); } diff --git a/cpp/src/dual_simplex/presolve.cpp b/cpp/src/dual_simplex/presolve.cpp index 8d4a533e9..f4602ac56 100644 --- a/cpp/src/dual_simplex/presolve.cpp +++ b/cpp/src/dual_simplex/presolve.cpp @@ -445,6 +445,7 @@ i_t find_dependent_rows(lp_problem_t& problem, i_t pivots = right_looking_lu_row_permutation_only(C, settings, 1e-13, tic(), q, pinv); if (pivots == CONCURRENT_HALT_RETURN) { return CONCURRENT_HALT_RETURN; } + if (pivots == TIME_LIMIT_RETURN) { return TIME_LIMIT_RETURN; } if (pivots < m) { settings.log.printf("Found %d dependent rows\n", m - pivots); const i_t num_dependent = m - pivots; @@ -1101,6 +1102,7 @@ i_t presolve(const lp_problem_t& original, f_t dependent_row_start = tic(); const i_t independent_rows = find_dependent_rows(problem, settings, dependent_rows, infeasible); if (independent_rows == CONCURRENT_HALT_RETURN) { return CONCURRENT_HALT_RETURN; } + if (independent_rows == TIME_LIMIT_RETURN) { return TIME_LIMIT_RETURN; } if (infeasible != kOk) { settings.log.printf("Found problem infeasible in presolve\n"); return -1; diff --git a/cpp/src/dual_simplex/primal.cpp b/cpp/src/dual_simplex/primal.cpp index 98f5f4193..2d0944542 100644 --- a/cpp/src/dual_simplex/primal.cpp +++ b/cpp/src/dual_simplex/primal.cpp @@ -294,10 +294,15 @@ primal::status_t primal_phase2(i_t phase, std::vector q(m); std::vector deficient; std::vector slacks_needed; - i_t rank = - factorize_basis(lp.A, settings, basic_list, L, U, p, pinv, q, deficient, slacks_needed); + i_t rank = factorize_basis( + lp.A, settings, basic_list, L, U, p, pinv, q, deficient, slacks_needed, start_time); if (rank == CONCURRENT_HALT_RETURN) { return primal::status_t::CONCURRENT_LIMIT; + } else if (rank == TIME_LIMIT_RETURN) { + return primal::status_t::TIME_LIMIT; + } else if (rank < 0) { + return toc(start_time) > settings.time_limit ? primal::status_t::TIME_LIMIT + : primal::status_t::NUMERICAL; } else if (rank != m) { settings.log.debug("Failed to factorize basis. rank %d m %d\n", rank, m); basis_repair(lp.A, @@ -310,12 +315,16 @@ primal::status_t primal_phase2(i_t phase, nonbasic_list, superbasic_list, vstatus); - rank = factorize_basis(lp.A, settings, basic_list, L, U, p, pinv, q, deficient, slacks_needed); + rank = factorize_basis( + lp.A, settings, basic_list, L, U, p, pinv, q, deficient, slacks_needed, start_time); if (rank == CONCURRENT_HALT_RETURN) { return primal::status_t::CONCURRENT_LIMIT; - } else if (rank == -1) { + } else if (rank == TIME_LIMIT_RETURN) { + return primal::status_t::TIME_LIMIT; + } else if (rank < 0) { settings.log.printf("Failed to factorize basis after repair. rank %d m %d\n", rank, m); - return primal::status_t::NUMERICAL; + return toc(start_time) > settings.time_limit ? primal::status_t::TIME_LIMIT + : primal::status_t::NUMERICAL; } else { settings.log.debug("Basis repaired\n"); } diff --git a/cpp/src/dual_simplex/right_looking_lu.cpp b/cpp/src/dual_simplex/right_looking_lu.cpp index cb9834705..4c55e1f19 100644 --- a/cpp/src/dual_simplex/right_looking_lu.cpp +++ b/cpp/src/dual_simplex/right_looking_lu.cpp @@ -580,7 +580,8 @@ i_t right_looking_lu(const csc_matrix_t& A, VectorI& q, csc_matrix_t& L, csc_matrix_t& U, - VectorI& pinv) + VectorI& pinv, + f_t start_time) { raft::common::nvtx::range scope("LU::right_looking_lu"); const i_t n = column_list.size(); @@ -634,7 +635,10 @@ i_t right_looking_lu(const csc_matrix_t& A, i_t pivots = 0; for (i_t k = 0; k < n; ++k) { - if (settings.concurrent_halt != nullptr && *settings.concurrent_halt == 1) { return -1; } + if (toc(start_time) > settings.time_limit) { return TIME_LIMIT_RETURN; } + if (settings.concurrent_halt != nullptr && *settings.concurrent_halt == 1) { + return CONCURRENT_HALT_RETURN; + } // Find pivot that satisfies // abs(pivot) >= abstol, // abs(pivot) >= threshold_tol * max abs[pivot column] @@ -1114,11 +1118,7 @@ i_t right_looking_lu_row_permutation_only(const csc_matrix_t& A, toc(factorization_start_time)); last_print = tic(); } - if (toc(factorization_start_time) > settings.time_limit) { - settings.log.printf("Right-looking LU factorization time exceeded\n"); - return -1; - } - + if (toc(start_time) > settings.time_limit) { return TIME_LIMIT_RETURN; } if (settings.concurrent_halt != nullptr && *settings.concurrent_halt == 1) { settings.log.printf("Concurrent halt\n"); return CONCURRENT_HALT_RETURN; @@ -1157,7 +1157,8 @@ template int right_looking_lu>( std::vector& q, csc_matrix_t& L, csc_matrix_t& U, - std::vector& pinv); + std::vector& pinv, + double start_time); template int right_looking_lu>( const csc_matrix_t& A, @@ -1167,7 +1168,8 @@ template int right_looking_lu>( ins_vector& q, csc_matrix_t& L, csc_matrix_t& U, - ins_vector& pinv); + ins_vector& pinv, + double start_time); template int right_looking_lu_row_permutation_only( const csc_matrix_t& A, diff --git a/cpp/src/dual_simplex/right_looking_lu.hpp b/cpp/src/dual_simplex/right_looking_lu.hpp index 179fff01b..3f83da42f 100644 --- a/cpp/src/dual_simplex/right_looking_lu.hpp +++ b/cpp/src/dual_simplex/right_looking_lu.hpp @@ -22,7 +22,8 @@ i_t right_looking_lu(const csc_matrix_t& A, VectorI& q, csc_matrix_t& L, csc_matrix_t& U, - VectorI& pinv); + VectorI& pinv, + f_t start_time); template i_t right_looking_lu_row_permutation_only(const csc_matrix_t& A, diff --git a/cpp/src/dual_simplex/solve.cpp b/cpp/src/dual_simplex/solve.cpp index 1e825b5e4..42f99a629 100644 --- a/cpp/src/dual_simplex/solve.cpp +++ b/cpp/src/dual_simplex/solve.cpp @@ -154,6 +154,7 @@ lp_status_t solve_linear_program_with_advanced_basis( ok = presolve(original_lp, settings, presolved_lp, presolve_info); } if (ok == CONCURRENT_HALT_RETURN) { return lp_status_t::CONCURRENT_LIMIT; } + if (ok == TIME_LIMIT_RETURN) { return lp_status_t::TIME_LIMIT; } if (ok == -1) { return lp_status_t::INFEASIBLE; } constexpr bool write_out_matlab = false; @@ -349,6 +350,7 @@ lp_status_t solve_linear_program_with_barrier(const user_problem_t& us lp_problem_t presolved_lp(user_problem.handle_ptr, 1, 1, 1); const i_t ok = presolve(original_lp, barrier_settings, presolved_lp, presolve_info); if (ok == CONCURRENT_HALT_RETURN) { return lp_status_t::CONCURRENT_LIMIT; } + if (ok == TIME_LIMIT_RETURN) { return lp_status_t::TIME_LIMIT; } if (ok == -1) { return lp_status_t::INFEASIBLE; } // Apply columns scaling to the presolve LP diff --git a/cpp/src/dual_simplex/types.hpp b/cpp/src/dual_simplex/types.hpp index 9de33ed3b..ea46a1f67 100644 --- a/cpp/src/dual_simplex/types.hpp +++ b/cpp/src/dual_simplex/types.hpp @@ -21,5 +21,7 @@ constexpr float64_t inf = std::numeric_limits::infinity(); // We return this constant to signal that a concurrent halt has occurred #define CONCURRENT_HALT_RETURN -2 +// We return this constant to signal that a time limit has occurred +#define TIME_LIMIT_RETURN -3 } // namespace cuopt::linear_programming::dual_simplex From 942de9c5937debdecf58ba4cd27139670629fa21 Mon Sep 17 00:00:00 2001 From: akifcorduk Date: Tue, 17 Feb 2026 02:56:56 -0800 Subject: [PATCH 06/15] remove timers from cuts --- cpp/src/branch_and_bound/branch_and_bound.cpp | 17 ++-- cpp/src/cuts/cuts.cpp | 79 +++---------------- cpp/src/cuts/cuts.hpp | 20 ++--- 3 files changed, 25 insertions(+), 91 deletions(-) diff --git a/cpp/src/branch_and_bound/branch_and_bound.cpp b/cpp/src/branch_and_bound/branch_and_bound.cpp index bd06dd3a0..33a67acbf 100644 --- a/cpp/src/branch_and_bound/branch_and_bound.cpp +++ b/cpp/src/branch_and_bound/branch_and_bound.cpp @@ -2139,8 +2139,7 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut basis_update, root_relax_soln_.x, basic_list, - nonbasic_list, - exploration_stats_.start_time); + nonbasic_list); if (stop_for_time_limit()) { return solver_status_; } f_t cut_generation_time = toc(cut_start_time); if (cut_generation_time > 1.0) { @@ -2149,7 +2148,7 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut // Score the cuts if (stop_for_time_limit()) { return solver_status_; } f_t score_start_time = tic(); - cut_pool.score_cuts(root_relax_soln_.x, exploration_stats_.start_time); + cut_pool.score_cuts(root_relax_soln_.x); if (stop_for_time_limit()) { return solver_status_; } f_t score_time = toc(score_start_time); if (score_time > 1.0) { settings_.log.debug("Cut scoring time %.2f seconds\n", score_time); } @@ -2157,8 +2156,7 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut csr_matrix_t cuts_to_add(0, original_lp_.num_cols, 0); std::vector cut_rhs; std::vector cut_types; - i_t num_cuts = - cut_pool.get_best_cuts(cuts_to_add, cut_rhs, cut_types, exploration_stats_.start_time); + i_t num_cuts = cut_pool.get_best_cuts(cuts_to_add, cut_rhs, cut_types); if (stop_for_time_limit()) { return solver_status_; } if (num_cuts == 0) { break; } cut_info.record_cut_types(cut_types); @@ -2205,8 +2203,7 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut basic_list, nonbasic_list, root_vstatus_, - edge_norms_, - exploration_stats_.start_time); + edge_norms_); var_types_.resize(original_lp_.num_cols, variable_type_t::CONTINUOUS); mutex_original_lp_.unlock(); if (stop_for_time_limit()) { return solver_status_; } @@ -2214,11 +2211,7 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut if (add_cuts_time > 1.0) { settings_.log.debug("Add cuts time %.2f seconds\n", add_cuts_time); } - if (add_cuts_status == -2) { - solver_status_ = mip_status_t::TIME_LIMIT; - set_final_solution(solution, root_objective_); - return solver_status_; - } else if (add_cuts_status != 0) { + if (add_cuts_status != 0) { settings_.log.printf("Failed to add cuts\n"); return mip_status_t::NUMERICAL; } diff --git a/cpp/src/cuts/cuts.cpp b/cpp/src/cuts/cuts.cpp index 2d98badb8..724233b1f 100644 --- a/cpp/src/cuts/cuts.cpp +++ b/cpp/src/cuts/cuts.cpp @@ -97,7 +97,7 @@ f_t cut_pool_t::cut_orthogonality(i_t i, i_t j) } template -void cut_pool_t::score_cuts(std::vector& x_relax, f_t start_time) +void cut_pool_t::score_cuts(std::vector& x_relax) { const f_t min_cut_distance = 1e-4; cut_distances_.resize(cut_storage_.m, 0.0); @@ -105,11 +105,6 @@ void cut_pool_t::score_cuts(std::vector& x_relax, f_t start_time) const bool verbose = false; for (i_t i = 0; i < cut_storage_.m; i++) { - if (toc(start_time) >= settings_.time_limit) { - best_cuts_.clear(); - scored_cuts_ = 0; - return; - } f_t violation; f_t cut_dist = cut_distance(i, x_relax, violation, cut_norms_[i]); cut_distances_[i] = cut_dist <= min_cut_distance ? 0.0 : cut_dist; @@ -140,7 +135,6 @@ void cut_pool_t::score_cuts(std::vector& x_relax, f_t start_time) } while (scored_cuts_ < max_cuts && !sorted_indices.empty()) { - if (toc(start_time) >= settings_.time_limit) { return; } const i_t i = sorted_indices.back(); sorted_indices.pop_back(); @@ -149,7 +143,6 @@ void cut_pool_t::score_cuts(std::vector& x_relax, f_t start_time) f_t cut_ortho = 1.0; const i_t best_cuts_size = best_cuts_.size(); for (i_t k = 0; k < best_cuts_size; k++) { - if (toc(start_time) >= settings_.time_limit) { return; } const i_t j = best_cuts_[k]; cut_ortho = std::min(cut_ortho, cut_orthogonality(i, j)); } @@ -163,8 +156,7 @@ void cut_pool_t::score_cuts(std::vector& x_relax, f_t start_time) template i_t cut_pool_t::get_best_cuts(csr_matrix_t& best_cuts, std::vector& best_rhs, - std::vector& best_cut_types, - f_t start_time) + std::vector& best_cut_types) { best_cuts.m = 0; best_cuts.n = original_vars_; @@ -179,7 +171,6 @@ i_t cut_pool_t::get_best_cuts(csr_matrix_t& best_cuts, best_cut_types.reserve(scored_cuts_); for (i_t k = 0; k < static_cast(best_cuts_.size()); ++k) { - if (toc(start_time) >= settings_.time_limit) { break; } const i_t i = best_cuts_[k]; sparse_vector_t cut(cut_storage_, i); cut.negate(); @@ -571,46 +562,33 @@ void cut_generation_t::generate_cuts(const lp_problem_t& lp, basis_update_mpf_t& basis_update, const std::vector& xstar, const std::vector& basic_list, - const std::vector& nonbasic_list, - f_t start_time) + const std::vector& nonbasic_list) { - if (toc(start_time) >= settings.time_limit) { return; } - // Generate Gomory and CG Cuts if (settings.mixed_integer_gomory_cuts != 0 || settings.strong_chvatal_gomory_cuts != 0) { f_t cut_start_time = tic(); - generate_gomory_cuts(lp, - settings, - Arow, - new_slacks, - var_types, - basis_update, - xstar, - basic_list, - nonbasic_list, - start_time); + generate_gomory_cuts( + lp, settings, Arow, new_slacks, var_types, basis_update, xstar, basic_list, nonbasic_list); f_t cut_generation_time = toc(cut_start_time); if (cut_generation_time > 1.0) { settings.log.debug("Gomory and CG cut generation time %.2f seconds\n", cut_generation_time); } } - if (toc(start_time) >= settings.time_limit) { return; } // Generate Knapsack cuts if (settings.knapsack_cuts != 0) { f_t cut_start_time = tic(); - generate_knapsack_cuts(lp, settings, Arow, new_slacks, var_types, xstar, start_time); + generate_knapsack_cuts(lp, settings, Arow, new_slacks, var_types, xstar); f_t cut_generation_time = toc(cut_start_time); if (cut_generation_time > 1.0) { settings.log.debug("Knapsack cut generation time %.2f seconds\n", cut_generation_time); } } - if (toc(start_time) >= settings.time_limit) { return; } // Generate MIR and CG cuts if (settings.mir_cuts != 0 || settings.strong_chvatal_gomory_cuts != 0) { f_t cut_start_time = tic(); - generate_mir_cuts(lp, settings, Arow, new_slacks, var_types, xstar, start_time); + generate_mir_cuts(lp, settings, Arow, new_slacks, var_types, xstar); f_t cut_generation_time = toc(cut_start_time); if (cut_generation_time > 1.0) { settings.log.debug("MIR and CG cut generation time %.2f seconds\n", cut_generation_time); @@ -625,12 +603,10 @@ void cut_generation_t::generate_knapsack_cuts( csr_matrix_t& Arow, const std::vector& new_slacks, const std::vector& var_types, - const std::vector& xstar, - f_t start_time) + const std::vector& xstar) { if (knapsack_generation_.num_knapsack_constraints() > 0) { for (i_t knapsack_row : knapsack_generation_.get_knapsack_constraints()) { - if (toc(start_time) >= settings.time_limit) { return; } sparse_vector_t cut(lp.num_cols, 0); f_t cut_rhs; i_t knapsack_status = knapsack_generation_.generate_knapsack_cuts( @@ -647,10 +623,8 @@ void cut_generation_t::generate_mir_cuts( csr_matrix_t& Arow, const std::vector& new_slacks, const std::vector& var_types, - const std::vector& xstar, - f_t start_time) + const std::vector& xstar) { - if (toc(start_time) >= settings.time_limit) { return; } f_t mir_start_time = tic(); mixed_integer_rounding_cut_t mir(lp, settings, new_slacks, xstar); strong_cg_cut_t cg(lp, var_types, xstar); @@ -668,7 +642,6 @@ void cut_generation_t::generate_mir_cuts( // Compute initial scores for all rows std::vector score(lp.num_rows, 0.0); for (i_t i = 0; i < lp.num_rows; i++) { - if (toc(start_time) >= settings.time_limit) { return; } const i_t row_start = Arow.row_start[i]; const i_t row_end = Arow.row_start[i + 1]; @@ -718,7 +691,6 @@ void cut_generation_t::generate_mir_cuts( const i_t max_cuts = std::min(lp.num_rows, 1000); f_t work_estimate = 0.0; for (i_t h = 0; h < max_cuts; h++) { - if (toc(start_time) >= settings.time_limit) { return; } // Get the row with the highest score const i_t i = sorted_indices.back(); sorted_indices.pop_back(); @@ -799,7 +771,6 @@ void cut_generation_t::generate_mir_cuts( work_estimate += lp.num_cols; while (!add_cut && num_aggregated < max_aggregated) { - if (toc(start_time) >= settings.time_limit) { return; } sparse_vector_t transformed_inequality; inequality.squeeze(transformed_inequality); f_t transformed_rhs = inequality_rhs; @@ -1011,15 +982,13 @@ void cut_generation_t::generate_gomory_cuts( basis_update_mpf_t& basis_update, const std::vector& xstar, const std::vector& basic_list, - const std::vector& nonbasic_list, - f_t start_time) + const std::vector& nonbasic_list) { tableau_equality_t tableau(lp, basis_update, nonbasic_list); mixed_integer_rounding_cut_t mir(lp, settings, new_slacks, xstar); strong_cg_cut_t cg(lp, var_types, xstar); for (i_t i = 0; i < lp.num_rows; i++) { - if (toc(start_time) >= settings.time_limit) { return; } sparse_vector_t inequality(lp.num_cols, 0); f_t inequality_rhs; const i_t j = basic_list[i]; @@ -2346,13 +2315,9 @@ i_t add_cuts(const simplex_solver_settings_t& settings, std::vector& basic_list, std::vector& nonbasic_list, std::vector& vstatus, - std::vector& edge_norms, - f_t start_time) + std::vector& edge_norms) { - constexpr i_t time_limit_status = -2; - if (toc(start_time) >= settings.time_limit) { return time_limit_status; } - // Given a set of cuts: C*x <= d that are currently violated // by the current solution x* (i.e. C*x* > d), this function // adds the cuts into the LP and solves again. @@ -2380,7 +2345,6 @@ i_t add_cuts(const simplex_solver_settings_t& settings, settings.log.debug("Original lp rows %d\n", lp.num_rows); settings.log.debug("Original lp cols %d\n", lp.num_cols); - if (toc(start_time) >= settings.time_limit) { return time_limit_status; } csr_matrix_t new_A_row(lp.num_rows, lp.num_cols, 1); lp.A.to_compressed_row(new_A_row); @@ -2390,7 +2354,6 @@ i_t add_cuts(const simplex_solver_settings_t& settings, assert(append_status == 0); } - if (toc(start_time) >= settings.time_limit) { return time_limit_status; } csc_matrix_t new_A_col(lp.num_rows + p, lp.num_cols, 1); new_A_row.to_compressed_col(new_A_col); @@ -2445,7 +2408,6 @@ i_t add_cuts(const simplex_solver_settings_t& settings, settings.log.debug("Done adding rhs\n"); // Construct C_B = C(:, basic_list) - if (toc(start_time) >= settings.time_limit) { return time_limit_status; } std::vector C_col_degree(lp.num_cols, 0); i_t cuts_nz = cuts.row_start[p]; for (i_t q = 0; q < cuts_nz; q++) { @@ -2475,7 +2437,6 @@ i_t add_cuts(const simplex_solver_settings_t& settings, } settings.log.debug("Done estimating C_B_nz\n"); - if (toc(start_time) >= settings.time_limit) { return time_limit_status; } csr_matrix_t C_B(p, num_basic, C_B_nz); nz = 0; for (i_t i = 0; i < p; i++) { @@ -2500,7 +2461,6 @@ i_t add_cuts(const simplex_solver_settings_t& settings, settings.log.debug("C_B rows %d cols %d nz %d\n", C_B.m, C_B.n, nz); // Adjust the basis update to include the new cuts - if (toc(start_time) >= settings.time_limit) { return time_limit_status; } basis_update.append_cuts(C_B); basic_list.resize(lp.num_rows, 0); @@ -2538,7 +2498,6 @@ i_t add_cuts(const simplex_solver_settings_t& settings, solution.y.resize(lp.num_rows, 0.0); solution.z.resize(lp.num_cols, 0.0); - if (toc(start_time) >= settings.time_limit) { return time_limit_status; } return 0; } @@ -2559,9 +2518,6 @@ i_t remove_cuts(lp_problem_t& lp, basis_update_mpf_t& basis_update, f_t start_time) { - constexpr i_t time_limit_status = -2; - if (toc(start_time) >= settings.time_limit) { return time_limit_status; } - std::vector cuts_to_remove; cuts_to_remove.reserve(lp.num_rows - original_rows); std::vector slacks_to_remove; @@ -2570,7 +2526,6 @@ i_t remove_cuts(lp_problem_t& lp, std::vector is_slack(lp.num_cols, 0); for (i_t j : new_slacks) { - if (toc(start_time) >= settings.time_limit) { return time_limit_status; } is_slack[j] = 1; #ifdef CHECK_SLACKS // Check that slack column length is 1 @@ -2585,14 +2540,12 @@ i_t remove_cuts(lp_problem_t& lp, } for (i_t k = original_rows; k < lp.num_rows; k++) { - if (toc(start_time) >= settings.time_limit) { return time_limit_status; } if (std::abs(y[k]) < dual_tol) { const i_t row_start = Arow.row_start[k]; const i_t row_end = Arow.row_start[k + 1]; i_t last_slack = -1; const f_t slack_tol = 1e-3; for (i_t p = row_start; p < row_end; p++) { - if (toc(start_time) >= settings.time_limit) { return time_limit_status; } const i_t j = Arow.j[p]; if (is_slack[j]) { if (vstatus[j] == variable_status_t::BASIC && x[j] > slack_tol) { last_slack = j; } @@ -2606,7 +2559,6 @@ i_t remove_cuts(lp_problem_t& lp, } if (cuts_to_remove.size() > 0) { - if (toc(start_time) >= settings.time_limit) { return time_limit_status; } std::vector marked_rows(lp.num_rows, 0); for (i_t i : cuts_to_remove) { marked_rows[i] = 1; @@ -2620,7 +2572,6 @@ i_t remove_cuts(lp_problem_t& lp, std::vector new_solution_y(lp.num_rows - cuts_to_remove.size()); i_t h = 0; for (i_t i = 0; i < lp.num_rows; i++) { - if (toc(start_time) >= settings.time_limit) { return time_limit_status; } if (!marked_rows[i]) { new_rhs[h] = lp.rhs[i]; new_solution_y[h] = y[i]; @@ -2647,7 +2598,6 @@ i_t remove_cuts(lp_problem_t& lp, std::vector new_is_slacks(lp.num_cols - slacks_to_remove.size(), 0); h = 0; for (i_t k = 0; k < lp.num_cols; k++) { - if (toc(start_time) >= settings.time_limit) { return time_limit_status; } if (!marked_cols[k]) { new_objective[h] = lp.objective[k]; new_lower[h] = lp.lower[k]; @@ -2695,15 +2645,13 @@ i_t remove_cuts(lp_problem_t& lp, lp.num_cols, lp.A.col_start[lp.A.n]); - if (toc(start_time) >= settings.time_limit) { return time_limit_status; } basis_update.resize(lp.num_rows); i_t refactor_status = basis_update.refactor_basis( lp.A, settings, lp.lower, lp.upper, basic_list, nonbasic_list, vstatus, start_time); if (refactor_status == CONCURRENT_HALT_RETURN) { return CONCURRENT_HALT_RETURN; } - if (refactor_status == TIME_LIMIT_RETURN) { return time_limit_status; } + if (refactor_status == TIME_LIMIT_RETURN) { return TIME_LIMIT_RETURN; } } - if (toc(start_time) >= settings.time_limit) { return time_limit_status; } return 0; } @@ -2849,8 +2797,7 @@ template int add_cuts(const simplex_solver_settings_t& settings, std::vector& basic_list, std::vector& nonbasic_list, std::vector& vstatus, - std::vector& edge_norms, - double start_time); + std::vector& edge_norms); template int remove_cuts(lp_problem_t& lp, const simplex_solver_settings_t& settings, diff --git a/cpp/src/cuts/cuts.hpp b/cpp/src/cuts/cuts.hpp index 17f5e032a..96df6240b 100644 --- a/cpp/src/cuts/cuts.hpp +++ b/cpp/src/cuts/cuts.hpp @@ -138,13 +138,12 @@ class cut_pool_t { // cut'*xstart < rhs void add_cut(cut_type_t cut_type, const sparse_vector_t& cut, f_t rhs); - void score_cuts(std::vector& x_relax, f_t start_time); + void score_cuts(std::vector& x_relax); // We return the cuts in the form best_cuts*x <= best_rhs i_t get_best_cuts(csr_matrix_t& best_cuts, std::vector& best_rhs, - std::vector& best_cut_types, - f_t start_time); + std::vector& best_cut_types); void age_cuts(); @@ -240,8 +239,7 @@ class cut_generation_t { basis_update_mpf_t& basis_update, const std::vector& xstar, const std::vector& basic_list, - const std::vector& nonbasic_list, - f_t start_time); + const std::vector& nonbasic_list); private: // Generate all mixed integer gomory cuts @@ -253,8 +251,7 @@ class cut_generation_t { basis_update_mpf_t& basis_update, const std::vector& xstar, const std::vector& basic_list, - const std::vector& nonbasic_list, - f_t start_time); + const std::vector& nonbasic_list); // Generate all mixed integer rounding cuts void generate_mir_cuts(const lp_problem_t& lp, @@ -262,8 +259,7 @@ class cut_generation_t { csr_matrix_t& Arow, const std::vector& new_slacks, const std::vector& var_types, - const std::vector& xstar, - f_t start_time); + const std::vector& xstar); // Generate all knapsack cuts void generate_knapsack_cuts(const lp_problem_t& lp, @@ -271,8 +267,7 @@ class cut_generation_t { csr_matrix_t& Arow, const std::vector& new_slacks, const std::vector& var_types, - const std::vector& xstar, - f_t start_time); + const std::vector& xstar); cut_pool_t& cut_pool_; knapsack_generation_t knapsack_generation_; @@ -463,8 +458,7 @@ i_t add_cuts(const simplex_solver_settings_t& settings, std::vector& basic_list, std::vector& nonbasic_list, std::vector& vstatus, - std::vector& edge_norms, - f_t start_time); + std::vector& edge_norms); template i_t remove_cuts(lp_problem_t& lp, From 0b944d1e01661ae4c39eff7036324f329ee6895a Mon Sep 17 00:00:00 2001 From: akifcorduk Date: Tue, 17 Feb 2026 04:17:26 -0800 Subject: [PATCH 07/15] convert lambda to function and remove unnecessary checks --- cpp/src/branch_and_bound/branch_and_bound.cpp | 57 +++++++------------ cpp/src/branch_and_bound/branch_and_bound.hpp | 2 + 2 files changed, 22 insertions(+), 37 deletions(-) diff --git a/cpp/src/branch_and_bound/branch_and_bound.cpp b/cpp/src/branch_and_bound/branch_and_bound.cpp index 33a67acbf..6624e42e8 100644 --- a/cpp/src/branch_and_bound/branch_and_bound.cpp +++ b/cpp/src/branch_and_bound/branch_and_bound.cpp @@ -1946,6 +1946,18 @@ lp_status_t branch_and_bound_t::solve_root_relaxation( return root_status; } +template +bool branch_and_bound_t::stop_for_time_limit(mip_solution_t& solution) +{ + const f_t elapsed = toc(exploration_stats_.start_time); + if (elapsed > settings_.time_limit) { + solver_status_ = mip_status_t::TIME_LIMIT; + set_final_solution(solution, root_objective_); + return true; + } + return false; +}; + template mip_status_t branch_and_bound_t::solve(mip_solution_t& solution) { @@ -2099,19 +2111,10 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut f_t last_upper_bound = std::numeric_limits::infinity(); f_t last_objective = root_objective_; f_t root_relax_objective = root_objective_; - auto stop_for_time_limit = [&]() -> bool { - const f_t elapsed = toc(exploration_stats_.start_time); - if (elapsed > settings_.time_limit) { - solver_status_ = mip_status_t::TIME_LIMIT; - set_final_solution(solution, root_objective_); - return true; - } - return false; - }; i_t cut_pool_size = 0; for (i_t cut_pass = 0; cut_pass < settings_.max_cut_passes; cut_pass++) { - if (stop_for_time_limit()) { return solver_status_; } + if (stop_for_time_limit(solution)) { return solver_status_; } if (num_fractional == 0) { set_solution_at_root(solution, cut_info); return mip_status_t::OPTIMAL; @@ -2128,8 +2131,6 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut } #endif - // Generate cuts and add them to the cut pool - if (stop_for_time_limit()) { return solver_status_; } f_t cut_start_time = tic(); cut_generation.generate_cuts(original_lp_, settings_, @@ -2140,16 +2141,14 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut root_relax_soln_.x, basic_list, nonbasic_list); - if (stop_for_time_limit()) { return solver_status_; } f_t cut_generation_time = toc(cut_start_time); if (cut_generation_time > 1.0) { settings_.log.debug("Cut generation time %.2f seconds\n", cut_generation_time); } // Score the cuts - if (stop_for_time_limit()) { return solver_status_; } f_t score_start_time = tic(); cut_pool.score_cuts(root_relax_soln_.x); - if (stop_for_time_limit()) { return solver_status_; } + if (stop_for_time_limit(solution)) { return solver_status_; } f_t score_time = toc(score_start_time); if (score_time > 1.0) { settings_.log.debug("Cut scoring time %.2f seconds\n", score_time); } // Get the best cuts from the cut pool @@ -2157,7 +2156,6 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut std::vector cut_rhs; std::vector cut_types; i_t num_cuts = cut_pool.get_best_cuts(cuts_to_add, cut_rhs, cut_types); - if (stop_for_time_limit()) { return solver_status_; } if (num_cuts == 0) { break; } cut_info.record_cut_types(cut_types); #ifdef PRINT_CUT_POOL_TYPES @@ -2190,7 +2188,6 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut cuts_to_add.m + original_lp_.num_rows); lp_settings.log.log = false; - if (stop_for_time_limit()) { return solver_status_; } f_t add_cuts_start_time = tic(); mutex_original_lp_.lock(); i_t add_cuts_status = add_cuts(settings_, @@ -2206,7 +2203,6 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut edge_norms_); var_types_.resize(original_lp_.num_cols, variable_type_t::CONTINUOUS); mutex_original_lp_.unlock(); - if (stop_for_time_limit()) { return solver_status_; } f_t add_cuts_time = toc(add_cuts_start_time); if (add_cuts_time > 1.0) { settings_.log.debug("Add cuts time %.2f seconds\n", add_cuts_time); @@ -2238,7 +2234,6 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut #endif original_lp_.A.to_compressed_row(Arow_); - if (stop_for_time_limit()) { return solver_status_; } f_t node_presolve_start_time = tic(); bounds_strengthening_t node_presolve(original_lp_, Arow_, row_sense, var_types_); std::vector new_lower = original_lp_.lower; @@ -2249,7 +2244,6 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut original_lp_.lower = new_lower; original_lp_.upper = new_upper; mutex_original_lp_.unlock(); - if (stop_for_time_limit()) { return solver_status_; } f_t node_presolve_time = toc(node_presolve_start_time); if (node_presolve_time > 1.0) { settings_.log.debug("Node presolve time %.2f seconds\n", node_presolve_time); @@ -2262,9 +2256,8 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut i_t iter = 0; bool initialize_basis = false; lp_settings.concurrent_halt = NULL; - if (stop_for_time_limit()) { return solver_status_; } - f_t dual_phase2_start_time = tic(); - dual::status_t cut_status = dual_phase2_with_advanced_basis(2, + f_t dual_phase2_start_time = tic(); + dual::status_t cut_status = dual_phase2_with_advanced_basis(2, 0, initialize_basis, exploration_stats_.start_time, @@ -2283,15 +2276,10 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut if (dual_phase2_time > 1.0) { settings_.log.debug("Dual phase2 time %.2f seconds\n", dual_phase2_time); } - if (stop_for_time_limit()) { return solver_status_; } - if (cut_status == dual::status_t::TIME_LIMIT) { - solver_status_ = mip_status_t::TIME_LIMIT; - set_final_solution(solution, root_objective_); - return solver_status_; - } + + if (stop_for_time_limit(solution)) { return solver_status_; } if (cut_status != dual::status_t::OPTIMAL) { - if (stop_for_time_limit()) { return solver_status_; } settings_.log.printf("Numerical issue at root node. Resolving from scratch\n"); lp_status_t scratch_status = solve_linear_program_with_advanced_basis(original_lp_, @@ -2303,11 +2291,7 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut nonbasic_list, root_vstatus_, edge_norms_); - if (stop_for_time_limit() || scratch_status == lp_status_t::TIME_LIMIT) { - solver_status_ = mip_status_t::TIME_LIMIT; - set_final_solution(solution, root_objective_); - return solver_status_; - } + if (stop_for_time_limit(solution)) { return solver_status_; } if (scratch_status == lp_status_t::OPTIMAL) { // We recovered cut_status = convert_lp_status_to_dual_status(scratch_status); @@ -2319,7 +2303,6 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut } } - if (stop_for_time_limit()) { return solver_status_; } f_t remove_cuts_start_time = tic(); mutex_original_lp_.lock(); i_t remove_cuts_status = remove_cuts(original_lp_, @@ -2338,7 +2321,7 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut basis_update, exploration_stats_.start_time); mutex_original_lp_.unlock(); - if (stop_for_time_limit()) { return solver_status_; } + if (stop_for_time_limit(solution)) { return solver_status_; } f_t remove_cuts_time = toc(remove_cuts_start_time); if (remove_cuts_time > 1.0) { settings_.log.debug("Remove cuts time %.2f seconds\n", remove_cuts_time); diff --git a/cpp/src/branch_and_bound/branch_and_bound.hpp b/cpp/src/branch_and_bound/branch_and_bound.hpp index 84ee986c4..a13d5cedc 100644 --- a/cpp/src/branch_and_bound/branch_and_bound.hpp +++ b/cpp/src/branch_and_bound/branch_and_bound.hpp @@ -106,6 +106,8 @@ class branch_and_bound_t { void set_concurrent_lp_root_solve(bool enable) { enable_concurrent_lp_root_solve_ = enable; } + bool stop_for_time_limit(mip_solution_t& solution); + // Repair a low-quality solution from the heuristics. bool repair_solution(const std::vector& leaf_edge_norms, const std::vector& potential_solution, From 71b7f2f41247b6b0bcec382457f7cfeb694cc6b7 Mon Sep 17 00:00:00 2001 From: akifcorduk Date: Tue, 17 Feb 2026 08:01:00 -0800 Subject: [PATCH 08/15] fix thrust changes --- cpp/src/pdlp/restart_strategy/pdlp_restart_strategy.cu | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cpp/src/pdlp/restart_strategy/pdlp_restart_strategy.cu b/cpp/src/pdlp/restart_strategy/pdlp_restart_strategy.cu index bc12fb360..31916e2c3 100644 --- a/cpp/src/pdlp/restart_strategy/pdlp_restart_strategy.cu +++ b/cpp/src/pdlp/restart_strategy/pdlp_restart_strategy.cu @@ -1995,14 +1995,14 @@ void pdlp_restart_strategy_t::solve_bound_constrained_trust_region( f_t* end = threshold_.data() + primal_size_h_ + dual_size_h_; auto highest_negInf_primal = thrust::find(handle_ptr_->get_thrust_policy(), - thrust::make_reverse_iterator(thrust::device_ptr(end)), - thrust::make_reverse_iterator(thrust::device_ptr(start)), + cuda::std::reverse_iterator(thrust::device_ptr(end)), + cuda::std::reverse_iterator(thrust::device_ptr(start)), -std::numeric_limits::infinity()); // Set ranges accordingly i_t index_start_primal = 0; i_t index_end_primal = primal_size_h_ + dual_size_h_; - if (highest_negInf_primal != thrust::make_reverse_iterator(thrust::device_ptr(start))) { + if (highest_negInf_primal != cuda::std::reverse_iterator(thrust::device_ptr(start))) { cuopt_assert(device_to_host_value(thrust::raw_pointer_cast(&*highest_negInf_primal)) == -std::numeric_limits::infinity(), "Incorrect primal reverse iterator"); From 82b2d64408abcaced475e47fda80df332ff48d9c Mon Sep 17 00:00:00 2001 From: akifcorduk Date: Wed, 18 Feb 2026 02:12:13 -0800 Subject: [PATCH 09/15] handle review comments --- cpp/src/branch_and_bound/branch_and_bound.cpp | 10 ++++++++-- cpp/src/cuts/cuts.cpp | 3 +-- cpp/src/dual_simplex/crossover.cpp | 9 ++------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/cpp/src/branch_and_bound/branch_and_bound.cpp b/cpp/src/branch_and_bound/branch_and_bound.cpp index 6624e42e8..8c32f5494 100644 --- a/cpp/src/branch_and_bound/branch_and_bound.cpp +++ b/cpp/src/branch_and_bound/branch_and_bound.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -1893,7 +1894,7 @@ lp_status_t branch_and_bound_t::solve_root_relaxation( if (refactor_status == TIME_LIMIT_RETURN) { root_status = lp_status_t::TIME_LIMIT; } else if (refactor_status == CONCURRENT_HALT_RETURN) { - root_status = lp_status_t::TIME_LIMIT; + root_status = lp_status_t::CONCURRENT_LIMIT; } else if (refactor_status != 0) { settings_.log.printf("Failed to refactor basis. %d deficient columns.\n", refactor_status); assert(refactor_status == 0); @@ -2373,7 +2374,12 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut set_uninitialized_steepest_edge_norms(original_lp_, basic_list, edge_norms_); pc_.resize(original_lp_.num_cols); - if (toc(exploration_stats_.start_time) < settings_.time_limit) { + if (toc(exploration_stats_.start_time) >= settings_.time_limit) { + solver_status_ = mip_status_t::TIME_LIMIT; + set_final_solution(solution, root_objective_); + return solver_status_; + } + { raft::common::nvtx::range scope_sb("BB::strong_branching"); strong_branching(original_problem_, original_lp_, diff --git a/cpp/src/cuts/cuts.cpp b/cpp/src/cuts/cuts.cpp index 724233b1f..fab6297de 100644 --- a/cpp/src/cuts/cuts.cpp +++ b/cpp/src/cuts/cuts.cpp @@ -170,8 +170,7 @@ i_t cut_pool_t::get_best_cuts(csr_matrix_t& best_cuts, best_cut_types.clear(); best_cut_types.reserve(scored_cuts_); - for (i_t k = 0; k < static_cast(best_cuts_.size()); ++k) { - const i_t i = best_cuts_[k]; + for (i_t i : best_cuts_) { sparse_vector_t cut(cut_storage_, i); cut.negate(); best_cuts.append_row(cut); diff --git a/cpp/src/dual_simplex/crossover.cpp b/cpp/src/dual_simplex/crossover.cpp index 65da8d45c..626b15035 100644 --- a/cpp/src/dual_simplex/crossover.cpp +++ b/cpp/src/dual_simplex/crossover.cpp @@ -1227,7 +1227,6 @@ crossover_status_t crossover(const lp_problem_t& lp, rank = factorize_basis( lp.A, settings, basic_list, L, U, p, pinv, q, deficient, slacks_needed, start_time); - if (rank == CONCURRENT_HALT_RETURN) { return crossover_status_t::CONCURRENT_LIMIT; } if (rank < 0) { return return_to_status(rank); } if (rank != m) { settings.log.debug("Failed to factorize basis. rank %d m %d\n", rank, m); @@ -1399,9 +1398,7 @@ crossover_status_t crossover(const lp_problem_t& lp, get_basis_from_vstatus(m, vstatus, basic_list, nonbasic_list, superbasic_list); rank = factorize_basis( lp.A, settings, basic_list, L, U, p, pinv, q, deficient, slacks_needed, start_time); - if (rank == CONCURRENT_HALT_RETURN) { - return crossover_status_t::CONCURRENT_LIMIT; - } else if (rank < 0) { + if (rank < 0) { return return_to_status(rank); } else if (rank != m) { settings.log.debug("Failed to factorize basis. rank %d m %d\n", rank, m); @@ -1417,9 +1414,7 @@ crossover_status_t crossover(const lp_problem_t& lp, vstatus); rank = factorize_basis( lp.A, settings, basic_list, L, U, p, pinv, q, deficient, slacks_needed, start_time); - if (rank == CONCURRENT_HALT_RETURN) { - return crossover_status_t::CONCURRENT_LIMIT; - } else if (rank < 0) { + if (rank < 0) { settings.log.printf("Failed to factorize basis after repair. rank %d m %d\n", rank, m); return return_to_status(rank); } else { From 0c811732b97d9e945e843c8eaa4647b0172126ae Mon Sep 17 00:00:00 2001 From: akifcorduk Date: Wed, 18 Feb 2026 06:04:46 -0800 Subject: [PATCH 10/15] handle review comments --- cpp/src/branch_and_bound/branch_and_bound.cpp | 36 +++++++++---------- cpp/src/cuts/cuts.cpp | 8 ++--- cpp/src/cuts/cuts.hpp | 4 +-- cpp/src/dual_simplex/basis_solves.cpp | 6 ++-- cpp/src/dual_simplex/right_looking_lu.cpp | 14 ++++---- cpp/src/dual_simplex/right_looking_lu.hpp | 4 +-- 6 files changed, 35 insertions(+), 37 deletions(-) diff --git a/cpp/src/branch_and_bound/branch_and_bound.cpp b/cpp/src/branch_and_bound/branch_and_bound.cpp index 8c32f5494..30b913e6e 100644 --- a/cpp/src/branch_and_bound/branch_and_bound.cpp +++ b/cpp/src/branch_and_bound/branch_and_bound.cpp @@ -20,7 +20,6 @@ #include #include #include -#include #include #include @@ -1957,7 +1956,7 @@ bool branch_and_bound_t::stop_for_time_limit(mip_solution_t& return true; } return false; -}; +} template mip_status_t branch_and_bound_t::solve(mip_solution_t& solution) @@ -2115,7 +2114,6 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut i_t cut_pool_size = 0; for (i_t cut_pass = 0; cut_pass < settings_.max_cut_passes; cut_pass++) { - if (stop_for_time_limit(solution)) { return solver_status_; } if (num_fractional == 0) { set_solution_at_root(solution, cut_info); return mip_status_t::OPTIMAL; @@ -2132,6 +2130,7 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut } #endif + // Generate cuts and add them to the cut pool f_t cut_start_time = tic(); cut_generation.generate_cuts(original_lp_, settings_, @@ -2306,23 +2305,22 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut f_t remove_cuts_start_time = tic(); mutex_original_lp_.lock(); - i_t remove_cuts_status = remove_cuts(original_lp_, - settings_, - Arow_, - new_slacks_, - original_rows, - var_types_, - root_vstatus_, - edge_norms_, - root_relax_soln_.x, - root_relax_soln_.y, - root_relax_soln_.z, - basic_list, - nonbasic_list, - basis_update, - exploration_stats_.start_time); + remove_cuts(original_lp_, + settings_, + exploration_stats_.start_time, + Arow_, + new_slacks_, + original_rows, + var_types_, + root_vstatus_, + edge_norms_, + root_relax_soln_.x, + root_relax_soln_.y, + root_relax_soln_.z, + basic_list, + nonbasic_list, + basis_update); mutex_original_lp_.unlock(); - if (stop_for_time_limit(solution)) { return solver_status_; } f_t remove_cuts_time = toc(remove_cuts_start_time); if (remove_cuts_time > 1.0) { settings_.log.debug("Remove cuts time %.2f seconds\n", remove_cuts_time); diff --git a/cpp/src/cuts/cuts.cpp b/cpp/src/cuts/cuts.cpp index fab6297de..bbb4d1f15 100644 --- a/cpp/src/cuts/cuts.cpp +++ b/cpp/src/cuts/cuts.cpp @@ -2503,6 +2503,7 @@ i_t add_cuts(const simplex_solver_settings_t& settings, template i_t remove_cuts(lp_problem_t& lp, const simplex_solver_settings_t& settings, + f_t start_time, csr_matrix_t& Arow, std::vector& new_slacks, i_t original_rows, @@ -2514,8 +2515,7 @@ i_t remove_cuts(lp_problem_t& lp, std::vector& z, std::vector& basic_list, std::vector& nonbasic_list, - basis_update_mpf_t& basis_update, - f_t start_time) + basis_update_mpf_t& basis_update) { std::vector cuts_to_remove; cuts_to_remove.reserve(lp.num_rows - original_rows); @@ -2800,6 +2800,7 @@ template int add_cuts(const simplex_solver_settings_t& settings, template int remove_cuts(lp_problem_t& lp, const simplex_solver_settings_t& settings, + double start_time, csr_matrix_t& Arow, std::vector& new_slacks, int original_rows, @@ -2811,8 +2812,7 @@ template int remove_cuts(lp_problem_t& lp, std::vector& z, std::vector& basic_list, std::vector& nonbasic_list, - basis_update_mpf_t& basis_update, - double start_time); + basis_update_mpf_t& basis_update); template void read_saved_solution_for_cut_verification( const lp_problem_t& lp, diff --git a/cpp/src/cuts/cuts.hpp b/cpp/src/cuts/cuts.hpp index 96df6240b..4f55e96e4 100644 --- a/cpp/src/cuts/cuts.hpp +++ b/cpp/src/cuts/cuts.hpp @@ -463,6 +463,7 @@ i_t add_cuts(const simplex_solver_settings_t& settings, template i_t remove_cuts(lp_problem_t& lp, const simplex_solver_settings_t& settings, + f_t start_time, csr_matrix_t& Arow, std::vector& new_slacks, i_t original_rows, @@ -474,7 +475,6 @@ i_t remove_cuts(lp_problem_t& lp, std::vector& z, std::vector& basic_list, std::vector& nonbasic_list, - basis_update_mpf_t& basis_update, - f_t start_time); + basis_update_mpf_t& basis_update); } // namespace cuopt::linear_programming::dual_simplex diff --git a/cpp/src/dual_simplex/basis_solves.cpp b/cpp/src/dual_simplex/basis_solves.cpp index 9a8cfc143..1c17fc557 100644 --- a/cpp/src/dual_simplex/basis_solves.cpp +++ b/cpp/src/dual_simplex/basis_solves.cpp @@ -361,11 +361,11 @@ i_t factorize_basis(const csc_matrix_t& A, settings, settings.threshold_partial_pivoting_tol, identity, + start_time, S_col_perm, SL, SU, - S_perm_inv, - start_time); + S_perm_inv); if (settings.concurrent_halt != nullptr && *settings.concurrent_halt == 1) { return CONCURRENT_HALT_RETURN; } @@ -570,7 +570,7 @@ i_t factorize_basis(const csc_matrix_t& A, } q.resize(m); f_t fact_start = tic(); - rank = right_looking_lu(A, settings, medium_tol, basic_list, q, L, U, pinv, start_time); + rank = right_looking_lu(A, settings, medium_tol, basic_list, start_time, q, L, U, pinv); if (rank < 0) { if (settings.concurrent_halt != nullptr && *settings.concurrent_halt == 1) { return CONCURRENT_HALT_RETURN; diff --git a/cpp/src/dual_simplex/right_looking_lu.cpp b/cpp/src/dual_simplex/right_looking_lu.cpp index 4c55e1f19..0f3a9bc2b 100644 --- a/cpp/src/dual_simplex/right_looking_lu.cpp +++ b/cpp/src/dual_simplex/right_looking_lu.cpp @@ -577,11 +577,11 @@ i_t right_looking_lu(const csc_matrix_t& A, const simplex_solver_settings_t& settings, f_t tol, const VectorI& column_list, + f_t start_time, VectorI& q, csc_matrix_t& L, csc_matrix_t& U, - VectorI& pinv, - f_t start_time) + VectorI& pinv) { raft::common::nvtx::range scope("LU::right_looking_lu"); const i_t n = column_list.size(); @@ -635,10 +635,10 @@ i_t right_looking_lu(const csc_matrix_t& A, i_t pivots = 0; for (i_t k = 0; k < n; ++k) { - if (toc(start_time) > settings.time_limit) { return TIME_LIMIT_RETURN; } if (settings.concurrent_halt != nullptr && *settings.concurrent_halt == 1) { return CONCURRENT_HALT_RETURN; } + if (toc(start_time) > settings.time_limit) { return TIME_LIMIT_RETURN; } // Find pivot that satisfies // abs(pivot) >= abstol, // abs(pivot) >= threshold_tol * max abs[pivot column] @@ -1154,22 +1154,22 @@ template int right_looking_lu>( const simplex_solver_settings_t& settings, double tol, const std::vector& column_list, + double start_time, std::vector& q, csc_matrix_t& L, csc_matrix_t& U, - std::vector& pinv, - double start_time); + std::vector& pinv); template int right_looking_lu>( const csc_matrix_t& A, const simplex_solver_settings_t& settings, double tol, const ins_vector& column_list, + double start_time, ins_vector& q, csc_matrix_t& L, csc_matrix_t& U, - ins_vector& pinv, - double start_time); + ins_vector& pinv); template int right_looking_lu_row_permutation_only( const csc_matrix_t& A, diff --git a/cpp/src/dual_simplex/right_looking_lu.hpp b/cpp/src/dual_simplex/right_looking_lu.hpp index 3f83da42f..02883ef75 100644 --- a/cpp/src/dual_simplex/right_looking_lu.hpp +++ b/cpp/src/dual_simplex/right_looking_lu.hpp @@ -19,11 +19,11 @@ i_t right_looking_lu(const csc_matrix_t& A, const simplex_solver_settings_t& settings, f_t tol, const VectorI& column_list, + f_t start_time, VectorI& q, csc_matrix_t& L, csc_matrix_t& U, - VectorI& pinv, - f_t start_time); + VectorI& pinv); template i_t right_looking_lu_row_permutation_only(const csc_matrix_t& A, From 609c578c19312fc5c284892841120e8637f1145e Mon Sep 17 00:00:00 2001 From: akifcorduk Date: Wed, 18 Feb 2026 22:15:36 -0800 Subject: [PATCH 11/15] move timer with inout parameters --- cpp/src/branch_and_bound/branch_and_bound.cpp | 54 +++++-------------- cpp/src/cuts/cuts.cpp | 2 +- cpp/src/dual_simplex/basis_solves.cpp | 8 +-- cpp/src/dual_simplex/basis_solves.hpp | 4 +- cpp/src/dual_simplex/basis_updates.cpp | 12 ++--- cpp/src/dual_simplex/basis_updates.hpp | 4 +- cpp/src/dual_simplex/crossover.cpp | 16 +++--- cpp/src/dual_simplex/phase2.cpp | 6 +-- cpp/src/dual_simplex/primal.cpp | 4 +- 9 files changed, 42 insertions(+), 68 deletions(-) diff --git a/cpp/src/branch_and_bound/branch_and_bound.cpp b/cpp/src/branch_and_bound/branch_and_bound.cpp index 30b913e6e..054c7e23a 100644 --- a/cpp/src/branch_and_bound/branch_and_bound.cpp +++ b/cpp/src/branch_and_bound/branch_and_bound.cpp @@ -1886,15 +1886,11 @@ lp_status_t branch_and_bound_t::solve_root_relaxation( root_crossover_settings, original_lp_.lower, original_lp_.upper, + exploration_stats_.start_time, basic_list, nonbasic_list, - crossover_vstatus_, - exploration_stats_.start_time); - if (refactor_status == TIME_LIMIT_RETURN) { - root_status = lp_status_t::TIME_LIMIT; - } else if (refactor_status == CONCURRENT_HALT_RETURN) { - root_status = lp_status_t::CONCURRENT_LIMIT; - } else if (refactor_status != 0) { + crossover_vstatus_); + if (refactor_status != 0) { settings_.log.printf("Failed to refactor basis. %d deficient columns.\n", refactor_status); assert(refactor_status == 0); root_status = lp_status_t::NUMERICAL_ISSUES; @@ -1906,15 +1902,6 @@ lp_status_t branch_and_bound_t::solve_root_relaxation( user_objective = root_crossover_soln_.user_objective; iter = root_crossover_soln_.iterations; solver_name = "Barrier/PDLP and Crossover"; - } else if (crossover_status == crossover_status_t::TIME_LIMIT || - toc(exploration_stats_.start_time) > settings_.time_limit) { - set_root_concurrent_halt(1); - root_status = root_status_future.get(); - set_root_concurrent_halt(0); - root_status = lp_status_t::TIME_LIMIT; - user_objective = root_relax_soln_.user_objective; - iter = root_relax_soln_.iterations; - solver_name = "Dual Simplex"; } else { root_status = root_status_future.get(); user_objective = root_relax_soln_.user_objective; @@ -1946,18 +1933,6 @@ lp_status_t branch_and_bound_t::solve_root_relaxation( return root_status; } -template -bool branch_and_bound_t::stop_for_time_limit(mip_solution_t& solution) -{ - const f_t elapsed = toc(exploration_stats_.start_time); - if (elapsed > settings_.time_limit) { - solver_status_ = mip_status_t::TIME_LIMIT; - set_final_solution(solution, root_objective_); - return true; - } - return false; -} - template mip_status_t branch_and_bound_t::solve(mip_solution_t& solution) { @@ -2029,9 +2004,12 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut if (root_status == lp_status_t::INFEASIBLE) { settings_.log.printf("MIP Infeasible\n"); - if (settings_.heuristic_preemption_callback != nullptr) { - settings_.heuristic_preemption_callback(); - } + // 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) { @@ -2148,7 +2126,6 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut // Score the cuts f_t score_start_time = tic(); cut_pool.score_cuts(root_relax_soln_.x); - if (stop_for_time_limit(solution)) { return solver_status_; } f_t score_time = toc(score_start_time); if (score_time > 1.0) { settings_.log.debug("Cut scoring time %.2f seconds\n", score_time); } // Get the best cuts from the cut pool @@ -2276,8 +2253,11 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut if (dual_phase2_time > 1.0) { settings_.log.debug("Dual phase2 time %.2f seconds\n", dual_phase2_time); } - - if (stop_for_time_limit(solution)) { return solver_status_; } + if (cut_status == dual::status_t::TIME_LIMIT) { + solver_status_ = mip_status_t::TIME_LIMIT; + set_final_solution(solution, root_objective_); + return solver_status_; + } if (cut_status != dual::status_t::OPTIMAL) { settings_.log.printf("Numerical issue at root node. Resolving from scratch\n"); @@ -2291,7 +2271,6 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut nonbasic_list, root_vstatus_, edge_norms_); - if (stop_for_time_limit(solution)) { return solver_status_; } if (scratch_status == lp_status_t::OPTIMAL) { // We recovered cut_status = convert_lp_status_to_dual_status(scratch_status); @@ -2372,11 +2351,6 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut set_uninitialized_steepest_edge_norms(original_lp_, basic_list, edge_norms_); pc_.resize(original_lp_.num_cols); - if (toc(exploration_stats_.start_time) >= settings_.time_limit) { - solver_status_ = mip_status_t::TIME_LIMIT; - set_final_solution(solution, root_objective_); - return solver_status_; - } { raft::common::nvtx::range scope_sb("BB::strong_branching"); strong_branching(original_problem_, diff --git a/cpp/src/cuts/cuts.cpp b/cpp/src/cuts/cuts.cpp index bbb4d1f15..ad1d34f7a 100644 --- a/cpp/src/cuts/cuts.cpp +++ b/cpp/src/cuts/cuts.cpp @@ -2646,7 +2646,7 @@ i_t remove_cuts(lp_problem_t& lp, basis_update.resize(lp.num_rows); i_t refactor_status = basis_update.refactor_basis( - lp.A, settings, lp.lower, lp.upper, basic_list, nonbasic_list, vstatus, start_time); + lp.A, settings, lp.lower, lp.upper, start_time, basic_list, nonbasic_list, vstatus); if (refactor_status == CONCURRENT_HALT_RETURN) { return CONCURRENT_HALT_RETURN; } if (refactor_status == TIME_LIMIT_RETURN) { return TIME_LIMIT_RETURN; } } diff --git a/cpp/src/dual_simplex/basis_solves.cpp b/cpp/src/dual_simplex/basis_solves.cpp index 1c17fc557..2189dd57b 100644 --- a/cpp/src/dual_simplex/basis_solves.cpp +++ b/cpp/src/dual_simplex/basis_solves.cpp @@ -159,14 +159,14 @@ template i_t factorize_basis(const csc_matrix_t& A, const simplex_solver_settings_t& settings, const std::vector& basic_list, + f_t start_time, csc_matrix_t& L, csc_matrix_t& U, std::vector& p, std::vector& pinv, std::vector& q, std::vector& deficient, - std::vector& slacks_needed, - f_t start_time) + std::vector& slacks_needed) { raft::common::nvtx::range scope("LU::factorize_basis"); const i_t m = basic_list.size(); @@ -875,14 +875,14 @@ template void get_basis_from_vstatus(int m, template int factorize_basis(const csc_matrix_t& A, const simplex_solver_settings_t& settings, const std::vector& basis_list, + double start_time, csc_matrix_t& L, csc_matrix_t& U, std::vector& p, std::vector& pinv, std::vector& q, std::vector& deficient, - std::vector& slacks_needed, - double start_time); + std::vector& slacks_needed); template int basis_repair(const csc_matrix_t& A, const simplex_solver_settings_t& settings, diff --git a/cpp/src/dual_simplex/basis_solves.hpp b/cpp/src/dual_simplex/basis_solves.hpp index 13227a832..3050e791e 100644 --- a/cpp/src/dual_simplex/basis_solves.hpp +++ b/cpp/src/dual_simplex/basis_solves.hpp @@ -30,14 +30,14 @@ template i_t factorize_basis(const csc_matrix_t& A, const simplex_solver_settings_t& settings, const std::vector& basis_list, + f_t start_time, csc_matrix_t& L, csc_matrix_t& U, std::vector& p, std::vector& pinv, std::vector& q, std::vector& deficient, - std::vector& slacks_need, - f_t start_time); + std::vector& slacks_need); // Repair the basis by bringing in slacks template diff --git a/cpp/src/dual_simplex/basis_updates.cpp b/cpp/src/dual_simplex/basis_updates.cpp index 5f8ef4934..7814d57e7 100644 --- a/cpp/src/dual_simplex/basis_updates.cpp +++ b/cpp/src/dual_simplex/basis_updates.cpp @@ -2262,10 +2262,10 @@ int basis_update_mpf_t::refactor_basis( const simplex_solver_settings_t& settings, const std::vector& lower, const std::vector& upper, + f_t start_time, std::vector& basic_list, std::vector& nonbasic_list, - std::vector& vstatus, - f_t start_time) + std::vector& vstatus) { raft::common::nvtx::range scope("LU::refactor_basis"); std::vector deficient; @@ -2277,14 +2277,14 @@ int basis_update_mpf_t::refactor_basis( i_t status = factorize_basis(A, settings, basic_list, + start_time, L0_, U0_, row_permutation_, inverse_row_permutation_, q, deficient, - slacks_needed, - start_time); + slacks_needed); if (status == CONCURRENT_HALT_RETURN) { return CONCURRENT_HALT_RETURN; } if (status == TIME_LIMIT_RETURN) { return TIME_LIMIT_RETURN; } if (status == -1) { @@ -2320,14 +2320,14 @@ int basis_update_mpf_t::refactor_basis( status = factorize_basis(A, settings, basic_list, + start_time, L0_, U0_, row_permutation_, inverse_row_permutation_, q, deficient, - slacks_needed, - start_time); + slacks_needed); if (status == CONCURRENT_HALT_RETURN) { return CONCURRENT_HALT_RETURN; } if (status == TIME_LIMIT_RETURN) { return TIME_LIMIT_RETURN; } if (status == -1) { diff --git a/cpp/src/dual_simplex/basis_updates.hpp b/cpp/src/dual_simplex/basis_updates.hpp index 6fd7b13f3..8c4c6da29 100644 --- a/cpp/src/dual_simplex/basis_updates.hpp +++ b/cpp/src/dual_simplex/basis_updates.hpp @@ -380,10 +380,10 @@ class basis_update_mpf_t { const simplex_solver_settings_t& settings, const std::vector& lower, const std::vector& upper, + f_t start_time, std::vector& basic_list, std::vector& nonbasic_list, - std::vector& vstatus, - f_t start_time); + std::vector& vstatus); void set_refactor_frequency(i_t new_frequency) { refactor_frequency_ = new_frequency; } diff --git a/cpp/src/dual_simplex/crossover.cpp b/cpp/src/dual_simplex/crossover.cpp index 626b15035..b044a8678 100644 --- a/cpp/src/dual_simplex/crossover.cpp +++ b/cpp/src/dual_simplex/crossover.cpp @@ -506,7 +506,7 @@ i_t dual_push(const lp_problem_t& lp, std::vector deficient; std::vector slacks_needed; i_t rank = factorize_basis( - lp.A, settings, basic_list, L, U, p, pinv, q, deficient, slacks_needed, start_time); + lp.A, settings, basic_list, start_time, L, U, p, pinv, q, deficient, slacks_needed); if (rank == CONCURRENT_HALT_RETURN) { return CONCURRENT_HALT_RETURN; } else if (rank < 0) { @@ -524,7 +524,7 @@ i_t dual_push(const lp_problem_t& lp, superbasic_list, vstatus); rank = factorize_basis( - lp.A, settings, basic_list, L, U, p, pinv, q, deficient, slacks_needed, start_time); + lp.A, settings, basic_list, start_time, L, U, p, pinv, q, deficient, slacks_needed); if (rank == CONCURRENT_HALT_RETURN) { return CONCURRENT_HALT_RETURN; } else if (rank < 0) { @@ -862,7 +862,7 @@ i_t primal_push(const lp_problem_t& lp, std::vector deficient; std::vector slacks_needed; i_t rank = factorize_basis( - lp.A, settings, basic_list, L, U, p, pinv, q, deficient, slacks_needed, start_time); + lp.A, settings, basic_list, start_time, L, U, p, pinv, q, deficient, slacks_needed); if (rank == CONCURRENT_HALT_RETURN) { return CONCURRENT_HALT_RETURN; } else if (rank < 0) { @@ -883,7 +883,7 @@ i_t primal_push(const lp_problem_t& lp, find_primal_superbasic_variables( lp, settings, solution, solution, vstatus, nonbasic_list, superbasic_list); rank = factorize_basis( - lp.A, settings, basic_list, L, U, p, pinv, q, deficient, slacks_needed, start_time); + lp.A, settings, basic_list, start_time, L, U, p, pinv, q, deficient, slacks_needed); if (rank == CONCURRENT_HALT_RETURN) { return CONCURRENT_HALT_RETURN; } else if (rank < 0) { @@ -1226,7 +1226,7 @@ crossover_status_t crossover(const lp_problem_t& lp, std::vector slacks_needed; rank = factorize_basis( - lp.A, settings, basic_list, L, U, p, pinv, q, deficient, slacks_needed, start_time); + lp.A, settings, basic_list, start_time, L, U, p, pinv, q, deficient, slacks_needed); if (rank < 0) { return return_to_status(rank); } if (rank != m) { settings.log.debug("Failed to factorize basis. rank %d m %d\n", rank, m); @@ -1241,7 +1241,7 @@ crossover_status_t crossover(const lp_problem_t& lp, superbasic_list, vstatus); rank = factorize_basis( - lp.A, settings, basic_list, L, U, p, pinv, q, deficient, slacks_needed, start_time); + lp.A, settings, basic_list, start_time, L, U, p, pinv, q, deficient, slacks_needed); if (rank == CONCURRENT_HALT_RETURN) { return crossover_status_t::CONCURRENT_LIMIT; } else if (rank < 0) { @@ -1397,7 +1397,7 @@ crossover_status_t crossover(const lp_problem_t& lp, superbasic_list.clear(); get_basis_from_vstatus(m, vstatus, basic_list, nonbasic_list, superbasic_list); rank = factorize_basis( - lp.A, settings, basic_list, L, U, p, pinv, q, deficient, slacks_needed, start_time); + lp.A, settings, basic_list, start_time, L, U, p, pinv, q, deficient, slacks_needed); if (rank < 0) { return return_to_status(rank); } else if (rank != m) { @@ -1413,7 +1413,7 @@ crossover_status_t crossover(const lp_problem_t& lp, superbasic_list, vstatus); rank = factorize_basis( - lp.A, settings, basic_list, L, U, p, pinv, q, deficient, slacks_needed, start_time); + lp.A, settings, basic_list, start_time, L, U, p, pinv, q, deficient, slacks_needed); if (rank < 0) { settings.log.printf("Failed to factorize basis after repair. rank %d m %d\n", rank, m); return return_to_status(rank); diff --git a/cpp/src/dual_simplex/phase2.cpp b/cpp/src/dual_simplex/phase2.cpp index 25d1c5fbe..5de12cc8c 100644 --- a/cpp/src/dual_simplex/phase2.cpp +++ b/cpp/src/dual_simplex/phase2.cpp @@ -2505,7 +2505,7 @@ dual::status_t dual_phase2_with_advanced_basis(i_t phase, assert(nonbasic_list.size() == n - m); i_t refactor_status = ft.refactor_basis( - lp.A, settings, lp.lower, lp.upper, basic_list, nonbasic_list, vstatus, start_time); + lp.A, settings, lp.lower, lp.upper, start_time, basic_list, nonbasic_list, vstatus); if (refactor_status == CONCURRENT_HALT_RETURN) { return dual::status_t::CONCURRENT_LIMIT; } if (refactor_status == TIME_LIMIT_RETURN) { return dual::status_t::TIME_LIMIT; } if (refactor_status > 0) { return dual::status_t::NUMERICAL; } @@ -3373,7 +3373,7 @@ dual::status_t dual_phase2_with_advanced_basis(i_t phase, num_refactors++; bool should_recompute_x = false; i_t refactor_status = ft.refactor_basis( - lp.A, settings, lp.lower, lp.upper, basic_list, nonbasic_list, vstatus, start_time); + lp.A, settings, lp.lower, lp.upper, start_time, basic_list, nonbasic_list, vstatus); if (refactor_status == CONCURRENT_HALT_RETURN) { return dual::status_t::CONCURRENT_LIMIT; } if (refactor_status == TIME_LIMIT_RETURN) { return dual::status_t::TIME_LIMIT; } if (refactor_status > 0) { @@ -3384,7 +3384,7 @@ dual::status_t dual_phase2_with_advanced_basis(i_t phase, i_t deficient_size = 0; while (true) { deficient_size = ft.refactor_basis( - lp.A, settings, lp.lower, lp.upper, basic_list, nonbasic_list, vstatus, start_time); + lp.A, settings, lp.lower, lp.upper, start_time, basic_list, nonbasic_list, vstatus); if (deficient_size == CONCURRENT_HALT_RETURN) { return dual::status_t::CONCURRENT_LIMIT; } diff --git a/cpp/src/dual_simplex/primal.cpp b/cpp/src/dual_simplex/primal.cpp index 2d0944542..628fadcbe 100644 --- a/cpp/src/dual_simplex/primal.cpp +++ b/cpp/src/dual_simplex/primal.cpp @@ -295,7 +295,7 @@ primal::status_t primal_phase2(i_t phase, std::vector deficient; std::vector slacks_needed; i_t rank = factorize_basis( - lp.A, settings, basic_list, L, U, p, pinv, q, deficient, slacks_needed, start_time); + lp.A, settings, basic_list, start_time, L, U, p, pinv, q, deficient, slacks_needed); if (rank == CONCURRENT_HALT_RETURN) { return primal::status_t::CONCURRENT_LIMIT; } else if (rank == TIME_LIMIT_RETURN) { @@ -316,7 +316,7 @@ primal::status_t primal_phase2(i_t phase, superbasic_list, vstatus); rank = factorize_basis( - lp.A, settings, basic_list, L, U, p, pinv, q, deficient, slacks_needed, start_time); + lp.A, settings, basic_list, start_time, L, U, p, pinv, q, deficient, slacks_needed); if (rank == CONCURRENT_HALT_RETURN) { return primal::status_t::CONCURRENT_LIMIT; } else if (rank == TIME_LIMIT_RETURN) { From 0c54ecfb3045025bdfb6325fddccf40e8f7532e9 Mon Sep 17 00:00:00 2001 From: akifcorduk Date: Wed, 18 Feb 2026 22:38:23 -0800 Subject: [PATCH 12/15] fix merge conflicts --- cpp/src/dual_simplex/crossover.cpp | 9 ++++++--- cpp/src/dual_simplex/primal.cpp | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/cpp/src/dual_simplex/crossover.cpp b/cpp/src/dual_simplex/crossover.cpp index 931f515e7..988c9c50a 100644 --- a/cpp/src/dual_simplex/crossover.cpp +++ b/cpp/src/dual_simplex/crossover.cpp @@ -533,7 +533,8 @@ i_t dual_push(const lp_problem_t& lp, basic_list, nonbasic_list, superbasic_list, - vstatus); + vstatus, + work_estimate); rank = factorize_basis(lp.A, settings, basic_list, @@ -1293,7 +1294,8 @@ crossover_status_t crossover(const lp_problem_t& lp, basic_list, nonbasic_list, superbasic_list, - vstatus); + vstatus, + work_estimate); rank = factorize_basis(lp.A, settings, basic_list, @@ -1485,7 +1487,8 @@ crossover_status_t crossover(const lp_problem_t& lp, basic_list, nonbasic_list, superbasic_list, - vstatus); + vstatus, + work_estimate); rank = factorize_basis(lp.A, settings, basic_list, diff --git a/cpp/src/dual_simplex/primal.cpp b/cpp/src/dual_simplex/primal.cpp index b50e674c2..d4c6743dc 100644 --- a/cpp/src/dual_simplex/primal.cpp +++ b/cpp/src/dual_simplex/primal.cpp @@ -325,7 +325,8 @@ primal::status_t primal_phase2(i_t phase, basic_list, nonbasic_list, superbasic_list, - vstatus); + vstatus, + work_estimate); rank = factorize_basis(lp.A, settings, basic_list, From fc414e748f06a7058ff91c77affa69648a08e0d3 Mon Sep 17 00:00:00 2001 From: akifcorduk Date: Wed, 18 Feb 2026 23:06:59 -0800 Subject: [PATCH 13/15] revert cmake comment --- cpp/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index aa377b315..6a684be70 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -153,7 +153,7 @@ if(CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL 12.9 AND CMAKE_CUDA_COMPILE endif() list(APPEND CUOPT_CUDA_FLAGS -fopenmp) -# Add parallel compilation flags if PARALLEL_LEVEL is set +# Add jobserver flags for parallel compilation if PARALLEL_LEVEL is set if(PARALLEL_LEVEL AND NOT "${PARALLEL_LEVEL}" STREQUAL "") message(STATUS "Enabling nvcc parallel compilation support") list(APPEND CUOPT_CUDA_FLAGS --threads=0 --split-compile=0) From 84e344a79a5329d978fe1b10e1fa04f7cd43632a Mon Sep 17 00:00:00 2001 From: akifcorduk Date: Thu, 19 Feb 2026 00:23:52 -0800 Subject: [PATCH 14/15] fix rins assert race condition --- cpp/src/mip_heuristics/diversity/lns/rins.cu | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/cpp/src/mip_heuristics/diversity/lns/rins.cu b/cpp/src/mip_heuristics/diversity/lns/rins.cu index baa2b6d2f..7fd8533f8 100644 --- a/cpp/src/mip_heuristics/diversity/lns/rins.cu +++ b/cpp/src/mip_heuristics/diversity/lns/rins.cu @@ -67,8 +67,12 @@ void rins_t::node_callback(const std::vector& solution, f_t objec // opportunistic early test w/ atomic to avoid having to take the lock if (!rins_thread->cpu_thread_done) return; std::lock_guard lock(rins_mutex); - if (rins_thread->cpu_thread_done && dm.population.current_size() > 0 && - dm.population.is_feasible()) { + bool population_ready = false; + if (rins_thread->cpu_thread_done) { + std::lock_guard pop_lock(dm.population.write_mutex); + population_ready = dm.population.current_size() > 0 && dm.population.is_feasible(); + } + if (population_ready) { lp_optimal_solution = solution; rins_thread->start_cpu_solver(); } @@ -99,8 +103,6 @@ void rins_t::run_rins() { if (total_calls == 0) RAFT_CUDA_TRY(cudaSetDevice(context.handle_ptr->get_device())); - if (!dm.population.is_feasible()) return; - cuopt_assert(lp_optimal_solution.size() == problem_copy->n_variables, "Assignment size mismatch"); cuopt_assert(problem_copy->handle_ptr == &rins_handle, "Handle mismatch"); // Do not make assertions based on problem_ptr. The original problem may have been modified within @@ -111,13 +113,14 @@ void rins_t::run_rins() // "Problem size mismatch"); // cuopt_assert(problem_copy->n_binary_vars == problem_ptr->n_binary_vars, "Problem size // mismatch"); - cuopt_assert(dm.population.current_size() > 0, "No solutions in population"); solution_t best_sol(*problem_copy); rins_handle.sync_stream(); // copy the best from the population into a solution_t in the RINS stream { std::lock_guard lock(dm.population.write_mutex); + if (!dm.population.is_feasible()) return; + cuopt_assert(dm.population.current_size() > 0, "No solutions in population"); auto& best_feasible_ref = dm.population.best_feasible(); cuopt_assert(best_feasible_ref.assignment.size() == best_sol.assignment.size(), "Assignment size mismatch"); From b58edfc17e65ba74cefa36ec95b275b001cc0f72 Mon Sep 17 00:00:00 2001 From: akifcorduk Date: Thu, 19 Feb 2026 02:28:13 -0800 Subject: [PATCH 15/15] fix stale values on crushing the solution again. there might be some zero slack values left in the previous solution --- cpp/src/dual_simplex/presolve.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cpp/src/dual_simplex/presolve.cpp b/cpp/src/dual_simplex/presolve.cpp index f4602ac56..b9ee41951 100644 --- a/cpp/src/dual_simplex/presolve.cpp +++ b/cpp/src/dual_simplex/presolve.cpp @@ -1163,7 +1163,9 @@ void crush_primal_solution(const user_problem_t& user_problem, const std::vector& new_slacks, std::vector& solution) { - solution.resize(problem.num_cols, 0.0); + // Re-crush can be called with a reused output vector; make sure all entries, + // including previously added slacks, are reset before writing new values. + solution.assign(problem.num_cols, 0.0); for (i_t j = 0; j < user_problem.num_cols; j++) { solution[j] = user_solution[j]; } @@ -1202,7 +1204,8 @@ void crush_primal_solution_with_slack(const user_problem_t& user_probl const std::vector& new_slacks, std::vector& solution) { - solution.resize(problem.num_cols, 0.0); + // Re-crush can be called with a reused output vector; clear stale entries first. + solution.assign(problem.num_cols, 0.0); for (i_t j = 0; j < user_problem.num_cols; j++) { solution[j] = user_solution[j]; }