From 90dc7d10a3ca49adcd4751cd36b434645d00fde4 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Mon, 6 Oct 2025 16:01:56 +0000 Subject: [PATCH 01/87] initial --- cpp/src/mip/diversity/diversity_manager.cu | 106 +++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index b11f98e10..d599d467e 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -434,11 +434,117 @@ solution_t diversity_manager_t::run_solver() if (check_b_b_preemption()) { return population.best_feasible(); } + auto new_sol_vector = population.get_external_solutions(); + population.add_solutions_from_vec(std::move(new_sol_vector)); + if (context.settings.benchmark_info_ptr != nullptr) { context.settings.benchmark_info_ptr->objective_of_initial_population = population.best_feasible().get_user_objective(); } + // solutions may have been generated by b&b or cpufj already. take a look and RINS if so + // RINS + if (population.current_size() > 0) { + auto best_sol = population.best_feasible(); + printf("POPULATION FEASIBLE!!!! initial solution objective: %g\n", + best_sol.get_user_objective()); + + rmm::device_uvector vars_to_fix(problem_ptr->n_integer_vars, + problem_ptr->handle_ptr->get_stream()); + auto end = thrust::copy_if(problem_ptr->handle_ptr->get_thrust_policy(), + thrust::make_counting_iterator(i_t(0)), + thrust::make_counting_iterator(problem_ptr->n_integer_vars), + vars_to_fix.begin(), + [lpopt = lp_optimal_solution.data(), + incumbent = best_sol.assignment.data(), + pb = problem_ptr->view()] __device__(i_t var_idx) { + return pb.integer_equal(lpopt[var_idx], incumbent[var_idx]); + }); + vars_to_fix.resize(end - vars_to_fix.begin(), problem_ptr->handle_ptr->get_stream()); + f_t fractional_ratio = (f_t)(vars_to_fix.size()) / (f_t)problem_ptr->n_integer_vars; + + // sort by fixing priority + thrust::sort(problem_ptr->handle_ptr->get_thrust_policy(), + vars_to_fix.begin(), + vars_to_fix.end(), + [assignment = best_sol.assignment.data(), pb = problem_ptr->view()] __device__( + i_t var_1, i_t var_2) { + return get_fractionality_of_val(assignment[var_1]) < + get_fractionality_of_val(assignment[var_2]); + }); + + // fix n first according to fractional ratio + f_t rins_ratio = 0.5f; + i_t n_to_fix = std::max((int)(vars_to_fix.size() * rins_ratio), 0); + vars_to_fix.resize(n_to_fix, problem_ptr->handle_ptr->get_stream()); + thrust::sort( + problem_ptr->handle_ptr->get_thrust_policy(), vars_to_fix.begin(), vars_to_fix.end()); + + CUOPT_LOG_DEBUG("Running RINS"); + CUOPT_LOG_DEBUG("Fixed %d variables", vars_to_fix.size()); + CUOPT_LOG_DEBUG("Fractional ratio %g%%", fractional_ratio * 100); + + auto [fixed_problem, fixed_assignment, variable_map] = best_sol.fix_variables(vars_to_fix); + CUOPT_LOG_DEBUG( + "new var count %d var_count %d", fixed_problem.n_variables, problem_ptr->n_integer_vars); + // limit remaining fractional to their integer bounds + thrust::for_each( + problem_ptr->handle_ptr->get_thrust_policy(), + thrust::make_counting_iterator(i_t(0)), + thrust::make_counting_iterator(fixed_problem.n_variables), + [assignment = fixed_assignment.data(), pb = fixed_problem.view()] __device__(i_t var_idx) { + if (pb.is_integer_var(var_idx) && !pb.is_integer(assignment[var_idx])) { + get_lower(pb.variable_bounds[var_idx]) = std::floor(assignment[var_idx]); + get_upper(pb.variable_bounds[var_idx]) = std::ceil(assignment[var_idx]); + } + }); + fixed_problem.presolve_data.reset_additional_vars(fixed_problem, best_sol.handle_ptr); + fixed_problem.presolve_data.initialize_var_mapping(fixed_problem, best_sol.handle_ptr); + trivial_presolve(fixed_problem); + fixed_problem.check_problem_representation(true); + + // run sub-mip + namespace dual_simplex = cuopt::linear_programming::dual_simplex; + dual_simplex::user_problem_t branch_and_bound_problem(best_sol.handle_ptr); + dual_simplex::simplex_solver_settings_t branch_and_bound_settings; + dual_simplex::mip_solution_t branch_and_bound_solution(1); + dual_simplex::mip_status_t branch_and_bound_status = dual_simplex::mip_status_t::UNSET; + fixed_problem.get_host_user_problem(branch_and_bound_problem); + branch_and_bound_solution.resize(branch_and_bound_problem.num_cols); + // Fill in the settings for branch and bound + // branch_and_bound_settings.time_limit = 20; + branch_and_bound_settings.print_presolve_stats = false; + branch_and_bound_settings.absolute_mip_gap_tol = context.settings.tolerances.absolute_mip_gap; + branch_and_bound_settings.relative_mip_gap_tol = context.settings.tolerances.relative_mip_gap; + branch_and_bound_settings.integer_tol = context.settings.tolerances.integrality_tolerance; + branch_and_bound_settings.solution_callback = [this](std::vector& solution, + f_t objective) {}; + dual_simplex::branch_and_bound_t branch_and_bound(branch_and_bound_problem, + branch_and_bound_settings); + // branch_and_bound.set_initial_guess(cuopt::host_copy(fixed_assignment)); + branch_and_bound_status = branch_and_bound.solve(branch_and_bound_solution); + if (!std::isnan(branch_and_bound_solution.objective)) { + cuopt_assert(fixed_assignment.size() == branch_and_bound_solution.x.size(), + "Assignment size mismatch"); + CUOPT_LOG_DEBUG("Sub-MIP solution found. Objective %.16e. Status %d", + branch_and_bound_solution.objective, + int(branch_and_bound_status)); + raft::copy(fixed_assignment.data(), + branch_and_bound_solution.x.data(), + fixed_assignment.size(), + best_sol.handle_ptr->get_stream()); + } + best_sol.handle_ptr->sync_stream(); + // unfix the assignment on given result no matter if it is feasible + best_sol.unfix_variables(fixed_assignment, variable_map); + // cuopt_assert(solution.test_number_all_integer(), "All must be integers after offspring"); + best_sol.compute_feasibility(); + CUOPT_LOG_DEBUG("RINS Solution: feasible: %d, objective: %g", + best_sol.get_feasible(), + best_sol.get_user_objective()); + // initial_sol_vector.push_back(std::move(best_sol)); + } + if (fj_only_run) { solution_t sol(*problem_ptr); run_fj_alone(sol); From 73189ca8eefc13eb3f9b5d7e4e39ea36a7aee8fa Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Tue, 7 Oct 2025 11:49:58 +0000 Subject: [PATCH 02/87] tweaks --- cpp/src/mip/diversity/diversity_manager.cu | 35 ++++++++++++++-------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index d599d467e..592472468 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -442,6 +442,20 @@ solution_t diversity_manager_t::run_solver() population.best_feasible().get_user_objective(); } + if (fj_only_run) { + solution_t sol(*problem_ptr); + run_fj_alone(sol); + return sol; + } + + auto sol = generate_solution(timer.remaining_time(), false); + population.add_solution(std::move(solution_t(sol))); + if (timer.check_time_limit()) { + auto new_sol_vector = population.get_external_solutions(); + population.add_solutions_from_vec(std::move(new_sol_vector)); + return population.best_feasible(); + } + // solutions may have been generated by b&b or cpufj already. take a look and RINS if so // RINS if (population.current_size() > 0) { @@ -480,6 +494,8 @@ solution_t diversity_manager_t::run_solver() thrust::sort( problem_ptr->handle_ptr->get_thrust_policy(), vars_to_fix.begin(), vars_to_fix.end()); + if (n_to_fix == 0) exit(0); + CUOPT_LOG_DEBUG("Running RINS"); CUOPT_LOG_DEBUG("Fixed %d variables", vars_to_fix.size()); CUOPT_LOG_DEBUG("Fractional ratio %g%%", fractional_ratio * 100); @@ -487,6 +503,11 @@ solution_t diversity_manager_t::run_solver() auto [fixed_problem, fixed_assignment, variable_map] = best_sol.fix_variables(vars_to_fix); CUOPT_LOG_DEBUG( "new var count %d var_count %d", fixed_problem.n_variables, problem_ptr->n_integer_vars); + + f_t objective_cut = best_sol.get_objective() - + std::max(std::abs(0.001 * best_sol.get_objective()), OBJECTIVE_EPSILON); + fixed_problem.add_cutting_plane_at_objective(objective_cut); + // limit remaining fractional to their integer bounds thrust::for_each( problem_ptr->handle_ptr->get_thrust_policy(), @@ -544,20 +565,8 @@ solution_t diversity_manager_t::run_solver() best_sol.get_user_objective()); // initial_sol_vector.push_back(std::move(best_sol)); } + exit(0); - if (fj_only_run) { - solution_t sol(*problem_ptr); - run_fj_alone(sol); - return sol; - } - - auto sol = generate_solution(timer.remaining_time(), false); - population.add_solution(std::move(solution_t(sol))); - if (timer.check_time_limit()) { - auto new_sol_vector = population.get_external_solutions(); - population.add_solutions_from_vec(std::move(new_sol_vector)); - return population.best_feasible(); - } run_fp_alone(sol); population.update_weights(); From 8a2b91456c2f1c5c5c2999d16b134baac94d92a7 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Wed, 8 Oct 2025 09:52:04 +0000 Subject: [PATCH 03/87] added rins class and callback --- cpp/src/dual_simplex/branch_and_bound.cpp | 4 ++ .../dual_simplex/simplex_solver_settings.hpp | 1 + cpp/src/mip/diversity/diversity_manager.cu | 1 + cpp/src/mip/diversity/diversity_manager.cuh | 2 + cpp/src/mip/diversity/lns/rins.cu | 50 ++++++++++++++ cpp/src/mip/diversity/lns/rins.cuh | 67 +++++++++++++++++++ cpp/src/mip/solver.cu | 11 +++ 7 files changed, 136 insertions(+) create mode 100644 cpp/src/mip/diversity/lns/rins.cu create mode 100644 cpp/src/mip/diversity/lns/rins.cuh diff --git a/cpp/src/dual_simplex/branch_and_bound.cpp b/cpp/src/dual_simplex/branch_and_bound.cpp index 2ce3ee0b4..34426441b 100644 --- a/cpp/src/dual_simplex/branch_and_bound.cpp +++ b/cpp/src/dual_simplex/branch_and_bound.cpp @@ -637,6 +637,10 @@ node_status_t branch_and_bound_t::solve_node(search_tree_t& search_tree.graphviz_node(log, node_ptr, "lower bound", leaf_objective); pc_.update_pseudo_costs(node_ptr, leaf_objective); + if (settings_.node_processed_callback != nullptr) { + settings_.node_processed_callback(leaf_solution.x, leaf_objective); + } + if (leaf_num_fractional == 0) { // Found a integer feasible solution add_feasible_solution(leaf_objective, leaf_solution.x, node_ptr->depth, thread_type); diff --git a/cpp/src/dual_simplex/simplex_solver_settings.hpp b/cpp/src/dual_simplex/simplex_solver_settings.hpp index 59e6dc7bb..fa643fdc8 100644 --- a/cpp/src/dual_simplex/simplex_solver_settings.hpp +++ b/cpp/src/dual_simplex/simplex_solver_settings.hpp @@ -144,6 +144,7 @@ struct simplex_solver_settings_t { i_t num_diving_threads; // number of threads dedicated to diving i_t inside_mip; // 0 if outside MIP, 1 if inside MIP at root node, 2 if inside MIP at leaf node std::function&, f_t)> solution_callback; + std::function&, f_t)> node_processed_callback; std::function heuristic_preemption_callback; std::function&, std::vector&, f_t)> set_simplex_solution_callback; mutable logger_t log; diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index 592472468..712eb8e6a 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -55,6 +55,7 @@ diversity_manager_t::diversity_manager_t(mip_solver_context_tn_constraints, context.problem_ptr->handle_ptr->get_stream()), ls(context, lp_optimal_solution), + rins(context, *this), timer(diversity_config.default_time_limit), bound_prop_recombiner(context, context.problem_ptr->n_variables, diff --git a/cpp/src/mip/diversity/diversity_manager.cuh b/cpp/src/mip/diversity/diversity_manager.cuh index 8ab644f3a..6ef3c5cef 100644 --- a/cpp/src/mip/diversity/diversity_manager.cuh +++ b/cpp/src/mip/diversity/diversity_manager.cuh @@ -31,6 +31,7 @@ #include #include +#include #include #include #include @@ -94,6 +95,7 @@ class diversity_manager_t { rmm::device_uvector lp_dual_optimal_solution; std::atomic simplex_solution_exists{false}; local_search_t ls; + rins_t rins; cuopt::timer_t timer; bound_prop_recombiner_t bound_prop_recombiner; fp_recombiner_t fp_recombiner; diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu new file mode 100644 index 000000000..6b6d0d005 --- /dev/null +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -0,0 +1,50 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights + * reserved. SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +namespace cuopt::linear_programming::detail { + +template +rins_t::rins_t(std::string const& name_, + mip_solver_context_t& context_, + diversity_manager_t& dm_, + rins_settings_t settings_) + : context(context_), problem_ptr(context.problem_ptr), dm(dm_), settings(settings_) +{ + frac = settings.default_frac; +} + +template +void rins_t::node_callback(const std::vector& solution, f_t objective) +{ + total_calls++; + printf( + "-------- node processed w/ solution %d and objective %e\n", (int)solution.size(), objective); +} + +#if MIP_INSTANTIATE_FLOAT +template class rins_t; +#endif + +#if MIP_INSTANTIATE_DOUBLE +template class rins_t; +#endif + +} // namespace cuopt::linear_programming::detail diff --git a/cpp/src/mip/diversity/lns/rins.cuh b/cpp/src/mip/diversity/lns/rins.cuh new file mode 100644 index 000000000..c7b452b70 --- /dev/null +++ b/cpp/src/mip/diversity/lns/rins.cuh @@ -0,0 +1,67 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights + * reserved. SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace cuopt::linear_programming::detail { + +// forward declare +template +class diversity_manager_t; + +template +struct rins_settings_t { + i_t node_freq = 10; + f_t min_frac = 0.3; + f_t max_frac = 0.8; + f_t default_frac = 0.5; + f_t min_time_limit = 1.; + f_t max_time_limit = 20.; + f_t default_time_limit = 4.; +}; + +template +class rins_t { + public: + rins_t(mip_solver_context_t& context, + diversity_manager_t& dm, + rins_settings_t settings = rins_settings_t()); + + void node_callback(const std::vector& solution, f_t objective); + + mip_solver_context_t& context; + problem_t* problem_ptr; + diversity_manager_t& dm; + rins_settings_t settings; + + f_t frac{0.5}; + + i_t total_calls{0}; + i_t total_success{0}; +}; + +} // namespace cuopt::linear_programming::detail diff --git a/cpp/src/mip/solver.cu b/cpp/src/mip/solver.cu index 0114882b0..824b39f54 100644 --- a/cpp/src/mip/solver.cu +++ b/cpp/src/mip/solver.cu @@ -83,6 +83,11 @@ struct branch_and_bound_solution_helper_t { dm->set_simplex_solution(solution, dual_solution, objective); } + void node_processed_callback(std::vector& solution, f_t objective) + { + dm->rins.node_callback(solution, objective); + } + void preempt_heuristic_solver() { dm->population.preempt_heuristic_solver(); } diversity_manager_t* dm; dual_simplex::simplex_solver_settings_t& settings_; @@ -201,6 +206,12 @@ solution_t mip_solver_t::run_solver() std::placeholders::_2, std::placeholders::_3); + branch_and_bound_settings.node_processed_callback = + std::bind(&branch_and_bound_solution_helper_t::node_processed_callback, + &solution_helper, + std::placeholders::_1, + std::placeholders::_2); + // Create the branch and bound object branch_and_bound = std::make_unique>( branch_and_bound_problem, branch_and_bound_settings); From 22764d8525a6e724214c4e06a3fff98effc8f87d Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Wed, 8 Oct 2025 17:54:40 +0000 Subject: [PATCH 04/87] initial working --- cpp/src/dual_simplex/branch_and_bound.cpp | 17 +- cpp/src/dual_simplex/branch_and_bound.hpp | 2 +- cpp/src/dual_simplex/logger.hpp | 5 +- cpp/src/mip/CMakeLists.txt | 1 + cpp/src/mip/diversity/diversity_manager.cu | 111 -------- cpp/src/mip/diversity/lns/rins.cu | 285 ++++++++++++++++++++- cpp/src/mip/diversity/lns/rins.cuh | 63 ++++- cpp/src/mip/solver.cu | 4 +- 8 files changed, 350 insertions(+), 138 deletions(-) diff --git a/cpp/src/dual_simplex/branch_and_bound.cpp b/cpp/src/dual_simplex/branch_and_bound.cpp index 34426441b..6005dc003 100644 --- a/cpp/src/dual_simplex/branch_and_bound.cpp +++ b/cpp/src/dual_simplex/branch_and_bound.cpp @@ -638,7 +638,9 @@ node_status_t branch_and_bound_t::solve_node(search_tree_t& pc_.update_pseudo_costs(node_ptr, leaf_objective); if (settings_.node_processed_callback != nullptr) { - settings_.node_processed_callback(leaf_solution.x, leaf_objective); + std::vector original_x; + uncrush_primal_solution(original_problem_, original_lp_, leaf_solution.x, original_x); + settings_.node_processed_callback(original_x, leaf_objective); } if (leaf_num_fractional == 0) { @@ -1022,13 +1024,16 @@ void branch_and_bound_t::diving_thread(lp_problem_t& leaf_pr } template -mip_status_t branch_and_bound_t::solve(mip_solution_t& solution) +mip_status_t branch_and_bound_t::solve(mip_solution_t& solution, + std::string log_prefix) { logger_t log; - log.log = false; - status_ = mip_exploration_status_t::UNSET; - stats_.nodes_unexplored = 0; - stats_.nodes_explored = 0; + log.log = false; + log.log_prefix = log_prefix; + settings_.log.log_prefix = log_prefix; + status_ = mip_exploration_status_t::UNSET; + stats_.nodes_unexplored = 0; + stats_.nodes_explored = 0; if (guess_.size() != 0) { std::vector crushed_guess; diff --git a/cpp/src/dual_simplex/branch_and_bound.hpp b/cpp/src/dual_simplex/branch_and_bound.hpp index 23fb9eb7f..c45ca540f 100644 --- a/cpp/src/dual_simplex/branch_and_bound.hpp +++ b/cpp/src/dual_simplex/branch_and_bound.hpp @@ -142,7 +142,7 @@ class branch_and_bound_t { i_t get_heap_size(); // The main entry routine. Returns the solver status and populates solution with the incumbent. - mip_status_t solve(mip_solution_t& solution); + mip_status_t solve(mip_solution_t& solution, std::string log_prefix = ""); private: const user_problem_t& original_problem_; diff --git a/cpp/src/dual_simplex/logger.hpp b/cpp/src/dual_simplex/logger.hpp index 11d8ff936..cff54264f 100644 --- a/cpp/src/dual_simplex/logger.hpp +++ b/cpp/src/dual_simplex/logger.hpp @@ -71,7 +71,7 @@ class logger_t { size_t len = strlen(buffer); if (len > 0 && buffer[len - 1] == '\n') { buffer[len - 1] = '\0'; } - CUOPT_LOG_INFO(buffer); + CUOPT_LOG_INFO("%s%s", log_prefix.c_str(), buffer); } #else if (log_to_console) { @@ -105,7 +105,7 @@ class logger_t { size_t len = strlen(buffer); if (len > 0 && buffer[len - 1] == '\n') { buffer[len - 1] = '\0'; } - CUOPT_LOG_TRACE(buffer); + CUOPT_LOG_TRACE("%s%s", log_prefix.c_str(), buffer); } #else if (log_to_console) { @@ -128,6 +128,7 @@ class logger_t { bool log; bool log_to_console; + std::string log_prefix; private: bool log_to_file; diff --git a/cpp/src/mip/CMakeLists.txt b/cpp/src/mip/CMakeLists.txt index 2d11a8dcb..48f5f85b1 100644 --- a/cpp/src/mip/CMakeLists.txt +++ b/cpp/src/mip/CMakeLists.txt @@ -33,6 +33,7 @@ set(MIP_NON_LP_FILES ${CMAKE_CURRENT_SOURCE_DIR}/diversity/diversity_manager.cu ${CMAKE_CURRENT_SOURCE_DIR}/diversity/multi_armed_bandit.cu ${CMAKE_CURRENT_SOURCE_DIR}/diversity/population.cu + ${CMAKE_CURRENT_SOURCE_DIR}/diversity/lns/rins.cu ${CMAKE_CURRENT_SOURCE_DIR}/relaxed_lp/relaxed_lp.cu ${CMAKE_CURRENT_SOURCE_DIR}/local_search/local_search.cu ${CMAKE_CURRENT_SOURCE_DIR}/local_search/rounding/bounds_repair.cu diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index 712eb8e6a..abac62db4 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -457,117 +457,6 @@ solution_t diversity_manager_t::run_solver() return population.best_feasible(); } - // solutions may have been generated by b&b or cpufj already. take a look and RINS if so - // RINS - if (population.current_size() > 0) { - auto best_sol = population.best_feasible(); - printf("POPULATION FEASIBLE!!!! initial solution objective: %g\n", - best_sol.get_user_objective()); - - rmm::device_uvector vars_to_fix(problem_ptr->n_integer_vars, - problem_ptr->handle_ptr->get_stream()); - auto end = thrust::copy_if(problem_ptr->handle_ptr->get_thrust_policy(), - thrust::make_counting_iterator(i_t(0)), - thrust::make_counting_iterator(problem_ptr->n_integer_vars), - vars_to_fix.begin(), - [lpopt = lp_optimal_solution.data(), - incumbent = best_sol.assignment.data(), - pb = problem_ptr->view()] __device__(i_t var_idx) { - return pb.integer_equal(lpopt[var_idx], incumbent[var_idx]); - }); - vars_to_fix.resize(end - vars_to_fix.begin(), problem_ptr->handle_ptr->get_stream()); - f_t fractional_ratio = (f_t)(vars_to_fix.size()) / (f_t)problem_ptr->n_integer_vars; - - // sort by fixing priority - thrust::sort(problem_ptr->handle_ptr->get_thrust_policy(), - vars_to_fix.begin(), - vars_to_fix.end(), - [assignment = best_sol.assignment.data(), pb = problem_ptr->view()] __device__( - i_t var_1, i_t var_2) { - return get_fractionality_of_val(assignment[var_1]) < - get_fractionality_of_val(assignment[var_2]); - }); - - // fix n first according to fractional ratio - f_t rins_ratio = 0.5f; - i_t n_to_fix = std::max((int)(vars_to_fix.size() * rins_ratio), 0); - vars_to_fix.resize(n_to_fix, problem_ptr->handle_ptr->get_stream()); - thrust::sort( - problem_ptr->handle_ptr->get_thrust_policy(), vars_to_fix.begin(), vars_to_fix.end()); - - if (n_to_fix == 0) exit(0); - - CUOPT_LOG_DEBUG("Running RINS"); - CUOPT_LOG_DEBUG("Fixed %d variables", vars_to_fix.size()); - CUOPT_LOG_DEBUG("Fractional ratio %g%%", fractional_ratio * 100); - - auto [fixed_problem, fixed_assignment, variable_map] = best_sol.fix_variables(vars_to_fix); - CUOPT_LOG_DEBUG( - "new var count %d var_count %d", fixed_problem.n_variables, problem_ptr->n_integer_vars); - - f_t objective_cut = best_sol.get_objective() - - std::max(std::abs(0.001 * best_sol.get_objective()), OBJECTIVE_EPSILON); - fixed_problem.add_cutting_plane_at_objective(objective_cut); - - // limit remaining fractional to their integer bounds - thrust::for_each( - problem_ptr->handle_ptr->get_thrust_policy(), - thrust::make_counting_iterator(i_t(0)), - thrust::make_counting_iterator(fixed_problem.n_variables), - [assignment = fixed_assignment.data(), pb = fixed_problem.view()] __device__(i_t var_idx) { - if (pb.is_integer_var(var_idx) && !pb.is_integer(assignment[var_idx])) { - get_lower(pb.variable_bounds[var_idx]) = std::floor(assignment[var_idx]); - get_upper(pb.variable_bounds[var_idx]) = std::ceil(assignment[var_idx]); - } - }); - fixed_problem.presolve_data.reset_additional_vars(fixed_problem, best_sol.handle_ptr); - fixed_problem.presolve_data.initialize_var_mapping(fixed_problem, best_sol.handle_ptr); - trivial_presolve(fixed_problem); - fixed_problem.check_problem_representation(true); - - // run sub-mip - namespace dual_simplex = cuopt::linear_programming::dual_simplex; - dual_simplex::user_problem_t branch_and_bound_problem(best_sol.handle_ptr); - dual_simplex::simplex_solver_settings_t branch_and_bound_settings; - dual_simplex::mip_solution_t branch_and_bound_solution(1); - dual_simplex::mip_status_t branch_and_bound_status = dual_simplex::mip_status_t::UNSET; - fixed_problem.get_host_user_problem(branch_and_bound_problem); - branch_and_bound_solution.resize(branch_and_bound_problem.num_cols); - // Fill in the settings for branch and bound - // branch_and_bound_settings.time_limit = 20; - branch_and_bound_settings.print_presolve_stats = false; - branch_and_bound_settings.absolute_mip_gap_tol = context.settings.tolerances.absolute_mip_gap; - branch_and_bound_settings.relative_mip_gap_tol = context.settings.tolerances.relative_mip_gap; - branch_and_bound_settings.integer_tol = context.settings.tolerances.integrality_tolerance; - branch_and_bound_settings.solution_callback = [this](std::vector& solution, - f_t objective) {}; - dual_simplex::branch_and_bound_t branch_and_bound(branch_and_bound_problem, - branch_and_bound_settings); - // branch_and_bound.set_initial_guess(cuopt::host_copy(fixed_assignment)); - branch_and_bound_status = branch_and_bound.solve(branch_and_bound_solution); - if (!std::isnan(branch_and_bound_solution.objective)) { - cuopt_assert(fixed_assignment.size() == branch_and_bound_solution.x.size(), - "Assignment size mismatch"); - CUOPT_LOG_DEBUG("Sub-MIP solution found. Objective %.16e. Status %d", - branch_and_bound_solution.objective, - int(branch_and_bound_status)); - raft::copy(fixed_assignment.data(), - branch_and_bound_solution.x.data(), - fixed_assignment.size(), - best_sol.handle_ptr->get_stream()); - } - best_sol.handle_ptr->sync_stream(); - // unfix the assignment on given result no matter if it is feasible - best_sol.unfix_variables(fixed_assignment, variable_map); - // cuopt_assert(solution.test_number_all_integer(), "All must be integers after offspring"); - best_sol.compute_feasibility(); - CUOPT_LOG_DEBUG("RINS Solution: feasible: %d, objective: %g", - best_sol.get_feasible(), - best_sol.get_user_objective()); - // initial_sol_vector.push_back(std::move(best_sol)); - } - exit(0); - run_fp_alone(sol); population.update_weights(); diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index 6b6d0d005..9ee7b69db 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -17,33 +17,306 @@ #include +#include #include +#include namespace cuopt::linear_programming::detail { template -rins_t::rins_t(std::string const& name_, - mip_solver_context_t& context_, +rins_t::rins_t(mip_solver_context_t& context_, diversity_manager_t& dm_, - rins_settings_t settings_) + rins_settings_t settings_) : context(context_), problem_ptr(context.problem_ptr), dm(dm_), settings(settings_) { - frac = settings.default_frac; + fixrate = settings.default_fixrate; + rins_thread = std::make_unique>(); + rins_thread->rins_ptr = this; } +template +rins_thread_t::rins_thread_t() +{ + cpu_worker = std::thread(&rins_thread_t::cpu_worker_thread, this); +} + +template +rins_thread_t::~rins_thread_t() +{ + if (!cpu_thread_terminate) { kill_cpu_solver(); } +} + +template +void rins_thread_t::cpu_worker_thread() +{ + while (!cpu_thread_terminate) { + // Wait for start signal + { + std::unique_lock lock(cpu_mutex); + cpu_cv.wait(lock, [this] { return cpu_thread_should_start || cpu_thread_terminate; }); + } + + if (cpu_thread_terminate) break; + + // Run CPU solver + { + raft::common::nvtx::range fun_scope("Running RINS"); + // bleh + printf("-------- RINS triggered\n"); + rins_ptr->run_rins(); + printf("-------- RINS done\n"); + } + + cpu_thread_should_start = false; + cpu_thread_done = true; + } +} + +template +void rins_thread_t::kill_cpu_solver() +{ + cpu_thread_terminate = true; + cpu_cv.notify_one(); + cpu_worker.join(); +} + +template +void rins_thread_t::start_cpu_solver() +{ // Reset flags + cpu_thread_done = false; + cpu_thread_should_start = true; + cpu_cv.notify_one(); +} + +template +void rins_thread_t::stop_cpu_solver() +{ +} + +template +bool rins_thread_t::wait_for_cpu_solver() +{ + while (!cpu_thread_done && !cpu_thread_terminate) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + + return true; +} + +template +void rins_t::new_best_incumbent_callback(const std::vector& solution) +{ + // printf("-------- new best incumbent callback w/ solution %d\n", (int)solution.size()); + node_count_at_last_improvement = node_count.load(); +} + +// node_callback may be called from different threads(i think?). need lock protection template void rins_t::node_callback(const std::vector& solution, f_t objective) { + std::lock_guard lock(rins_mutex); + + node_count++; total_calls++; - printf( - "-------- node processed w/ solution %d and objective %e\n", (int)solution.size(), objective); + // printf( + // "-------- node processed w/ solution %d and objective %e\n", (int)solution.size(), + // objective); + + if (node_count - node_count_at_last_improvement < settings.nodes_after_later_improvement) return; + + if (node_count - node_count_at_last_rins > settings.node_freq) { + node_count_at_last_rins = node_count.load(); + + // printf("-------- rins triggered at node %d\n", node_count.load()); + if (!rins_thread->cpu_thread_should_start && dm.population.current_size() > 0) { + lp_optimal_solution = solution; + rins_thread->start_cpu_solver(); + } else { + // printf("-------- rins thread not done\n"); + } + } +} + +template +void rins_t::run_rins() +{ + // solutions may have been generated by b&b or cpufj already. take a look and RINS if so + // RINS + if (dm.population.current_size() > 0) { + auto best_sol = dm.population.best_feasible(); + i_t sol_size_before_rins = best_sol.assignment.size(); + auto lp_opt_device = + cuopt::device_copy(this->lp_optimal_solution, problem_ptr->handle_ptr->get_stream()); + cuopt_assert(lp_opt_device.size() == problem_ptr->n_variables, "Assignment size mismatch"); + cuopt_assert(best_sol.assignment.size() == problem_ptr->n_variables, + "Assignment size mismatch"); + + rmm::device_uvector vars_to_fix(problem_ptr->n_integer_vars, + problem_ptr->handle_ptr->get_stream()); + auto end = + thrust::copy_if(problem_ptr->handle_ptr->get_thrust_policy(), + thrust::make_counting_iterator(i_t(0)), + thrust::make_counting_iterator(problem_ptr->n_integer_vars), + vars_to_fix.begin(), + [lpopt = lp_opt_device.data(), + incumbent = best_sol.assignment.data(), + pb = problem_ptr->view()] __device__(i_t int_idx) { + i_t var_idx = pb.integer_indices[int_idx]; + cuopt_assert(var_idx < pb.n_variables, "Variable index out of bounds"); + return pb.integer_equal(lpopt[var_idx], incumbent[var_idx]); + }); + vars_to_fix.resize(end - vars_to_fix.begin(), problem_ptr->handle_ptr->get_stream()); + f_t fractional_ratio = (f_t)(vars_to_fix.size()) / (f_t)problem_ptr->n_integer_vars; + + // sort by fixing priority + thrust::sort(problem_ptr->handle_ptr->get_thrust_policy(), + vars_to_fix.begin(), + vars_to_fix.end(), + [assignment = best_sol.assignment.data(), pb = problem_ptr->view()] __device__( + i_t var_1, i_t var_2) { + return get_fractionality_of_val(assignment[var_1]) < + get_fractionality_of_val(assignment[var_2]); + }); + + // fix n first according to fractional ratio + f_t rins_ratio = fixrate; + i_t n_to_fix = std::max((int)(vars_to_fix.size() * rins_ratio), 0); + vars_to_fix.resize(n_to_fix, problem_ptr->handle_ptr->get_stream()); + thrust::sort( + problem_ptr->handle_ptr->get_thrust_policy(), vars_to_fix.begin(), vars_to_fix.end()); + + if (n_to_fix == 0) return; + + CUOPT_LOG_DEBUG("Running RINS on solution with objective %g, fixing %d/%d", + best_sol.get_user_objective(), + vars_to_fix.size(), + problem_ptr->n_integer_vars); + CUOPT_LOG_DEBUG("RINS fixrate %g time limit %g", fixrate, time_limit); + CUOPT_LOG_DEBUG("RINS fractional ratio %g%%", fractional_ratio * 100); + + f_t prev_obj = best_sol.get_objective(); + + auto [fixed_problem, fixed_assignment, variable_map] = best_sol.fix_variables(vars_to_fix); + CUOPT_LOG_DEBUG( + "new var count %d var_count %d", fixed_problem.n_variables, problem_ptr->n_integer_vars); + + // if (settings.objective_cut) { + // f_t objective_cut = best_sol.get_objective() - + // std::max(std::abs(0.001 * best_sol.get_objective()), + // OBJECTIVE_EPSILON); + // fixed_problem.add_cutting_plane_at_objective(objective_cut); + // } + + // limit remaining fractional to their integer bounds + // thrust::for_each( + // problem_ptr->handle_ptr->get_thrust_policy(), + // thrust::make_counting_iterator(i_t(0)), + // thrust::make_counting_iterator(fixed_problem.n_variables), + // [assignment = fixed_assignment.data(), pb = fixed_problem.view()] __device__(i_t var_idx) { + // if (pb.is_integer_var(var_idx) && !pb.is_integer(assignment[var_idx])) { + // get_lower(pb.variable_bounds[var_idx]) = std::floor(assignment[var_idx]); + // get_upper(pb.variable_bounds[var_idx]) = std::ceil(assignment[var_idx]); + // } + // }); + fixed_problem.presolve_data.reset_additional_vars(fixed_problem, best_sol.handle_ptr); + fixed_problem.presolve_data.initialize_var_mapping(fixed_problem, best_sol.handle_ptr); + trivial_presolve(fixed_problem); + fixed_problem.check_problem_representation(true); + + // run sub-mip + namespace dual_simplex = cuopt::linear_programming::dual_simplex; + dual_simplex::user_problem_t branch_and_bound_problem(best_sol.handle_ptr); + dual_simplex::simplex_solver_settings_t branch_and_bound_settings; + dual_simplex::mip_solution_t branch_and_bound_solution(1); + dual_simplex::mip_status_t branch_and_bound_status = dual_simplex::mip_status_t::UNSET; + fixed_problem.get_host_user_problem(branch_and_bound_problem); + branch_and_bound_solution.resize(branch_and_bound_problem.num_cols); + // Fill in the settings for branch and bound + branch_and_bound_settings.time_limit = time_limit; + branch_and_bound_settings.print_presolve_stats = false; + branch_and_bound_settings.absolute_mip_gap_tol = context.settings.tolerances.absolute_mip_gap; + branch_and_bound_settings.relative_mip_gap_tol = context.settings.tolerances.relative_mip_gap; + branch_and_bound_settings.integer_tol = context.settings.tolerances.integrality_tolerance; + // branch_and_bound_settings.num_threads = 4; + branch_and_bound_settings.num_bfs_threads = 2; + branch_and_bound_settings.num_diving_threads = 2; + branch_and_bound_settings.solution_callback = [this](std::vector& solution, + f_t objective) {}; + dual_simplex::branch_and_bound_t branch_and_bound(branch_and_bound_problem, + branch_and_bound_settings); + // branch_and_bound.set_initial_guess(cuopt::host_copy(fixed_assignment)); + branch_and_bound_status = branch_and_bound.solve(branch_and_bound_solution, "[RINS] "); + bool rins_solution_found = false; + if (!std::isnan(branch_and_bound_solution.objective)) { + cuopt_assert(fixed_assignment.size() == branch_and_bound_solution.x.size(), + "Assignment size mismatch"); + CUOPT_LOG_DEBUG("RINS solution found. Objective %.16e. Status %d", + branch_and_bound_solution.objective, + int(branch_and_bound_status)); + // first post process the trivial presolve on a device vector + rmm::device_uvector post_processed_solution(branch_and_bound_solution.x.size(), + best_sol.handle_ptr->get_stream()); + raft::copy(post_processed_solution.data(), + branch_and_bound_solution.x.data(), + branch_and_bound_solution.x.size(), + best_sol.handle_ptr->get_stream()); + fixed_problem.post_process_assignment(post_processed_solution, false); + cuopt_assert(post_processed_solution.size() == fixed_assignment.size(), + "Assignment size mismatch"); + best_sol.handle_ptr->sync_stream(); + std::swap(fixed_assignment, post_processed_solution); + + rins_solution_found = true; + } + if (branch_and_bound_status == dual_simplex::mip_status_t::OPTIMAL) { + CUOPT_LOG_DEBUG("RINS submip optimal"); + // do goldilocks update + fixrate = std::max(fixrate - 0.05, settings.min_fixrate); + time_limit = std::max(time_limit - 2, settings.min_time_limit); + } else if (branch_and_bound_status == dual_simplex::mip_status_t::TIME_LIMIT) { + CUOPT_LOG_DEBUG("RINS submip time limit"); + // do goldilocks update + fixrate = std::min(fixrate + 0.05, settings.max_fixrate); + time_limit = std::min(time_limit + 2, settings.max_time_limit); + } else if (branch_and_bound_status == dual_simplex::mip_status_t::INFEASIBLE) { + CUOPT_LOG_DEBUG("RINS submip infeasible"); + // do goldilocks update, decreasing fixrate + fixrate = std::max(fixrate - 0.05, settings.min_fixrate); + } else { + CUOPT_LOG_DEBUG("RINS solution not found"); + // do goldilocks update + fixrate = std::min(fixrate + 0.05, settings.max_fixrate); + time_limit = std::min(time_limit + 2, settings.max_time_limit); + } + best_sol.handle_ptr->sync_stream(); + // unfix the assignment on given result no matter if it is feasible + best_sol.unfix_variables(fixed_assignment, variable_map); + // cuopt_assert(solution.test_number_all_integer(), "All must be integers after offspring"); + best_sol.compute_feasibility(); + if (rins_solution_found) { + CUOPT_LOG_DEBUG("RINS Solution: feasible: %d, objective: %g", + best_sol.get_feasible(), + best_sol.get_user_objective()); + if (best_sol.get_objective() < prev_obj) { + CUOPT_LOG_DEBUG( + "RINS solution improved objective from %g to %g", prev_obj, best_sol.get_objective()); + } + cuopt_assert(best_sol.assignment.size() == sol_size_before_rins, "Assignment size mismatch"); + // TODO: figure out WHY??? + if ((int)best_sol.assignment.size() == sol_size_before_rins && + (int)best_sol.assignment.size() == problem_ptr->n_variables) + dm.population.add_solution(std::move(best_sol)); + } + } } #if MIP_INSTANTIATE_FLOAT +template class rins_thread_t; template class rins_t; #endif #if MIP_INSTANTIATE_DOUBLE +template class rins_thread_t; template class rins_t; #endif diff --git a/cpp/src/mip/diversity/lns/rins.cuh b/cpp/src/mip/diversity/lns/rins.cuh index c7b452b70..10355c023 100644 --- a/cpp/src/mip/diversity/lns/rins.cuh +++ b/cpp/src/mip/diversity/lns/rins.cuh @@ -22,9 +22,12 @@ #include #include +#include +#include #include #include #include +#include #include namespace cuopt::linear_programming::detail { @@ -33,15 +36,41 @@ namespace cuopt::linear_programming::detail { template class diversity_manager_t; -template struct rins_settings_t { - i_t node_freq = 10; - f_t min_frac = 0.3; - f_t max_frac = 0.8; - f_t default_frac = 0.5; - f_t min_time_limit = 1.; - f_t max_time_limit = 20.; - f_t default_time_limit = 4.; + int node_freq = 10; + int nodes_after_later_improvement = 20; + double min_fixrate = 0.3; + double max_fixrate = 0.8; + double default_fixrate = 0.5; + double min_time_limit = 1.; + double max_time_limit = 35.; + double default_time_limit = 6.; + bool objective_cut = true; +}; + +template +class rins_t; + +template +struct rins_thread_t { + rins_thread_t(); + ~rins_thread_t(); + + void cpu_worker_thread(); + void start_cpu_solver(); + void stop_cpu_solver(); + bool wait_for_cpu_solver(); // return feasibility + void kill_cpu_solver(); + + std::thread cpu_worker; + std::mutex cpu_mutex; + std::condition_variable cpu_cv; + std::atomic should_stop{false}; + std::atomic cpu_thread_should_start{false}; + std::atomic cpu_thread_done{false}; + std::atomic cpu_thread_terminate{false}; + + rins_t* rins_ptr{nullptr}; }; template @@ -49,19 +78,31 @@ class rins_t { public: rins_t(mip_solver_context_t& context, diversity_manager_t& dm, - rins_settings_t settings = rins_settings_t()); + rins_settings_t settings = rins_settings_t()); void node_callback(const std::vector& solution, f_t objective); + void new_best_incumbent_callback(const std::vector& solution); + + void run_rins(); mip_solver_context_t& context; problem_t* problem_ptr; diversity_manager_t& dm; - rins_settings_t settings; + rins_settings_t settings; - f_t frac{0.5}; + std::vector lp_optimal_solution; + f_t fixrate{0.5}; i_t total_calls{0}; i_t total_success{0}; + f_t time_limit{4.}; + + std::atomic node_count{0}; + std::atomic node_count_at_last_rins{0}; + std::atomic node_count_at_last_improvement{0}; + std::mutex rins_mutex; + + std::unique_ptr> rins_thread; }; } // namespace cuopt::linear_programming::detail diff --git a/cpp/src/mip/solver.cu b/cpp/src/mip/solver.cu index 824b39f54..9e0a690a1 100644 --- a/cpp/src/mip/solver.cu +++ b/cpp/src/mip/solver.cu @@ -74,6 +74,7 @@ struct branch_and_bound_solution_helper_t { void solution_callback(std::vector& solution, f_t objective) { dm->population.add_external_solution(solution, objective, solution_origin_t::BRANCH_AND_BOUND); + dm->rins.new_best_incumbent_callback(solution); } void set_simplex_solution(std::vector& solution, @@ -229,7 +230,8 @@ solution_t mip_solver_t::run_solver() branch_and_bound_status_future = std::async(std::launch::async, &dual_simplex::branch_and_bound_t::solve, branch_and_bound.get(), - std::ref(branch_and_bound_solution)); + std::ref(branch_and_bound_solution), + "aaaaaaa"); } // Start the primal heuristics From a6e33813162b1e16237a8dc87680e687b3ff42a4 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Thu, 9 Oct 2025 11:55:21 +0000 Subject: [PATCH 05/87] objective cut --- cpp/src/mip/diversity/lns/rins.cu | 38 +++++++++++++++++++++------- cpp/src/mip/diversity/population.cuh | 4 ++- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index 9ee7b69db..4691ede19 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -143,7 +143,18 @@ void rins_t::run_rins() // solutions may have been generated by b&b or cpufj already. take a look and RINS if so // RINS if (dm.population.current_size() > 0) { - auto best_sol = dm.population.best_feasible(); + auto best_sol = dm.population.best_feasible(); + // also scour through the external solution queue to potentially + // nab better solutiions early + auto queues = std::array{std::ref(dm.population.external_solution_queue), + std::ref(dm.population.external_solution_queue_cpufj)}; + for (auto& queue : queues) { + for (auto& h_entry : queue.get()) { + if (h_entry.objective >= best_sol.get_objective()) { continue; } + best_sol.copy_new_assignment(h_entry.solution); + best_sol.compute_feasibility(); + } + } i_t sol_size_before_rins = best_sol.assignment.size(); auto lp_opt_device = cuopt::device_copy(this->lp_optimal_solution, problem_ptr->handle_ptr->get_stream()); @@ -200,12 +211,18 @@ void rins_t::run_rins() CUOPT_LOG_DEBUG( "new var count %d var_count %d", fixed_problem.n_variables, problem_ptr->n_integer_vars); - // if (settings.objective_cut) { - // f_t objective_cut = best_sol.get_objective() - - // std::max(std::abs(0.001 * best_sol.get_objective()), - // OBJECTIVE_EPSILON); - // fixed_problem.add_cutting_plane_at_objective(objective_cut); - // } + // should probably just do an spmv to get the objective instead. ugly mess of copies + solution_t best_sol_fixed_space(fixed_problem); + best_sol_fixed_space.copy_new_assignment(host_copy(fixed_assignment)); + best_sol_fixed_space.compute_feasibility(); + CUOPT_LOG_DEBUG("RINS best sol fixed space objective %g", best_sol_fixed_space.get_objective()); + + if (settings.objective_cut) { + f_t objective_cut = + best_sol_fixed_space.get_objective() - + std::max(std::abs(0.001 * best_sol_fixed_space.get_objective()), OBJECTIVE_EPSILON); + fixed_problem.add_cutting_plane_at_objective(objective_cut); + } // limit remaining fractional to their integer bounds // thrust::for_each( @@ -223,6 +240,8 @@ void rins_t::run_rins() trivial_presolve(fixed_problem); fixed_problem.check_problem_representation(true); + // TODO: try running CPUFJ in a thread as well? + // run sub-mip namespace dual_simplex = cuopt::linear_programming::dual_simplex; dual_simplex::user_problem_t branch_and_bound_problem(best_sol.handle_ptr); @@ -293,7 +312,7 @@ void rins_t::run_rins() best_sol.unfix_variables(fixed_assignment, variable_map); // cuopt_assert(solution.test_number_all_integer(), "All must be integers after offspring"); best_sol.compute_feasibility(); - if (rins_solution_found) { + if (rins_solution_found && best_sol.get_feasible()) { CUOPT_LOG_DEBUG("RINS Solution: feasible: %d, objective: %g", best_sol.get_feasible(), best_sol.get_user_objective()); @@ -305,7 +324,8 @@ void rins_t::run_rins() // TODO: figure out WHY??? if ((int)best_sol.assignment.size() == sol_size_before_rins && (int)best_sol.assignment.size() == problem_ptr->n_variables) - dm.population.add_solution(std::move(best_sol)); + dm.population.add_external_solution( + best_sol.get_host_assignment(), best_sol.get_objective(), solution_origin_t::RINS); } } } diff --git a/cpp/src/mip/diversity/population.cuh b/cpp/src/mip/diversity/population.cuh index 79b2040ff..99788be19 100644 --- a/cpp/src/mip/diversity/population.cuh +++ b/cpp/src/mip/diversity/population.cuh @@ -35,13 +35,15 @@ namespace cuopt::linear_programming::detail { template class diversity_manager_t; -enum class solution_origin_t { BRANCH_AND_BOUND, CPUFJ, EXTERNAL }; +enum class solution_origin_t { BRANCH_AND_BOUND, CPUFJ, RENS, RINS, EXTERNAL }; constexpr const char* solution_origin_to_string(solution_origin_t origin) { switch (origin) { case solution_origin_t::BRANCH_AND_BOUND: return "B&B"; case solution_origin_t::CPUFJ: return "CPUFJ"; + case solution_origin_t::RENS: return "RENS"; + case solution_origin_t::RINS: return "RINS"; case solution_origin_t::EXTERNAL: return "injected"; default: return "unknown"; } From eed48a52df92f4ac2d859d221bb49ffe703a0e33 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Thu, 16 Oct 2025 09:57:20 +0000 Subject: [PATCH 06/87] take from external queue --- cpp/src/mip/diversity/lns/rins.cu | 12 ++++++++---- cpp/src/mip/diversity/lns/rins.cuh | 6 +++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index 4691ede19..7b9a90100 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -62,9 +62,9 @@ void rins_thread_t::cpu_worker_thread() { raft::common::nvtx::range fun_scope("Running RINS"); // bleh - printf("-------- RINS triggered\n"); + // printf("-------- RINS triggered\n"); rins_ptr->run_rins(); - printf("-------- RINS done\n"); + // printf("-------- RINS done\n"); } cpu_thread_should_start = false; @@ -128,7 +128,9 @@ void rins_t::node_callback(const std::vector& solution, f_t objec node_count_at_last_rins = node_count.load(); // printf("-------- rins triggered at node %d\n", node_count.load()); - if (!rins_thread->cpu_thread_should_start && dm.population.current_size() > 0) { + if (!rins_thread->cpu_thread_should_start && + (dm.population.current_size() > 0 || dm.population.external_solution_queue.size() > 0 || + dm.population.external_solution_queue_cpufj.size() > 0)) { lp_optimal_solution = solution; rins_thread->start_cpu_solver(); } else { @@ -142,7 +144,8 @@ void rins_t::run_rins() { // solutions may have been generated by b&b or cpufj already. take a look and RINS if so // RINS - if (dm.population.current_size() > 0) { + if (dm.population.current_size() > 0 || dm.population.external_solution_queue.size() > 0 || + dm.population.external_solution_queue_cpufj.size() > 0) { auto best_sol = dm.population.best_feasible(); // also scour through the external solution queue to potentially // nab better solutiions early @@ -155,6 +158,7 @@ void rins_t::run_rins() best_sol.compute_feasibility(); } } + if (!best_sol.get_feasible()) { return; } i_t sol_size_before_rins = best_sol.assignment.size(); auto lp_opt_device = cuopt::device_copy(this->lp_optimal_solution, problem_ptr->handle_ptr->get_stream()); diff --git a/cpp/src/mip/diversity/lns/rins.cuh b/cpp/src/mip/diversity/lns/rins.cuh index 10355c023..97d584f4e 100644 --- a/cpp/src/mip/diversity/lns/rins.cuh +++ b/cpp/src/mip/diversity/lns/rins.cuh @@ -37,9 +37,9 @@ template class diversity_manager_t; struct rins_settings_t { - int node_freq = 10; - int nodes_after_later_improvement = 20; - double min_fixrate = 0.3; + int node_freq = 100; + int nodes_after_later_improvement = 200; + double min_fixrate = 0.1; double max_fixrate = 0.8; double default_fixrate = 0.5; double min_time_limit = 1.; From b91c057a77efdafa43e80d3b3ecd98d663d93871 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Thu, 16 Oct 2025 13:35:01 +0000 Subject: [PATCH 07/87] assert moved --- cpp/src/mip/diversity/lns/rins.cu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index 7b9a90100..6b54e7099 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -314,9 +314,9 @@ void rins_t::run_rins() best_sol.handle_ptr->sync_stream(); // unfix the assignment on given result no matter if it is feasible best_sol.unfix_variables(fixed_assignment, variable_map); - // cuopt_assert(solution.test_number_all_integer(), "All must be integers after offspring"); best_sol.compute_feasibility(); if (rins_solution_found && best_sol.get_feasible()) { + cuopt_assert(best_sol.test_number_all_integer(), "All must be integers after offspring"); CUOPT_LOG_DEBUG("RINS Solution: feasible: %d, objective: %g", best_sol.get_feasible(), best_sol.get_user_objective()); From 0cea76226ee4a86099144d6ee33057ee28798d39 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Fri, 17 Oct 2025 09:42:31 +0000 Subject: [PATCH 08/87] tuning tweaks --- cpp/src/dual_simplex/branch_and_bound.cpp | 4 ++ .../dual_simplex/simplex_solver_settings.hpp | 2 + cpp/src/mip/diversity/lns/rins.cu | 53 ++++++++++++------- cpp/src/mip/diversity/lns/rins.cuh | 6 +-- 4 files changed, 42 insertions(+), 23 deletions(-) diff --git a/cpp/src/dual_simplex/branch_and_bound.cpp b/cpp/src/dual_simplex/branch_and_bound.cpp index 6005dc003..d7538fffc 100644 --- a/cpp/src/dual_simplex/branch_and_bound.cpp +++ b/cpp/src/dual_simplex/branch_and_bound.cpp @@ -851,6 +851,10 @@ void branch_and_bound_t::explore_subtree(i_t task_id, status_ = mip_exploration_status_t::TIME_LIMIT; return; } + if (stats_.nodes_explored >= settings_.node_limit) { + status_ = mip_exploration_status_t::TIME_LIMIT; + return; + } // Set the correct bounds for the leaf problem leaf_problem.lower = original_lp_.lower; diff --git a/cpp/src/dual_simplex/simplex_solver_settings.hpp b/cpp/src/dual_simplex/simplex_solver_settings.hpp index fa643fdc8..99b86030b 100644 --- a/cpp/src/dual_simplex/simplex_solver_settings.hpp +++ b/cpp/src/dual_simplex/simplex_solver_settings.hpp @@ -33,6 +33,7 @@ struct simplex_solver_settings_t { public: simplex_solver_settings_t() : iteration_limit(std::numeric_limits::max()), + node_limit(std::numeric_limits::max()), time_limit(std::numeric_limits::infinity()), absolute_mip_gap_tol(0.0), relative_mip_gap_tol(1e-3), @@ -91,6 +92,7 @@ struct simplex_solver_settings_t { void set_log_filename(const std::string& log_filename) { log.set_log_file(log_filename); } void close_log_file() { log.close_log_file(); } i_t iteration_limit; + i_t node_limit; f_t time_limit; f_t absolute_mip_gap_tol; // Tolerance on mip gap to declare optimal f_t relative_mip_gap_tol; // Tolerance on mip gap to declare optimal diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index 6b54e7099..e32750c29 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -117,7 +117,6 @@ void rins_t::node_callback(const std::vector& solution, f_t objec std::lock_guard lock(rins_mutex); node_count++; - total_calls++; // printf( // "-------- node processed w/ solution %d and objective %e\n", (int)solution.size(), // objective); @@ -168,18 +167,16 @@ void rins_t::run_rins() rmm::device_uvector vars_to_fix(problem_ptr->n_integer_vars, problem_ptr->handle_ptr->get_stream()); - auto end = - thrust::copy_if(problem_ptr->handle_ptr->get_thrust_policy(), - thrust::make_counting_iterator(i_t(0)), - thrust::make_counting_iterator(problem_ptr->n_integer_vars), - vars_to_fix.begin(), - [lpopt = lp_opt_device.data(), - incumbent = best_sol.assignment.data(), - pb = problem_ptr->view()] __device__(i_t int_idx) { - i_t var_idx = pb.integer_indices[int_idx]; - cuopt_assert(var_idx < pb.n_variables, "Variable index out of bounds"); - return pb.integer_equal(lpopt[var_idx], incumbent[var_idx]); - }); + auto end = thrust::copy_if(problem_ptr->handle_ptr->get_thrust_policy(), + thrust::make_counting_iterator(i_t(0)), + thrust::make_counting_iterator(problem_ptr->n_variables), + vars_to_fix.begin(), + [lpopt = lp_opt_device.data(), + incumbent = best_sol.assignment.data(), + pb = problem_ptr->view()] __device__(i_t var_idx) { + if (!pb.is_integer_var(var_idx)) return false; + return pb.integer_equal(lpopt[var_idx], incumbent[var_idx]); + }); vars_to_fix.resize(end - vars_to_fix.begin(), problem_ptr->handle_ptr->get_stream()); f_t fractional_ratio = (f_t)(vars_to_fix.size()) / (f_t)problem_ptr->n_integer_vars; @@ -200,8 +197,17 @@ void rins_t::run_rins() thrust::sort( problem_ptr->handle_ptr->get_thrust_policy(), vars_to_fix.begin(), vars_to_fix.end()); + cuopt_assert(thrust::all_of(problem_ptr->handle_ptr->get_thrust_policy(), + vars_to_fix.begin(), + vars_to_fix.end(), + [pb = problem_ptr->view()] __device__(i_t var_idx) { + return pb.is_integer_var(var_idx); + }), + "All variables to fix must be integer variables"); + if (n_to_fix == 0) return; + total_calls++; CUOPT_LOG_DEBUG("Running RINS on solution with objective %g, fixing %d/%d", best_sol.get_user_objective(), vars_to_fix.size(), @@ -209,7 +215,7 @@ void rins_t::run_rins() CUOPT_LOG_DEBUG("RINS fixrate %g time limit %g", fixrate, time_limit); CUOPT_LOG_DEBUG("RINS fractional ratio %g%%", fractional_ratio * 100); - f_t prev_obj = best_sol.get_objective(); + f_t prev_obj = best_sol.get_user_objective(); auto [fixed_problem, fixed_assignment, variable_map] = best_sol.fix_variables(vars_to_fix); CUOPT_LOG_DEBUG( @@ -219,7 +225,8 @@ void rins_t::run_rins() solution_t best_sol_fixed_space(fixed_problem); best_sol_fixed_space.copy_new_assignment(host_copy(fixed_assignment)); best_sol_fixed_space.compute_feasibility(); - CUOPT_LOG_DEBUG("RINS best sol fixed space objective %g", best_sol_fixed_space.get_objective()); + CUOPT_LOG_DEBUG("RINS best sol fixed space objective %g", + best_sol_fixed_space.get_user_objective()); if (settings.objective_cut) { f_t objective_cut = @@ -256,6 +263,7 @@ void rins_t::run_rins() branch_and_bound_solution.resize(branch_and_bound_problem.num_cols); // Fill in the settings for branch and bound branch_and_bound_settings.time_limit = time_limit; + branch_and_bound_settings.node_limit = 5000; branch_and_bound_settings.print_presolve_stats = false; branch_and_bound_settings.absolute_mip_gap_tol = context.settings.tolerances.absolute_mip_gap; branch_and_bound_settings.relative_mip_gap_tol = context.settings.tolerances.relative_mip_gap; @@ -267,7 +275,7 @@ void rins_t::run_rins() f_t objective) {}; dual_simplex::branch_and_bound_t branch_and_bound(branch_and_bound_problem, branch_and_bound_settings); - // branch_and_bound.set_initial_guess(cuopt::host_copy(fixed_assignment)); + branch_and_bound.set_initial_guess(cuopt::host_copy(fixed_assignment)); branch_and_bound_status = branch_and_bound.solve(branch_and_bound_solution, "[RINS] "); bool rins_solution_found = false; if (!std::isnan(branch_and_bound_solution.objective)) { @@ -303,8 +311,10 @@ void rins_t::run_rins() time_limit = std::min(time_limit + 2, settings.max_time_limit); } else if (branch_and_bound_status == dual_simplex::mip_status_t::INFEASIBLE) { CUOPT_LOG_DEBUG("RINS submip infeasible"); + fixrate = std::min(fixrate + 0.05, settings.max_fixrate); + time_limit = std::min(time_limit + 2, settings.max_time_limit); // do goldilocks update, decreasing fixrate - fixrate = std::max(fixrate - 0.05, settings.min_fixrate); + // fixrate = std::max(fixrate - 0.05, settings.min_fixrate); } else { CUOPT_LOG_DEBUG("RINS solution not found"); // do goldilocks update @@ -320,9 +330,11 @@ void rins_t::run_rins() CUOPT_LOG_DEBUG("RINS Solution: feasible: %d, objective: %g", best_sol.get_feasible(), best_sol.get_user_objective()); - if (best_sol.get_objective() < prev_obj) { - CUOPT_LOG_DEBUG( - "RINS solution improved objective from %g to %g", prev_obj, best_sol.get_objective()); + if (best_sol.get_user_objective() < prev_obj) { + CUOPT_LOG_DEBUG("RINS solution improved objective from %g to %g", + prev_obj, + best_sol.get_user_objective()); + total_success++; } cuopt_assert(best_sol.assignment.size() == sol_size_before_rins, "Assignment size mismatch"); // TODO: figure out WHY??? @@ -332,6 +344,7 @@ void rins_t::run_rins() best_sol.get_host_assignment(), best_sol.get_objective(), solution_origin_t::RINS); } } + CUOPT_LOG_DEBUG("RINS calls/successes %d/%d", total_calls, total_success); } #if MIP_INSTANTIATE_FLOAT diff --git a/cpp/src/mip/diversity/lns/rins.cuh b/cpp/src/mip/diversity/lns/rins.cuh index 97d584f4e..f57ee06e0 100644 --- a/cpp/src/mip/diversity/lns/rins.cuh +++ b/cpp/src/mip/diversity/lns/rins.cuh @@ -42,8 +42,8 @@ struct rins_settings_t { double min_fixrate = 0.1; double max_fixrate = 0.8; double default_fixrate = 0.5; - double min_time_limit = 1.; - double max_time_limit = 35.; + double min_time_limit = 10.; + double max_time_limit = 20.; double default_time_limit = 6.; bool objective_cut = true; }; @@ -95,7 +95,7 @@ class rins_t { f_t fixrate{0.5}; i_t total_calls{0}; i_t total_success{0}; - f_t time_limit{4.}; + f_t time_limit{10.}; std::atomic node_count{0}; std::atomic node_count_at_last_rins{0}; From af84d2f10b36700bb2e82f5f88533002983be74c Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Fri, 17 Oct 2025 10:17:17 +0000 Subject: [PATCH 09/87] fix incorrect node counting in RINS --- cpp/src/mip/diversity/lns/rins.cu | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index e32750c29..9c3e561c5 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -117,15 +117,16 @@ void rins_t::node_callback(const std::vector& solution, f_t objec std::lock_guard lock(rins_mutex); node_count++; - // printf( - // "-------- node processed w/ solution %d and objective %e\n", (int)solution.size(), - // objective); + // CUOPT_LOG_INFO("RINS callback node count %d, node count at last improvement %d, node count at + // last rins %d", node_count.load(), node_count_at_last_improvement.load(), + // node_count_at_last_rins.load()); + // printf( + // "-------- node processed w/ solution %d and objective %e\n", (int)solution.size(), + // objective); if (node_count - node_count_at_last_improvement < settings.nodes_after_later_improvement) return; if (node_count - node_count_at_last_rins > settings.node_freq) { - node_count_at_last_rins = node_count.load(); - // printf("-------- rins triggered at node %d\n", node_count.load()); if (!rins_thread->cpu_thread_should_start && (dm.population.current_size() > 0 || dm.population.external_solution_queue.size() > 0 || @@ -205,9 +206,13 @@ void rins_t::run_rins() }), "All variables to fix must be integer variables"); - if (n_to_fix == 0) return; + if (n_to_fix == 0) { + CUOPT_LOG_DEBUG("RINS no variables to fix"); + return; + } total_calls++; + node_count_at_last_rins = node_count.load(); CUOPT_LOG_DEBUG("Running RINS on solution with objective %g, fixing %d/%d", best_sol.get_user_objective(), vars_to_fix.size(), From 0a6e7d906d766f3ce81c7530a5b70502dbd963b0 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Fri, 17 Oct 2025 12:11:40 +0000 Subject: [PATCH 10/87] rins fjcpu thread and crash fix --- cpp/src/mip/diversity/lns/rins.cu | 51 ++++++++++++++-- cpp/src/mip/feasibility_jump/fj_cpu.cu | 73 +++++++++++++++++++++++ cpp/src/mip/feasibility_jump/fj_cpu.cuh | 27 +++++++++ cpp/src/mip/local_search/local_search.cu | 71 ---------------------- cpp/src/mip/local_search/local_search.cuh | 24 +------- 5 files changed, 148 insertions(+), 98 deletions(-) diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index 9c3e561c5..ea961c5a5 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -144,6 +145,7 @@ void rins_t::run_rins() { // solutions may have been generated by b&b or cpufj already. take a look and RINS if so // RINS + if (dm.population.solutions.size() == 0) { return; } if (dm.population.current_size() > 0 || dm.population.external_solution_queue.size() > 0 || dm.population.external_solution_queue_cpufj.size() > 0) { auto best_sol = dm.population.best_feasible(); @@ -257,6 +259,17 @@ void rins_t::run_rins() fixed_problem.check_problem_representation(true); // TODO: try running CPUFJ in a thread as well? + mip_solver_context_t fj_context( + best_sol.handle_ptr, &fixed_problem, context.settings, context.scaling); + fj_t fj(fj_context); + solution_t fj_solution(fixed_problem); + fj_solution.copy_new_assignment(cuopt::host_copy(fixed_assignment)); + std::vector default_weights(fixed_problem.n_constraints, 1.); + cpu_fj_thread_t cpu_fj_thread; + cpu_fj_thread.fj_cpu = fj.create_cpu_climber( + fj_solution, default_weights, default_weights, 0., fj_settings_t{}, true); + cpu_fj_thread.fj_ptr = &fj; + cpu_fj_thread.start_cpu_solver(); // run sub-mip namespace dual_simplex = cuopt::linear_programming::dual_simplex; @@ -267,8 +280,8 @@ void rins_t::run_rins() fixed_problem.get_host_user_problem(branch_and_bound_problem); branch_and_bound_solution.resize(branch_and_bound_problem.num_cols); // Fill in the settings for branch and bound - branch_and_bound_settings.time_limit = time_limit; - branch_and_bound_settings.node_limit = 5000; + branch_and_bound_settings.time_limit = time_limit; + // branch_and_bound_settings.node_limit = 5000; branch_and_bound_settings.print_presolve_stats = false; branch_and_bound_settings.absolute_mip_gap_tol = context.settings.tolerances.absolute_mip_gap; branch_and_bound_settings.relative_mip_gap_tol = context.settings.tolerances.relative_mip_gap; @@ -283,6 +296,7 @@ void rins_t::run_rins() branch_and_bound.set_initial_guess(cuopt::host_copy(fixed_assignment)); branch_and_bound_status = branch_and_bound.solve(branch_and_bound_solution, "[RINS] "); bool rins_solution_found = false; + if (!std::isnan(branch_and_bound_solution.objective)) { cuopt_assert(fixed_assignment.size() == branch_and_bound_solution.x.size(), "Assignment size mismatch"); @@ -319,14 +333,43 @@ void rins_t::run_rins() fixrate = std::min(fixrate + 0.05, settings.max_fixrate); time_limit = std::min(time_limit + 2, settings.max_time_limit); // do goldilocks update, decreasing fixrate - // fixrate = std::max(fixrate - 0.05, settings.min_fixrate); + fixrate = std::max(fixrate - 0.05, settings.min_fixrate); } else { CUOPT_LOG_DEBUG("RINS solution not found"); // do goldilocks update fixrate = std::min(fixrate + 0.05, settings.max_fixrate); time_limit = std::min(time_limit + 2, settings.max_time_limit); } - best_sol.handle_ptr->sync_stream(); + + cpu_fj_thread.stop_cpu_solver(); + bool fj_solution_found = cpu_fj_thread.wait_for_cpu_solver(); + CUOPT_LOG_DEBUG("RINS FJ ran for %d iterations", cpu_fj_thread.fj_cpu->iterations); + if (fj_solution_found) { + CUOPT_LOG_DEBUG("RINS FJ solution found. Objective %.16e", + cpu_fj_thread.fj_cpu->h_best_objective); + if (cpu_fj_thread.fj_cpu->h_best_objective < branch_and_bound_solution.objective || + std::isnan(branch_and_bound_solution.objective)) { + // first post process the trivial presolve on a device vector + rmm::device_uvector post_processed_solution(branch_and_bound_solution.x.size(), + best_sol.handle_ptr->get_stream()); + raft::copy(post_processed_solution.data(), + cpu_fj_thread.fj_cpu->h_best_assignment.data(), + cpu_fj_thread.fj_cpu->h_best_assignment.size(), + best_sol.handle_ptr->get_stream()); + fixed_problem.post_process_assignment(post_processed_solution, false); + cuopt_assert(post_processed_solution.size() == fixed_assignment.size(), + "Assignment size mismatch"); + best_sol.handle_ptr->sync_stream(); + std::swap(fixed_assignment, post_processed_solution); + + CUOPT_LOG_DEBUG("RINS FJ solution improved objective from %g to %g", + best_sol.get_user_objective(), + fj_solution.get_user_objective()); + rins_solution_found = true; + } + } + cpu_fj_thread.kill_cpu_solver(); + // unfix the assignment on given result no matter if it is feasible best_sol.unfix_variables(fixed_assignment, variable_map); best_sol.compute_feasibility(); diff --git a/cpp/src/mip/feasibility_jump/fj_cpu.cu b/cpp/src/mip/feasibility_jump/fj_cpu.cu index 137c67876..5471a9c67 100644 --- a/cpp/src/mip/feasibility_jump/fj_cpu.cu +++ b/cpp/src/mip/feasibility_jump/fj_cpu.cu @@ -1054,12 +1054,85 @@ bool fj_t::cpu_solve(fj_cpu_climber_t& fj_cpu, f_t in_time_l return fj_cpu.feasible_found; } +template +cpu_fj_thread_t::cpu_fj_thread_t() +{ + cpu_worker = std::thread(&cpu_fj_thread_t::cpu_worker_thread, this); +} + +template +cpu_fj_thread_t::~cpu_fj_thread_t() +{ + if (!cpu_thread_terminate) { kill_cpu_solver(); } +} + +template +void cpu_fj_thread_t::cpu_worker_thread() +{ + while (!cpu_thread_terminate) { + // Wait for start signal + { + std::unique_lock lock(cpu_mutex); + cpu_cv.wait(lock, [this] { return cpu_thread_should_start || cpu_thread_terminate; }); + } + + if (cpu_thread_terminate) break; + + // Run CPU solver + { + raft::common::nvtx::range fun_scope("Running CPU FJ"); + cpu_fj_solution_found = fj_ptr->cpu_solve(*fj_cpu); + } + + cpu_thread_should_start = false; + cpu_thread_done = true; + } +} + +template +void cpu_fj_thread_t::kill_cpu_solver() +{ + cpu_thread_terminate = true; + if (fj_cpu) fj_cpu->halted = true; + cpu_cv.notify_one(); + cpu_worker.join(); +} + +template +void cpu_fj_thread_t::start_cpu_solver() +{ + cuopt_assert(fj_cpu != nullptr, "fj_cpu must not be null"); + // Reset flags + cpu_thread_done = false; + cpu_thread_should_start = true; + fj_cpu->halted = false; + cpu_cv.notify_one(); +} + +template +void cpu_fj_thread_t::stop_cpu_solver() +{ + fj_cpu->halted = true; +} + +template +bool cpu_fj_thread_t::wait_for_cpu_solver() +{ + while (!cpu_thread_done && !cpu_thread_terminate) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + + return cpu_fj_solution_found; +} + #if MIP_INSTANTIATE_FLOAT template class fj_t; +template class cpu_fj_thread_t; #endif #if MIP_INSTANTIATE_DOUBLE template class fj_t; +template class cpu_fj_thread_t; #endif } // namespace cuopt::linear_programming::detail diff --git a/cpp/src/mip/feasibility_jump/fj_cpu.cuh b/cpp/src/mip/feasibility_jump/fj_cpu.cuh index bbd05a21c..cfee4c4c4 100644 --- a/cpp/src/mip/feasibility_jump/fj_cpu.cuh +++ b/cpp/src/mip/feasibility_jump/fj_cpu.cuh @@ -17,7 +17,11 @@ #pragma once +#include +#include #include +#include +#include #include #include @@ -121,4 +125,27 @@ struct fj_cpu_climber_t { std::atomic halted{false}; }; +template +struct cpu_fj_thread_t { + cpu_fj_thread_t(); + ~cpu_fj_thread_t(); + + void cpu_worker_thread(); + void start_cpu_solver(); + void stop_cpu_solver(); + bool wait_for_cpu_solver(); // return feasibility + void kill_cpu_solver(); + + std::thread cpu_worker; + std::mutex cpu_mutex; + std::condition_variable cpu_cv; + std::atomic should_stop{false}; + std::atomic cpu_thread_should_start{false}; + std::atomic cpu_thread_done{false}; + std::atomic cpu_thread_terminate{false}; + bool cpu_fj_solution_found{false}; + std::unique_ptr> fj_cpu; + fj_t* fj_ptr{nullptr}; +}; + } // namespace cuopt::linear_programming::detail diff --git a/cpp/src/mip/local_search/local_search.cu b/cpp/src/mip/local_search/local_search.cu index ee055b974..b4c89f072 100644 --- a/cpp/src/mip/local_search/local_search.cu +++ b/cpp/src/mip/local_search/local_search.cu @@ -34,77 +34,6 @@ namespace cuopt::linear_programming::detail { -template -cpu_fj_thread_t::cpu_fj_thread_t() -{ - cpu_worker = std::thread(&cpu_fj_thread_t::cpu_worker_thread, this); -} - -template -cpu_fj_thread_t::~cpu_fj_thread_t() -{ - if (!cpu_thread_terminate) { kill_cpu_solver(); } -} - -template -void cpu_fj_thread_t::cpu_worker_thread() -{ - while (!cpu_thread_terminate) { - // Wait for start signal - { - std::unique_lock lock(cpu_mutex); - cpu_cv.wait(lock, [this] { return cpu_thread_should_start || cpu_thread_terminate; }); - } - - if (cpu_thread_terminate) break; - - // Run CPU solver - { - raft::common::nvtx::range fun_scope("Running CPU FJ"); - cpu_fj_solution_found = fj_ptr->cpu_solve(*fj_cpu); - } - - cpu_thread_should_start = false; - cpu_thread_done = true; - } -} - -template -void cpu_fj_thread_t::kill_cpu_solver() -{ - cpu_thread_terminate = true; - if (fj_cpu) fj_cpu->halted = true; - cpu_cv.notify_one(); - cpu_worker.join(); -} - -template -void cpu_fj_thread_t::start_cpu_solver() -{ - cuopt_assert(fj_cpu != nullptr, "fj_cpu must not be null"); - // Reset flags - cpu_thread_done = false; - cpu_thread_should_start = true; - fj_cpu->halted = false; - cpu_cv.notify_one(); -} - -template -void cpu_fj_thread_t::stop_cpu_solver() -{ - fj_cpu->halted = true; -} - -template -bool cpu_fj_thread_t::wait_for_cpu_solver() -{ - while (!cpu_thread_done && !cpu_thread_terminate) { - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - } - - return cpu_fj_solution_found; -} - template local_search_t::local_search_t(mip_solver_context_t& context_, rmm::device_uvector& lp_optimal_solution_) diff --git a/cpp/src/mip/local_search/local_search.cuh b/cpp/src/mip/local_search/local_search.cuh index 1219a7d3f..076212a89 100644 --- a/cpp/src/mip/local_search/local_search.cuh +++ b/cpp/src/mip/local_search/local_search.cuh @@ -18,6 +18,7 @@ #pragma once #include +#include #include #include #include @@ -52,29 +53,6 @@ struct ls_config_t { ls_method_t ls_method = ls_method_t::RANDOM; }; -template -struct cpu_fj_thread_t { - cpu_fj_thread_t(); - ~cpu_fj_thread_t(); - - void cpu_worker_thread(); - void start_cpu_solver(); - void stop_cpu_solver(); - bool wait_for_cpu_solver(); // return feasibility - void kill_cpu_solver(); - - std::thread cpu_worker; - std::mutex cpu_mutex; - std::condition_variable cpu_cv; - std::atomic should_stop{false}; - std::atomic cpu_thread_should_start{false}; - std::atomic cpu_thread_done{false}; - std::atomic cpu_thread_terminate{false}; - bool cpu_fj_solution_found{false}; - std::unique_ptr> fj_cpu; - fj_t* fj_ptr{nullptr}; -}; - template class local_search_t { public: From deebc2c676471483965ab02a3d3bb62b0d0c7a3b Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Fri, 17 Oct 2025 12:29:16 +0000 Subject: [PATCH 11/87] looser optimality gap tolerance to solve submips more quickly --- cpp/src/mip/diversity/lns/rins.cu | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index ea961c5a5..99757788d 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -280,7 +280,8 @@ void rins_t::run_rins() fixed_problem.get_host_user_problem(branch_and_bound_problem); branch_and_bound_solution.resize(branch_and_bound_problem.num_cols); // Fill in the settings for branch and bound - branch_and_bound_settings.time_limit = time_limit; + branch_and_bound_settings.time_limit = time_limit; + branch_and_bound_settings.relative_mip_gap_tol = 0.03; // 3% // branch_and_bound_settings.node_limit = 5000; branch_and_bound_settings.print_presolve_stats = false; branch_and_bound_settings.absolute_mip_gap_tol = context.settings.tolerances.absolute_mip_gap; From fd3aa5f244fd7cf06189e77d80f448d8752a033a Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Fri, 17 Oct 2025 14:18:13 +0000 Subject: [PATCH 12/87] protect population vector access w/ mutex --- cpp/src/mip/diversity/lns/rins.cu | 11 ++++------- cpp/src/mip/diversity/population.cu | 16 +++++++++++++--- cpp/src/mip/diversity/population.cuh | 2 +- .../recombiners/bound_prop_recombiner.cuh | 6 +++--- 4 files changed, 21 insertions(+), 14 deletions(-) diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index 99757788d..16b71e0dd 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -129,9 +129,7 @@ void rins_t::node_callback(const std::vector& solution, f_t objec if (node_count - node_count_at_last_rins > settings.node_freq) { // printf("-------- rins triggered at node %d\n", node_count.load()); - if (!rins_thread->cpu_thread_should_start && - (dm.population.current_size() > 0 || dm.population.external_solution_queue.size() > 0 || - dm.population.external_solution_queue_cpufj.size() > 0)) { + if (!rins_thread->cpu_thread_should_start && dm.population.current_size() > 0) { lp_optimal_solution = solution; rins_thread->start_cpu_solver(); } else { @@ -145,10 +143,9 @@ void rins_t::run_rins() { // solutions may have been generated by b&b or cpufj already. take a look and RINS if so // RINS - if (dm.population.solutions.size() == 0) { return; } - if (dm.population.current_size() > 0 || dm.population.external_solution_queue.size() > 0 || - dm.population.external_solution_queue_cpufj.size() > 0) { - auto best_sol = dm.population.best_feasible(); + auto pop = dm.population.population_to_vector(); + if (pop.size() > 0) { + auto best_sol = pop[0]; // also scour through the external solution queue to potentially // nab better solutiions early auto queues = std::array{std::ref(dm.population.external_solution_queue), diff --git a/cpp/src/mip/diversity/population.cu b/cpp/src/mip/diversity/population.cu index ae02ff738..a52234c77 100644 --- a/cpp/src/mip/diversity/population.cu +++ b/cpp/src/mip/diversity/population.cu @@ -94,6 +94,8 @@ template std::pair, solution_t> population_t::get_two_random( bool tournament) { + std::lock_guard lock(solution_mutex); + raft::common::nvtx::range fun_scope("get_two_random"); cuopt_assert(indices.size() > 2, "There should be enough solutions"); size_t add = (size_t)(!solutions[0].first); @@ -137,7 +139,7 @@ void population_t::add_solutions_from_vec(std::vector size_t population_t::get_external_solution_size() { - std::lock_guard lock(solution_mutex); + std::lock_guard lock(solution_mutex); return external_solution_queue.size(); } @@ -146,7 +148,7 @@ void population_t::add_external_solution(const std::vector& solut f_t objective, solution_origin_t origin) { - std::lock_guard lock(solution_mutex); + std::lock_guard lock(solution_mutex); if (origin == solution_origin_t::CPUFJ) { external_solution_queue_cpufj.emplace_back(solution, objective, origin); @@ -191,7 +193,7 @@ void population_t::preempt_heuristic_solver() template std::vector> population_t::get_external_solutions() { - std::lock_guard lock(solution_mutex); + std::lock_guard lock(solution_mutex); std::vector> return_vector; i_t counter = 0; f_t new_best_feasible_objective = best_feasible_objective; @@ -242,6 +244,7 @@ std::vector> population_t::get_external_solutions template bool population_t::is_better_than_best_feasible(solution_t& sol) { + std::lock_guard lock(solution_mutex); bool obj_better = sol.get_objective() < best_feasible_objective; return obj_better && sol.get_feasible(); } @@ -249,6 +252,7 @@ bool population_t::is_better_than_best_feasible(solution_t& template void population_t::run_solution_callbacks(solution_t& sol) { + std::lock_guard lock(solution_mutex); bool better_solution_found = is_better_than_best_feasible(sol); auto user_callbacks = context.settings.get_mip_callbacks(); if (better_solution_found) { @@ -375,6 +379,7 @@ void population_t::adjust_weights_according_to_best_feasible() template i_t population_t::add_solution(solution_t&& sol) { + std::lock_guard lock(solution_mutex); raft::common::nvtx::range fun_scope("add_solution"); population_hash_map.insert(sol); double sol_cost = sol.get_quality(weights); @@ -542,6 +547,7 @@ void population_t::compute_new_weights() template void population_t::update_qualities() { + std::lock_guard lock(solution_mutex); if (indices.size() == 1) return; using pr = std::pair; for (size_t i = !is_feasible(); i < indices.size(); i++) @@ -619,6 +625,7 @@ bool population_t::check_if_feasible_similar_exists(size_t start_index template void population_t::eradicate_similar(size_t start_index, solution_t& sol) { + std::lock_guard lock(solution_mutex); raft::common::nvtx::range fun_scope("eradicate_similar"); for (size_t i = start_index; i < indices.size(); i++) { if (check_sols_similar(sol, solutions[indices[i].first].second)) { @@ -640,6 +647,7 @@ void population_t::eradicate_similar(size_t start_index, solution_t std::vector> population_t::population_to_vector() { + std::lock_guard lock(solution_mutex); std::vector> sol_vec; bool population_feasible = is_feasible(); for (size_t i = !population_feasible; i < indices.size(); i++) { @@ -651,6 +659,7 @@ std::vector> population_t::population_to_vector() template void population_t::halve_the_population() { + std::lock_guard lock(solution_mutex); raft::common::nvtx::range fun_scope("halve_the_population"); // try 3/4 here if (current_size() <= (max_solutions * halving_skip_ratio)) { return; } @@ -685,6 +694,7 @@ void population_t::halve_the_population() template size_t population_t::find_free_solution_index() { + std::lock_guard lock(solution_mutex); raft::common::nvtx::range fun_scope("find_free_solution_index"); // ASSERT such index exists for (size_t i = 1; i < solutions.size(); i++) diff --git a/cpp/src/mip/diversity/population.cuh b/cpp/src/mip/diversity/population.cuh index 99788be19..728d45bbc 100644 --- a/cpp/src/mip/diversity/population.cuh +++ b/cpp/src/mip/diversity/population.cuh @@ -204,7 +204,7 @@ class population_t { std::vector external_solution_queue_cpufj; std::mt19937 rng; i_t update_iter = 0; - std::mutex solution_mutex; + std::recursive_mutex solution_mutex; std::atomic early_exit_primal_generation = false; std::atomic preempt_heuristic_solver_ = false; f_t best_feasible_objective = std::numeric_limits::max(); diff --git a/cpp/src/mip/diversity/recombiners/bound_prop_recombiner.cuh b/cpp/src/mip/diversity/recombiners/bound_prop_recombiner.cuh index 52347fc8b..712033b05 100644 --- a/cpp/src/mip/diversity/recombiners/bound_prop_recombiner.cuh +++ b/cpp/src/mip/diversity/recombiners/bound_prop_recombiner.cuh @@ -211,9 +211,9 @@ class bound_prop_recombiner_t : public recombiner_t { offspring.assignment = std::move(old_assignment); offspring.handle_ptr->sync_stream(); offspring.unfix_variables(fixed_assignment, variable_map); - cuopt_func_call(bool feasible_after_unfix = offspring.get_feasible()); - cuopt_assert(feasible_after_unfix == feasible_after_bounds_prop, - "Feasible after unfix should be same as feasible after bounds prop!"); + // cuopt_func_call(bool feasible_after_unfix = offspring.get_feasible()); + // cuopt_assert(feasible_after_unfix == feasible_after_bounds_prop, + // "Feasible after unfix should be same as feasible after bounds prop!"); a.handle_ptr->sync_stream(); } else { timer_t timer(bp_recombiner_config_t::bounds_prop_time_limit); From 721f22b5e902f8a05c8c6e98b2b22f56074906a2 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Fri, 17 Oct 2025 14:34:37 +0000 Subject: [PATCH 13/87] fix empty pop crash --- cpp/src/mip/diversity/population.cu | 1 + 1 file changed, 1 insertion(+) diff --git a/cpp/src/mip/diversity/population.cu b/cpp/src/mip/diversity/population.cu index a52234c77..54a8750a6 100644 --- a/cpp/src/mip/diversity/population.cu +++ b/cpp/src/mip/diversity/population.cu @@ -648,6 +648,7 @@ template std::vector> population_t::population_to_vector() { std::lock_guard lock(solution_mutex); + if (solutions.empty()) return {}; std::vector> sol_vec; bool population_feasible = is_feasible(); for (size_t i = !population_feasible; i < indices.size(); i++) { From 141d6f2de815d3bf7f448f902e3f08532154f288 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Fri, 17 Oct 2025 15:14:23 +0000 Subject: [PATCH 14/87] add missing cudaSetDevice call and further guards --- cpp/src/mip/diversity/diversity_manager.cu | 2 ++ cpp/src/mip/diversity/lns/rins.cu | 3 +++ cpp/src/mip/diversity/lns/rins.cuh | 2 ++ cpp/src/mip/diversity/population.cu | 10 ++++++++++ cpp/src/mip/diversity/population.cuh | 4 ++-- 5 files changed, 19 insertions(+), 2 deletions(-) diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index abac62db4..a24759a24 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -449,6 +449,8 @@ solution_t diversity_manager_t::run_solver() return sol; } + rins.enabled = true; + auto sol = generate_solution(timer.remaining_time(), false); population.add_solution(std::move(solution_t(sol))); if (timer.check_time_limit()) { diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index 16b71e0dd..569fae7a8 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -115,6 +115,8 @@ void rins_t::new_best_incumbent_callback(const std::vector& solut template void rins_t::node_callback(const std::vector& solution, f_t objective) { + if (!enabled) return; + std::lock_guard lock(rins_mutex); node_count++; @@ -143,6 +145,7 @@ void rins_t::run_rins() { // solutions may have been generated by b&b or cpufj already. take a look and RINS if so // RINS + cudaSetDevice(context.handle_ptr->get_device()); auto pop = dm.population.population_to_vector(); if (pop.size() > 0) { auto best_sol = pop[0]; diff --git a/cpp/src/mip/diversity/lns/rins.cuh b/cpp/src/mip/diversity/lns/rins.cuh index f57ee06e0..51c9ef670 100644 --- a/cpp/src/mip/diversity/lns/rins.cuh +++ b/cpp/src/mip/diversity/lns/rins.cuh @@ -97,6 +97,8 @@ class rins_t { i_t total_success{0}; f_t time_limit{10.}; + bool enabled{false}; + std::atomic node_count{0}; std::atomic node_count_at_last_rins{0}; std::atomic node_count_at_last_improvement{0}; diff --git a/cpp/src/mip/diversity/population.cu b/cpp/src/mip/diversity/population.cu index 54a8750a6..093313417 100644 --- a/cpp/src/mip/diversity/population.cu +++ b/cpp/src/mip/diversity/population.cu @@ -68,6 +68,7 @@ i_t get_max_var_threshold(i_t n_vars) template void population_t::allocate_solutions() { + std::lock_guard lock(solution_mutex); for (size_t i = 0; i < max_solutions; ++i) { bool occupied = false; solutions.emplace_back(occupied, solution_t(*problem_ptr)); @@ -77,6 +78,8 @@ void population_t::allocate_solutions() template void population_t::initialize_population() { + std::lock_guard lock(solution_mutex); + var_threshold = get_max_var_threshold(problem_ptr->n_integer_vars); solutions.reserve(max_solutions); indices.reserve(max_solutions); @@ -130,6 +133,7 @@ std::pair, solution_t> population_t::ge template void population_t::add_solutions_from_vec(std::vector>&& solutions) { + std::lock_guard lock(solution_mutex); raft::common::nvtx::range fun_scope("add_solution_from_vec"); for (auto&& sol : solutions) { add_solution(std::move(sol)); @@ -347,6 +351,7 @@ void population_t::run_solution_callbacks(solution_t& sol) template void population_t::adjust_weights_according_to_best_feasible() { + std::lock_guard lock(solution_mutex); // check if the best in population still the best feasible if (!best().get_feasible()) { CUOPT_LOG_DEBUG("Best solution is infeasible, adjusting weights"); @@ -456,6 +461,7 @@ i_t population_t::add_solution(solution_t&& sol) template void population_t::normalize_weights() { + std::lock_guard lock(solution_mutex); CUOPT_LOG_DEBUG("Normalizing weights"); rmm::device_scalar l2_norm(problem_ptr->handle_ptr->get_stream()); @@ -499,6 +505,7 @@ void population_t::normalize_weights() template void population_t::compute_new_weights() { + std::lock_guard lock(solution_mutex); auto& best_sol = best(); auto settings = context.settings; @@ -563,6 +570,7 @@ void population_t::update_qualities() template void population_t::update_weights() { + std::lock_guard lock(solution_mutex); raft::common::nvtx::range fun_scope("adjust_weight_changes"); CUOPT_LOG_DEBUG("Changing the weights"); compute_new_weights(); @@ -596,6 +604,7 @@ size_t population_t::best_similar_index(solution_t& sol) template i_t population_t::insert_index(std::pair to_insert) { + std::lock_guard lock(solution_mutex); raft::common::nvtx::range fun_scope("insert_index"); // Assert free index is available indices.emplace_back(0, 0.0); @@ -613,6 +622,7 @@ template bool population_t::check_if_feasible_similar_exists(size_t start_index, solution_t& sol) { + std::lock_guard lock(solution_mutex); raft::common::nvtx::range fun_scope("check_if_feasible_similar_exists"); for (size_t i = start_index; i < indices.size(); i++) { if (check_sols_similar(sol, solutions[indices[i].first].second)) { diff --git a/cpp/src/mip/diversity/population.cuh b/cpp/src/mip/diversity/population.cuh index 728d45bbc..76b0ee72d 100644 --- a/cpp/src/mip/diversity/population.cuh +++ b/cpp/src/mip/diversity/population.cuh @@ -64,10 +64,10 @@ class population_t { // functions without logic: // -------------------- /*! \brief { Current number of solutions in the pool }*/ - size_t current_size() { return indices.size() - 1; } + size_t current_size() { return indices.empty() ? 0 : indices.size() - 1; } /*! \brief { Is feasible soution in the pool? } */ - bool is_feasible() { return solutions[0].first; } + bool is_feasible() { return indices.empty() ? false : solutions[0].first; } /*! \brief { Best feasible quality }*/ f_t feasible_quality() { return indices[0].second; } From f6e73f3b65f181eb55994bd4bd45e49f23d36040 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Fri, 17 Oct 2025 17:24:37 +0000 Subject: [PATCH 15/87] streamline population locks --- cpp/src/mip/diversity/population.cu | 33 +++++++++------------------- cpp/src/mip/diversity/population.cuh | 3 ++- 2 files changed, 12 insertions(+), 24 deletions(-) diff --git a/cpp/src/mip/diversity/population.cu b/cpp/src/mip/diversity/population.cu index 093313417..e0919e4db 100644 --- a/cpp/src/mip/diversity/population.cu +++ b/cpp/src/mip/diversity/population.cu @@ -68,7 +68,7 @@ i_t get_max_var_threshold(i_t n_vars) template void population_t::allocate_solutions() { - std::lock_guard lock(solution_mutex); + std::lock_guard lock(write_mutex); for (size_t i = 0; i < max_solutions; ++i) { bool occupied = false; solutions.emplace_back(occupied, solution_t(*problem_ptr)); @@ -78,7 +78,7 @@ void population_t::allocate_solutions() template void population_t::initialize_population() { - std::lock_guard lock(solution_mutex); + std::lock_guard lock(write_mutex); var_threshold = get_max_var_threshold(problem_ptr->n_integer_vars); solutions.reserve(max_solutions); @@ -97,8 +97,6 @@ template std::pair, solution_t> population_t::get_two_random( bool tournament) { - std::lock_guard lock(solution_mutex); - raft::common::nvtx::range fun_scope("get_two_random"); cuopt_assert(indices.size() > 2, "There should be enough solutions"); size_t add = (size_t)(!solutions[0].first); @@ -133,7 +131,6 @@ std::pair, solution_t> population_t::ge template void population_t::add_solutions_from_vec(std::vector>&& solutions) { - std::lock_guard lock(solution_mutex); raft::common::nvtx::range fun_scope("add_solution_from_vec"); for (auto&& sol : solutions) { add_solution(std::move(sol)); @@ -143,7 +140,7 @@ void population_t::add_solutions_from_vec(std::vector size_t population_t::get_external_solution_size() { - std::lock_guard lock(solution_mutex); + std::lock_guard lock(solution_mutex); return external_solution_queue.size(); } @@ -152,7 +149,7 @@ void population_t::add_external_solution(const std::vector& solut f_t objective, solution_origin_t origin) { - std::lock_guard lock(solution_mutex); + std::lock_guard lock(solution_mutex); if (origin == solution_origin_t::CPUFJ) { external_solution_queue_cpufj.emplace_back(solution, objective, origin); @@ -197,7 +194,7 @@ void population_t::preempt_heuristic_solver() template std::vector> population_t::get_external_solutions() { - std::lock_guard lock(solution_mutex); + std::lock_guard lock(solution_mutex); std::vector> return_vector; i_t counter = 0; f_t new_best_feasible_objective = best_feasible_objective; @@ -248,7 +245,6 @@ std::vector> population_t::get_external_solutions template bool population_t::is_better_than_best_feasible(solution_t& sol) { - std::lock_guard lock(solution_mutex); bool obj_better = sol.get_objective() < best_feasible_objective; return obj_better && sol.get_feasible(); } @@ -256,7 +252,6 @@ bool population_t::is_better_than_best_feasible(solution_t& template void population_t::run_solution_callbacks(solution_t& sol) { - std::lock_guard lock(solution_mutex); bool better_solution_found = is_better_than_best_feasible(sol); auto user_callbacks = context.settings.get_mip_callbacks(); if (better_solution_found) { @@ -351,7 +346,6 @@ void population_t::run_solution_callbacks(solution_t& sol) template void population_t::adjust_weights_according_to_best_feasible() { - std::lock_guard lock(solution_mutex); // check if the best in population still the best feasible if (!best().get_feasible()) { CUOPT_LOG_DEBUG("Best solution is infeasible, adjusting weights"); @@ -384,7 +378,7 @@ void population_t::adjust_weights_according_to_best_feasible() template i_t population_t::add_solution(solution_t&& sol) { - std::lock_guard lock(solution_mutex); + std::lock_guard lock(write_mutex); raft::common::nvtx::range fun_scope("add_solution"); population_hash_map.insert(sol); double sol_cost = sol.get_quality(weights); @@ -461,7 +455,6 @@ i_t population_t::add_solution(solution_t&& sol) template void population_t::normalize_weights() { - std::lock_guard lock(solution_mutex); CUOPT_LOG_DEBUG("Normalizing weights"); rmm::device_scalar l2_norm(problem_ptr->handle_ptr->get_stream()); @@ -505,7 +498,6 @@ void population_t::normalize_weights() template void population_t::compute_new_weights() { - std::lock_guard lock(solution_mutex); auto& best_sol = best(); auto settings = context.settings; @@ -554,7 +546,7 @@ void population_t::compute_new_weights() template void population_t::update_qualities() { - std::lock_guard lock(solution_mutex); + std::lock_guard lock(write_mutex); if (indices.size() == 1) return; using pr = std::pair; for (size_t i = !is_feasible(); i < indices.size(); i++) @@ -570,7 +562,6 @@ void population_t::update_qualities() template void population_t::update_weights() { - std::lock_guard lock(solution_mutex); raft::common::nvtx::range fun_scope("adjust_weight_changes"); CUOPT_LOG_DEBUG("Changing the weights"); compute_new_weights(); @@ -591,7 +582,6 @@ bool population_t::check_sols_similar(solution_t& sol1, template size_t population_t::best_similar_index(solution_t& sol) { - raft::common::nvtx::range fun_scope("best_similar_index"); if (indices.size() == 1) return max_solutions; for (size_t i = 1; i < indices.size(); i++) { if (check_sols_similar(sol, solutions[indices[i].first].second)) { return i; } @@ -604,7 +594,6 @@ size_t population_t::best_similar_index(solution_t& sol) template i_t population_t::insert_index(std::pair to_insert) { - std::lock_guard lock(solution_mutex); raft::common::nvtx::range fun_scope("insert_index"); // Assert free index is available indices.emplace_back(0, 0.0); @@ -622,7 +611,6 @@ template bool population_t::check_if_feasible_similar_exists(size_t start_index, solution_t& sol) { - std::lock_guard lock(solution_mutex); raft::common::nvtx::range fun_scope("check_if_feasible_similar_exists"); for (size_t i = start_index; i < indices.size(); i++) { if (check_sols_similar(sol, solutions[indices[i].first].second)) { @@ -635,7 +623,6 @@ bool population_t::check_if_feasible_similar_exists(size_t start_index template void population_t::eradicate_similar(size_t start_index, solution_t& sol) { - std::lock_guard lock(solution_mutex); raft::common::nvtx::range fun_scope("eradicate_similar"); for (size_t i = start_index; i < indices.size(); i++) { if (check_sols_similar(sol, solutions[indices[i].first].second)) { @@ -657,7 +644,7 @@ void population_t::eradicate_similar(size_t start_index, solution_t std::vector> population_t::population_to_vector() { - std::lock_guard lock(solution_mutex); + std::lock_guard lock(write_mutex); if (solutions.empty()) return {}; std::vector> sol_vec; bool population_feasible = is_feasible(); @@ -670,7 +657,6 @@ std::vector> population_t::population_to_vector() template void population_t::halve_the_population() { - std::lock_guard lock(solution_mutex); raft::common::nvtx::range fun_scope("halve_the_population"); // try 3/4 here if (current_size() <= (max_solutions * halving_skip_ratio)) { return; } @@ -680,6 +666,8 @@ void population_t::halve_the_population() i_t counter = 0; constexpr i_t max_adjustments = 4; size_t max_var_threshold = get_max_var_threshold(problem_ptr->n_integer_vars); + + std::lock_guard lock(write_mutex); while (current_size() > max_solutions / 2) { clear_except_best_feasible(); var_threshold = std::max(var_threshold * 0.97, 0.5 * problem_ptr->n_integer_vars); @@ -705,7 +693,6 @@ void population_t::halve_the_population() template size_t population_t::find_free_solution_index() { - std::lock_guard lock(solution_mutex); raft::common::nvtx::range fun_scope("find_free_solution_index"); // ASSERT such index exists for (size_t i = 1; i < solutions.size(); i++) diff --git a/cpp/src/mip/diversity/population.cuh b/cpp/src/mip/diversity/population.cuh index 76b0ee72d..4d124edd6 100644 --- a/cpp/src/mip/diversity/population.cuh +++ b/cpp/src/mip/diversity/population.cuh @@ -204,7 +204,8 @@ class population_t { std::vector external_solution_queue_cpufj; std::mt19937 rng; i_t update_iter = 0; - std::recursive_mutex solution_mutex; + std::recursive_mutex write_mutex; + std::mutex solution_mutex; std::atomic early_exit_primal_generation = false; std::atomic preempt_heuristic_solver_ = false; f_t best_feasible_objective = std::numeric_limits::max(); From 2215477754f08b69ef782806c9e16c09376aef98 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Sat, 18 Oct 2025 10:58:34 +0000 Subject: [PATCH 16/87] bump 1 --- cpp/src/mip/feasibility_jump/fj_cpu.cu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/mip/feasibility_jump/fj_cpu.cu b/cpp/src/mip/feasibility_jump/fj_cpu.cu index 5471a9c67..1c6b64810 100644 --- a/cpp/src/mip/feasibility_jump/fj_cpu.cu +++ b/cpp/src/mip/feasibility_jump/fj_cpu.cu @@ -174,7 +174,7 @@ static inline std::pair compute_score(fj_cpu_climber_t 0) base_obj = -fj_cpu.h_objective_weight; From 506966635128ef39ece437440fb8fbd7588ef860 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Sat, 18 Oct 2025 10:58:52 +0000 Subject: [PATCH 17/87] bump 2 --- cpp/src/mip/feasibility_jump/fj_cpu.cu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/mip/feasibility_jump/fj_cpu.cu b/cpp/src/mip/feasibility_jump/fj_cpu.cu index 1c6b64810..d9b797381 100644 --- a/cpp/src/mip/feasibility_jump/fj_cpu.cu +++ b/cpp/src/mip/feasibility_jump/fj_cpu.cu @@ -174,7 +174,7 @@ static inline std::pair compute_score(fj_cpu_climber_t 0) base_obj = -fj_cpu.h_objective_weight; From 80dd8bd450d2471005e049197673bd662be47828 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Sat, 18 Oct 2025 06:02:02 -0700 Subject: [PATCH 18/87] contention tweaks --- cpp/src/mip/diversity/lns/rins.cu | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index 569fae7a8..49c5f34f1 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -145,7 +145,10 @@ void rins_t::run_rins() { // solutions may have been generated by b&b or cpufj already. take a look and RINS if so // RINS - cudaSetDevice(context.handle_ptr->get_device()); + if (total_calls == 0) cudaSetDevice(context.handle_ptr->get_device()); + + if (!dm.population.is_feasible()) return; + auto pop = dm.population.population_to_vector(); if (pop.size() > 0) { auto best_sol = pop[0]; @@ -280,16 +283,17 @@ void rins_t::run_rins() fixed_problem.get_host_user_problem(branch_and_bound_problem); branch_and_bound_solution.resize(branch_and_bound_problem.num_cols); // Fill in the settings for branch and bound - branch_and_bound_settings.time_limit = time_limit; - branch_and_bound_settings.relative_mip_gap_tol = 0.03; // 3% + branch_and_bound_settings.time_limit = time_limit; // branch_and_bound_settings.node_limit = 5000; branch_and_bound_settings.print_presolve_stats = false; branch_and_bound_settings.absolute_mip_gap_tol = context.settings.tolerances.absolute_mip_gap; - branch_and_bound_settings.relative_mip_gap_tol = context.settings.tolerances.relative_mip_gap; - branch_and_bound_settings.integer_tol = context.settings.tolerances.integrality_tolerance; - // branch_and_bound_settings.num_threads = 4; - branch_and_bound_settings.num_bfs_threads = 2; - branch_and_bound_settings.num_diving_threads = 2; + // branch_and_bound_settings.relative_mip_gap_tol = + // context.settings.tolerances.relative_mip_gap; + branch_and_bound_settings.relative_mip_gap_tol = 0.03; // 3% + branch_and_bound_settings.integer_tol = context.settings.tolerances.integrality_tolerance; + branch_and_bound_settings.num_threads = 2; + branch_and_bound_settings.num_bfs_threads = 1; + branch_and_bound_settings.num_diving_threads = 1; branch_and_bound_settings.solution_callback = [this](std::vector& solution, f_t objective) {}; dual_simplex::branch_and_bound_t branch_and_bound(branch_and_bound_problem, @@ -388,7 +392,8 @@ void rins_t::run_rins() cuopt_assert(best_sol.assignment.size() == sol_size_before_rins, "Assignment size mismatch"); // TODO: figure out WHY??? if ((int)best_sol.assignment.size() == sol_size_before_rins && - (int)best_sol.assignment.size() == problem_ptr->n_variables) + (int)best_sol.assignment.size() == problem_ptr->n_variables && + best_sol.get_objective() < dm.population.best_feasible_objective) dm.population.add_external_solution( best_sol.get_host_assignment(), best_sol.get_objective(), solution_origin_t::RINS); } From e4f2eab86d688dff41a2e6ffb35c25b87d84d3d3 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Sat, 18 Oct 2025 06:02:17 -0700 Subject: [PATCH 19/87] bump --- cpp/src/mip/diversity/lns/rins.cu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index 49c5f34f1..47bed9bfb 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -390,7 +390,7 @@ void rins_t::run_rins() total_success++; } cuopt_assert(best_sol.assignment.size() == sol_size_before_rins, "Assignment size mismatch"); - // TODO: figure out WHY??? + // TODO: figure out WHY??? 1 if ((int)best_sol.assignment.size() == sol_size_before_rins && (int)best_sol.assignment.size() == problem_ptr->n_variables && best_sol.get_objective() < dm.population.best_feasible_objective) From 4ad54bf63d19ac431e9303612e118cb78ffa3010 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Mon, 20 Oct 2025 11:53:28 +0000 Subject: [PATCH 20/87] remove excessive locks, fix bnb bug --- cpp/src/dual_simplex/branch_and_bound.cpp | 4 +++- cpp/src/dual_simplex/presolve.cpp | 1 + cpp/src/mip/diversity/diversity_manager.cu | 4 ---- cpp/src/mip/diversity/lns/rins.cu | 6 ++++-- cpp/src/mip/diversity/population.cu | 4 +--- cpp/src/mip/feasibility_jump/fj_cpu.cu | 2 +- 6 files changed, 10 insertions(+), 11 deletions(-) diff --git a/cpp/src/dual_simplex/branch_and_bound.cpp b/cpp/src/dual_simplex/branch_and_bound.cpp index d7538fffc..8ecd69d43 100644 --- a/cpp/src/dual_simplex/branch_and_bound.cpp +++ b/cpp/src/dual_simplex/branch_and_bound.cpp @@ -483,7 +483,9 @@ mip_status_t branch_and_bound_t::set_final_solution(mip_solution_t& user_problem, { user_solution.resize(user_problem.num_cols); assert(problem.num_cols >= user_problem.num_cols); + assert(solution.size() >= user_problem.num_cols); std::copy(solution.begin(), solution.begin() + user_problem.num_cols, user_solution.data()); } diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index a24759a24..198054ea2 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -435,9 +435,6 @@ solution_t diversity_manager_t::run_solver() if (check_b_b_preemption()) { return population.best_feasible(); } - auto new_sol_vector = population.get_external_solutions(); - population.add_solutions_from_vec(std::move(new_sol_vector)); - if (context.settings.benchmark_info_ptr != nullptr) { context.settings.benchmark_info_ptr->objective_of_initial_population = population.best_feasible().get_user_objective(); @@ -458,7 +455,6 @@ solution_t diversity_manager_t::run_solver() population.add_solutions_from_vec(std::move(new_sol_vector)); return population.best_feasible(); } - run_fp_alone(sol); population.update_weights(); diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index 47bed9bfb..48dc5b39a 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -117,7 +117,7 @@ void rins_t::node_callback(const std::vector& solution, f_t objec { if (!enabled) return; - std::lock_guard lock(rins_mutex); + // std::lock_guard lock(rins_mutex); node_count++; // CUOPT_LOG_INFO("RINS callback node count %d, node count at last improvement %d, node count at @@ -131,7 +131,8 @@ void rins_t::node_callback(const std::vector& solution, f_t objec if (node_count - node_count_at_last_rins > settings.node_freq) { // printf("-------- rins triggered at node %d\n", node_count.load()); - if (!rins_thread->cpu_thread_should_start && dm.population.current_size() > 0) { + if (!rins_thread->cpu_thread_should_start && dm.population.current_size() > 0 && + dm.population.is_feasible()) { lp_optimal_solution = solution; rins_thread->start_cpu_solver(); } else { @@ -163,6 +164,7 @@ void rins_t::run_rins() best_sol.compute_feasibility(); } } + // TODO: gpu operations should probably be done on a separate stream here if (!best_sol.get_feasible()) { return; } i_t sol_size_before_rins = best_sol.assignment.size(); auto lp_opt_device = diff --git a/cpp/src/mip/diversity/population.cu b/cpp/src/mip/diversity/population.cu index e0919e4db..2a801a1e3 100644 --- a/cpp/src/mip/diversity/population.cu +++ b/cpp/src/mip/diversity/population.cu @@ -68,7 +68,6 @@ i_t get_max_var_threshold(i_t n_vars) template void population_t::allocate_solutions() { - std::lock_guard lock(write_mutex); for (size_t i = 0; i < max_solutions; ++i) { bool occupied = false; solutions.emplace_back(occupied, solution_t(*problem_ptr)); @@ -78,8 +77,6 @@ void population_t::allocate_solutions() template void population_t::initialize_population() { - std::lock_guard lock(write_mutex); - var_threshold = get_max_var_threshold(problem_ptr->n_integer_vars); solutions.reserve(max_solutions); indices.reserve(max_solutions); @@ -582,6 +579,7 @@ bool population_t::check_sols_similar(solution_t& sol1, template size_t population_t::best_similar_index(solution_t& sol) { + raft::common::nvtx::range fun_scope("best_similar_index"); if (indices.size() == 1) return max_solutions; for (size_t i = 1; i < indices.size(); i++) { if (check_sols_similar(sol, solutions[indices[i].first].second)) { return i; } diff --git a/cpp/src/mip/feasibility_jump/fj_cpu.cu b/cpp/src/mip/feasibility_jump/fj_cpu.cu index d9b797381..5471a9c67 100644 --- a/cpp/src/mip/feasibility_jump/fj_cpu.cu +++ b/cpp/src/mip/feasibility_jump/fj_cpu.cu @@ -174,7 +174,7 @@ static inline std::pair compute_score(fj_cpu_climber_t 0) base_obj = -fj_cpu.h_objective_weight; From 87c6b2f239ee013cebee6cbdd45ce8ee003b21dc Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Mon, 20 Oct 2025 11:54:07 +0000 Subject: [PATCH 21/87] more RINS threads --- cpp/src/mip/diversity/lns/rins.cu | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index 48dc5b39a..48c160316 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -292,10 +292,10 @@ void rins_t::run_rins() // branch_and_bound_settings.relative_mip_gap_tol = // context.settings.tolerances.relative_mip_gap; branch_and_bound_settings.relative_mip_gap_tol = 0.03; // 3% - branch_and_bound_settings.integer_tol = context.settings.tolerances.integrality_tolerance; - branch_and_bound_settings.num_threads = 2; - branch_and_bound_settings.num_bfs_threads = 1; - branch_and_bound_settings.num_diving_threads = 1; + branch_and_bound_settings.integer_tol = context.settings.tolerances.integrality_tolerance; + // branch_and_bound_settings.num_threads = 2; + branch_and_bound_settings.num_bfs_threads = 2; + branch_and_bound_settings.num_diving_threads = 2; branch_and_bound_settings.solution_callback = [this](std::vector& solution, f_t objective) {}; dual_simplex::branch_and_bound_t branch_and_bound(branch_and_bound_problem, From b016849c7fb3d8d77613d2a9c629141de0cb24a4 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Mon, 20 Oct 2025 11:57:13 +0000 Subject: [PATCH 22/87] node limit --- cpp/src/mip/diversity/lns/rins.cu | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index 48c160316..d577aab59 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -285,8 +285,8 @@ void rins_t::run_rins() fixed_problem.get_host_user_problem(branch_and_bound_problem); branch_and_bound_solution.resize(branch_and_bound_problem.num_cols); // Fill in the settings for branch and bound - branch_and_bound_settings.time_limit = time_limit; - // branch_and_bound_settings.node_limit = 5000; + branch_and_bound_settings.time_limit = time_limit; + branch_and_bound_settings.node_limit = 5000; branch_and_bound_settings.print_presolve_stats = false; branch_and_bound_settings.absolute_mip_gap_tol = context.settings.tolerances.absolute_mip_gap; // branch_and_bound_settings.relative_mip_gap_tol = From 20282508db5359927f9fb27ce497fb635084ac03 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Mon, 20 Oct 2025 13:15:54 +0000 Subject: [PATCH 23/87] random shuffle --- cpp/src/dual_simplex/branch_and_bound.cpp | 2 +- cpp/src/dual_simplex/presolve.cpp | 4 +- .../dual_simplex/simplex_solver_settings.hpp | 2 +- cpp/src/mip/diversity/lns/rins.cu | 46 +++++++------------ cpp/src/mip/diversity/lns/rins.cuh | 3 +- cpp/src/mip/solver.cu | 7 +-- cpp/src/utilities/seed_generator.cuh | 1 + 7 files changed, 29 insertions(+), 36 deletions(-) diff --git a/cpp/src/dual_simplex/branch_and_bound.cpp b/cpp/src/dual_simplex/branch_and_bound.cpp index 8ecd69d43..1887bccd0 100644 --- a/cpp/src/dual_simplex/branch_and_bound.cpp +++ b/cpp/src/dual_simplex/branch_and_bound.cpp @@ -642,7 +642,7 @@ node_status_t branch_and_bound_t::solve_node(search_tree_t& if (settings_.node_processed_callback != nullptr) { std::vector original_x; uncrush_primal_solution(original_problem_, original_lp_, leaf_solution.x, original_x); - settings_.node_processed_callback(original_x, leaf_objective); + settings_.node_processed_callback(node_ptr->node_id, original_x, leaf_objective); } if (leaf_num_fractional == 0) { diff --git a/cpp/src/dual_simplex/presolve.cpp b/cpp/src/dual_simplex/presolve.cpp index 7159eeea9..d3ddc0a05 100644 --- a/cpp/src/dual_simplex/presolve.cpp +++ b/cpp/src/dual_simplex/presolve.cpp @@ -1382,7 +1382,9 @@ void uncrush_primal_solution(const user_problem_t& user_problem, user_solution.resize(user_problem.num_cols); assert(problem.num_cols >= user_problem.num_cols); assert(solution.size() >= user_problem.num_cols); - std::copy(solution.begin(), solution.begin() + user_problem.num_cols, user_solution.data()); + std::copy(solution.begin(), + solution.begin() + std::min((i_t)solution.size(), user_problem.num_cols), + user_solution.data()); } template diff --git a/cpp/src/dual_simplex/simplex_solver_settings.hpp b/cpp/src/dual_simplex/simplex_solver_settings.hpp index 99b86030b..f3f032c27 100644 --- a/cpp/src/dual_simplex/simplex_solver_settings.hpp +++ b/cpp/src/dual_simplex/simplex_solver_settings.hpp @@ -146,7 +146,7 @@ struct simplex_solver_settings_t { i_t num_diving_threads; // number of threads dedicated to diving i_t inside_mip; // 0 if outside MIP, 1 if inside MIP at root node, 2 if inside MIP at leaf node std::function&, f_t)> solution_callback; - std::function&, f_t)> node_processed_callback; + std::function&, f_t)> node_processed_callback; std::function heuristic_preemption_callback; std::function&, std::vector&, f_t)> set_simplex_solution_callback; mutable logger_t log; diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index d577aab59..f64ac689c 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -33,6 +33,7 @@ rins_t::rins_t(mip_solver_context_t& context_, fixrate = settings.default_fixrate; rins_thread = std::make_unique>(); rins_thread->rins_ptr = this; + seed = cuopt::seed_generator::get_seed(); } template @@ -113,13 +114,13 @@ void rins_t::new_best_incumbent_callback(const std::vector& solut // node_callback may be called from different threads(i think?). need lock protection template -void rins_t::node_callback(const std::vector& solution, f_t objective) +void rins_t::node_callback(i_t node_id, const std::vector& solution, f_t objective) { - if (!enabled) return; + node_count++; - // std::lock_guard lock(rins_mutex); + if (!enabled) return; - node_count++; + std::lock_guard lock(rins_mutex); // CUOPT_LOG_INFO("RINS callback node count %d, node count at last improvement %d, node count at // last rins %d", node_count.load(), node_count_at_last_improvement.load(), // node_count_at_last_rins.load()); @@ -150,6 +151,8 @@ void rins_t::run_rins() if (!dm.population.is_feasible()) return; + cuopt_assert(lp_optimal_solution.size() == problem_ptr->n_variables, "Assignment size mismatch"); + auto pop = dm.population.population_to_vector(); if (pop.size() > 0) { auto best_sol = pop[0]; @@ -188,15 +191,11 @@ void rins_t::run_rins() vars_to_fix.resize(end - vars_to_fix.begin(), problem_ptr->handle_ptr->get_stream()); f_t fractional_ratio = (f_t)(vars_to_fix.size()) / (f_t)problem_ptr->n_integer_vars; - // sort by fixing priority - thrust::sort(problem_ptr->handle_ptr->get_thrust_policy(), - vars_to_fix.begin(), - vars_to_fix.end(), - [assignment = best_sol.assignment.data(), pb = problem_ptr->view()] __device__( - i_t var_1, i_t var_2) { - return get_fractionality_of_val(assignment[var_1]) < - get_fractionality_of_val(assignment[var_2]); - }); + thrust::default_random_engine g(seed + node_count); + + // shuffle fixing order + thrust::shuffle( + problem_ptr->handle_ptr->get_thrust_policy(), vars_to_fix.begin(), vars_to_fix.end(), g); // fix n first according to fractional ratio f_t rins_ratio = fixrate; @@ -247,17 +246,6 @@ void rins_t::run_rins() fixed_problem.add_cutting_plane_at_objective(objective_cut); } - // limit remaining fractional to their integer bounds - // thrust::for_each( - // problem_ptr->handle_ptr->get_thrust_policy(), - // thrust::make_counting_iterator(i_t(0)), - // thrust::make_counting_iterator(fixed_problem.n_variables), - // [assignment = fixed_assignment.data(), pb = fixed_problem.view()] __device__(i_t var_idx) { - // if (pb.is_integer_var(var_idx) && !pb.is_integer(assignment[var_idx])) { - // get_lower(pb.variable_bounds[var_idx]) = std::floor(assignment[var_idx]); - // get_upper(pb.variable_bounds[var_idx]) = std::ceil(assignment[var_idx]); - // } - // }); fixed_problem.presolve_data.reset_additional_vars(fixed_problem, best_sol.handle_ptr); fixed_problem.presolve_data.initialize_var_mapping(fixed_problem, best_sol.handle_ptr); trivial_presolve(fixed_problem); @@ -285,8 +273,8 @@ void rins_t::run_rins() fixed_problem.get_host_user_problem(branch_and_bound_problem); branch_and_bound_solution.resize(branch_and_bound_problem.num_cols); // Fill in the settings for branch and bound - branch_and_bound_settings.time_limit = time_limit; - branch_and_bound_settings.node_limit = 5000; + branch_and_bound_settings.time_limit = time_limit; + branch_and_bound_settings.node_limit = 5000 + node_count / 100; // try harder as time goes on branch_and_bound_settings.print_presolve_stats = false; branch_and_bound_settings.absolute_mip_gap_tol = context.settings.tolerances.absolute_mip_gap; // branch_and_bound_settings.relative_mip_gap_tol = @@ -392,10 +380,10 @@ void rins_t::run_rins() total_success++; } cuopt_assert(best_sol.assignment.size() == sol_size_before_rins, "Assignment size mismatch"); + cuopt_assert(best_sol.assignment.size() == problem_ptr->n_variables, + "Assignment size mismatch"); // TODO: figure out WHY??? 1 - if ((int)best_sol.assignment.size() == sol_size_before_rins && - (int)best_sol.assignment.size() == problem_ptr->n_variables && - best_sol.get_objective() < dm.population.best_feasible_objective) + if (best_sol.get_objective() < dm.population.best_feasible_objective) dm.population.add_external_solution( best_sol.get_host_assignment(), best_sol.get_objective(), solution_origin_t::RINS); } diff --git a/cpp/src/mip/diversity/lns/rins.cuh b/cpp/src/mip/diversity/lns/rins.cuh index 51c9ef670..e8334c2be 100644 --- a/cpp/src/mip/diversity/lns/rins.cuh +++ b/cpp/src/mip/diversity/lns/rins.cuh @@ -80,7 +80,7 @@ class rins_t { diversity_manager_t& dm, rins_settings_t settings = rins_settings_t()); - void node_callback(const std::vector& solution, f_t objective); + void node_callback(i_t node_id, const std::vector& solution, f_t objective); void new_best_incumbent_callback(const std::vector& solution); void run_rins(); @@ -96,6 +96,7 @@ class rins_t { i_t total_calls{0}; i_t total_success{0}; f_t time_limit{10.}; + i_t seed; bool enabled{false}; diff --git a/cpp/src/mip/solver.cu b/cpp/src/mip/solver.cu index 9e0a690a1..c0e99cad3 100644 --- a/cpp/src/mip/solver.cu +++ b/cpp/src/mip/solver.cu @@ -84,9 +84,9 @@ struct branch_and_bound_solution_helper_t { dm->set_simplex_solution(solution, dual_solution, objective); } - void node_processed_callback(std::vector& solution, f_t objective) + void node_processed_callback(i_t node_id, std::vector& solution, f_t objective) { - dm->rins.node_callback(solution, objective); + dm->rins.node_callback(node_id, solution, objective); } void preempt_heuristic_solver() { dm->population.preempt_heuristic_solver(); } @@ -211,7 +211,8 @@ solution_t mip_solver_t::run_solver() std::bind(&branch_and_bound_solution_helper_t::node_processed_callback, &solution_helper, std::placeholders::_1, - std::placeholders::_2); + std::placeholders::_2, + std::placeholders::_3); // Create the branch and bound object branch_and_bound = std::make_unique>( diff --git a/cpp/src/utilities/seed_generator.cuh b/cpp/src/utilities/seed_generator.cuh index b2af26fc9..09335e68d 100644 --- a/cpp/src/utilities/seed_generator.cuh +++ b/cpp/src/utilities/seed_generator.cuh @@ -21,6 +21,7 @@ namespace cuopt { +// TODO: should be thread local? class seed_generator { static int64_t seed_; From 427cc849c53ba26c9cc0b646103826c8af0c1f36 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Mon, 20 Oct 2025 15:04:22 +0000 Subject: [PATCH 24/87] tweaks --- cpp/src/dual_simplex/presolve.cpp | 2 +- cpp/src/mip/diversity/diversity_manager.cu | 2 ++ cpp/src/mip/diversity/lns/rins.cu | 4 +--- cpp/src/mip/diversity/lns/rins.cuh | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cpp/src/dual_simplex/presolve.cpp b/cpp/src/dual_simplex/presolve.cpp index d3ddc0a05..a0a606617 100644 --- a/cpp/src/dual_simplex/presolve.cpp +++ b/cpp/src/dual_simplex/presolve.cpp @@ -1381,7 +1381,7 @@ void uncrush_primal_solution(const user_problem_t& user_problem, { user_solution.resize(user_problem.num_cols); assert(problem.num_cols >= user_problem.num_cols); - assert(solution.size() >= user_problem.num_cols); + // assert(solution.size() >= user_problem.num_cols); std::copy(solution.begin(), solution.begin() + std::min((i_t)solution.size(), user_problem.num_cols), user_solution.data()); diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index 198054ea2..c6fe18ad8 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -465,6 +465,8 @@ solution_t diversity_manager_t::run_solver() } main_loop(); + auto new_sol_vector = population.get_external_solutions(); + population.add_solutions_from_vec(std::move(new_sol_vector)); return population.best_feasible(); }; diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index f64ac689c..f67f17152 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -199,7 +199,7 @@ void rins_t::run_rins() // fix n first according to fractional ratio f_t rins_ratio = fixrate; - i_t n_to_fix = std::max((int)(vars_to_fix.size() * rins_ratio), 0); + i_t n_to_fix = std::max((int)(vars_to_fix.size() * std::min(rins_ratio, fractional_ratio)), 0); vars_to_fix.resize(n_to_fix, problem_ptr->handle_ptr->get_stream()); thrust::sort( problem_ptr->handle_ptr->get_thrust_policy(), vars_to_fix.begin(), vars_to_fix.end()); @@ -325,8 +325,6 @@ void rins_t::run_rins() time_limit = std::min(time_limit + 2, settings.max_time_limit); } else if (branch_and_bound_status == dual_simplex::mip_status_t::INFEASIBLE) { CUOPT_LOG_DEBUG("RINS submip infeasible"); - fixrate = std::min(fixrate + 0.05, settings.max_fixrate); - time_limit = std::min(time_limit + 2, settings.max_time_limit); // do goldilocks update, decreasing fixrate fixrate = std::max(fixrate - 0.05, settings.min_fixrate); } else { diff --git a/cpp/src/mip/diversity/lns/rins.cuh b/cpp/src/mip/diversity/lns/rins.cuh index e8334c2be..7fb2b3a3e 100644 --- a/cpp/src/mip/diversity/lns/rins.cuh +++ b/cpp/src/mip/diversity/lns/rins.cuh @@ -39,9 +39,9 @@ class diversity_manager_t; struct rins_settings_t { int node_freq = 100; int nodes_after_later_improvement = 200; - double min_fixrate = 0.1; - double max_fixrate = 0.8; - double default_fixrate = 0.5; + double min_fixrate = 0.3; + double max_fixrate = 0.95; + double default_fixrate = 0.75; double min_time_limit = 10.; double max_time_limit = 20.; double default_time_limit = 6.; From f2ec2d7dc532f895394bee1224c08c0f97aca30e Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Mon, 20 Oct 2025 15:07:08 +0000 Subject: [PATCH 25/87] more threads --- cpp/src/mip/diversity/lns/rins.cu | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index f67f17152..e0447a094 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -282,8 +282,8 @@ void rins_t::run_rins() branch_and_bound_settings.relative_mip_gap_tol = 0.03; // 3% branch_and_bound_settings.integer_tol = context.settings.tolerances.integrality_tolerance; // branch_and_bound_settings.num_threads = 2; - branch_and_bound_settings.num_bfs_threads = 2; - branch_and_bound_settings.num_diving_threads = 2; + branch_and_bound_settings.num_bfs_threads = 6; + branch_and_bound_settings.num_diving_threads = 6; branch_and_bound_settings.solution_callback = [this](std::vector& solution, f_t objective) {}; dual_simplex::branch_and_bound_t branch_and_bound(branch_and_bound_problem, From 4c1ba90a3b840d5f4a78fd25074168a6d4aa66b1 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Mon, 20 Oct 2025 15:07:32 +0000 Subject: [PATCH 26/87] no objectve cut --- cpp/src/mip/diversity/lns/rins.cuh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/mip/diversity/lns/rins.cuh b/cpp/src/mip/diversity/lns/rins.cuh index 7fb2b3a3e..1d127762b 100644 --- a/cpp/src/mip/diversity/lns/rins.cuh +++ b/cpp/src/mip/diversity/lns/rins.cuh @@ -45,7 +45,7 @@ struct rins_settings_t { double min_time_limit = 10.; double max_time_limit = 20.; double default_time_limit = 6.; - bool objective_cut = true; + bool objective_cut = false; }; template From 2734212f650eb05a65fe2fbb95bf4560175da0da Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Mon, 20 Oct 2025 16:36:40 +0000 Subject: [PATCH 27/87] multireference rins --- cpp/src/mip/diversity/lns/rins.cu | 46 ++++++++++++++++++++++++++---- cpp/src/mip/diversity/lns/rins.cuh | 2 +- 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index e0447a094..e4d67cfca 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -191,12 +191,47 @@ void rins_t::run_rins() vars_to_fix.resize(end - vars_to_fix.begin(), problem_ptr->handle_ptr->get_stream()); f_t fractional_ratio = (f_t)(vars_to_fix.size()) / (f_t)problem_ptr->n_integer_vars; + // establish fixing priorities by looking at the number of equal integer assignments + // on other diverse solutions + rmm::device_uvector similarity_scores(vars_to_fix.size(), + problem_ptr->handle_ptr->get_stream()); + thrust::uninitialized_fill(problem_ptr->handle_ptr->get_thrust_policy(), + similarity_scores.begin(), + similarity_scores.end(), + 0); + for (i_t sol_idx = 1; sol_idx < (i_t)pop.size(); sol_idx++) { + auto& other_sol = pop[sol_idx]; + thrust::for_each( + problem_ptr->handle_ptr->get_thrust_policy(), + vars_to_fix.begin(), + vars_to_fix.end(), + [similarity_scores = similarity_scores.data(), + sol = best_sol.assignment.data(), + other_sol = other_sol.assignment.data(), + pb = problem_ptr->view()] __device__(i_t var_idx) { + if (pb.is_integer_var(var_idx) && pb.integer_equal(sol[var_idx], other_sol[var_idx])) { + similarity_scores[var_idx]++; + } + }); + } + thrust::default_random_engine g(seed + node_count); // shuffle fixing order thrust::shuffle( problem_ptr->handle_ptr->get_thrust_policy(), vars_to_fix.begin(), vars_to_fix.end(), g); + // sort variables to fix by similarity score, keep the shuffle on equivalent vars w/ stable sort + thrust::stable_sort( + problem_ptr->handle_ptr->get_thrust_policy(), + vars_to_fix.begin(), + vars_to_fix.end(), + [seed = this->seed, similarity_scores = similarity_scores.data()] __device__(i_t var_idx_1, + i_t var_idx_2) { + raft::random::PCGenerator rng(seed, var_idx_1, var_idx_2); + return similarity_scores[var_idx_1] > similarity_scores[var_idx_2]; + }); + // fix n first according to fractional ratio f_t rins_ratio = fixrate; i_t n_to_fix = std::max((int)(vars_to_fix.size() * std::min(rins_ratio, fractional_ratio)), 0); @@ -274,16 +309,17 @@ void rins_t::run_rins() branch_and_bound_solution.resize(branch_and_bound_problem.num_cols); // Fill in the settings for branch and bound branch_and_bound_settings.time_limit = time_limit; - branch_and_bound_settings.node_limit = 5000 + node_count / 100; // try harder as time goes on + // branch_and_bound_settings.node_limit = 5000 + node_count / 100; // try harder as time goes + // on branch_and_bound_settings.print_presolve_stats = false; branch_and_bound_settings.absolute_mip_gap_tol = context.settings.tolerances.absolute_mip_gap; // branch_and_bound_settings.relative_mip_gap_tol = // context.settings.tolerances.relative_mip_gap; branch_and_bound_settings.relative_mip_gap_tol = 0.03; // 3% - branch_and_bound_settings.integer_tol = context.settings.tolerances.integrality_tolerance; - // branch_and_bound_settings.num_threads = 2; - branch_and_bound_settings.num_bfs_threads = 6; - branch_and_bound_settings.num_diving_threads = 6; + branch_and_bound_settings.integer_tol = context.settings.tolerances.integrality_tolerance; + branch_and_bound_settings.num_threads = 2; + branch_and_bound_settings.num_bfs_threads = 1; + branch_and_bound_settings.num_diving_threads = 1; branch_and_bound_settings.solution_callback = [this](std::vector& solution, f_t objective) {}; dual_simplex::branch_and_bound_t branch_and_bound(branch_and_bound_problem, diff --git a/cpp/src/mip/diversity/lns/rins.cuh b/cpp/src/mip/diversity/lns/rins.cuh index 1d127762b..7fb2b3a3e 100644 --- a/cpp/src/mip/diversity/lns/rins.cuh +++ b/cpp/src/mip/diversity/lns/rins.cuh @@ -45,7 +45,7 @@ struct rins_settings_t { double min_time_limit = 10.; double max_time_limit = 20.; double default_time_limit = 6.; - bool objective_cut = false; + bool objective_cut = true; }; template From b612ef897f0c0cbbc1fdc2785637cf9aed784b53 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Mon, 20 Oct 2025 16:40:06 +0000 Subject: [PATCH 28/87] reduced fixing rates --- cpp/src/mip/diversity/lns/rins.cuh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cpp/src/mip/diversity/lns/rins.cuh b/cpp/src/mip/diversity/lns/rins.cuh index 7fb2b3a3e..5e0510986 100644 --- a/cpp/src/mip/diversity/lns/rins.cuh +++ b/cpp/src/mip/diversity/lns/rins.cuh @@ -40,8 +40,8 @@ struct rins_settings_t { int node_freq = 100; int nodes_after_later_improvement = 200; double min_fixrate = 0.3; - double max_fixrate = 0.95; - double default_fixrate = 0.75; + double max_fixrate = 0.8; + double default_fixrate = 0.6; double min_time_limit = 10.; double max_time_limit = 20.; double default_time_limit = 6.; @@ -92,7 +92,7 @@ class rins_t { std::vector lp_optimal_solution; - f_t fixrate{0.5}; + f_t fixrate{0.6}; i_t total_calls{0}; i_t total_success{0}; f_t time_limit{10.}; From 390ca44d27171b7e99d897fc1a22afd28af7c9de Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Mon, 20 Oct 2025 16:49:42 +0000 Subject: [PATCH 29/87] scratch fj restart --- cpp/src/mip/local_search/local_search.cu | 30 +++++++++++++++++++++++ cpp/src/mip/local_search/local_search.cuh | 2 +- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/cpp/src/mip/local_search/local_search.cu b/cpp/src/mip/local_search/local_search.cu index b4c89f072..5a426003d 100644 --- a/cpp/src/mip/local_search/local_search.cu +++ b/cpp/src/mip/local_search/local_search.cu @@ -161,6 +161,36 @@ bool local_search_t::do_fj_solve(solution_t& solution, timer_t timer(time_limit); + if (pop_ptr && pop_ptr->is_feasible() && + pop_ptr->best_feasible().get_objective() < local_search_best_obj) { + CUOPT_LOG_DEBUG( + "******* Local search obj %g vs best overall %g, should perform a restart (new best)", + context.problem_ptr->get_user_obj_from_solver_obj(local_search_best_obj), + context.problem_ptr->get_user_obj_from_solver_obj(pop_ptr->best_feasible().get_objective())); + local_search_best_obj = pop_ptr->best_feasible().get_objective(); + + std::vector default_weights(context.problem_ptr->n_constraints, 1.); + + scratch_cpu_fj[1].stop_cpu_solver(); + scratch_cpu_fj[1].wait_for_cpu_solver(); + scratch_cpu_fj[1].fj_cpu = + fj.create_cpu_climber(pop_ptr->best_feasible(), default_weights, default_weights, 0.); + scratch_cpu_fj[1].fj_cpu->log_prefix = "******* scratch " + std::to_string(1) + ": "; + scratch_cpu_fj[1].fj_cpu->improvement_callback = [this](f_t obj, + const std::vector& h_vec) { + pop_ptr->add_external_solution(h_vec, obj, solution_origin_t::CPUFJ); + if (scratch_cpu_fj[1].fj_cpu->h_best_objective < local_search_best_obj) { + local_search_best_obj = obj; + CUOPT_LOG_DEBUG("******* New local search best obj %g, best overall %g", + context.problem_ptr->get_user_obj_from_solver_obj(obj), + context.problem_ptr->get_user_obj_from_solver_obj( + pop_ptr->is_feasible() ? pop_ptr->best_feasible().get_objective() + : std::numeric_limits::max())); + } + }; + scratch_cpu_fj[1].start_cpu_solver(); + } + auto h_weights = cuopt::host_copy(in_fj.cstr_weights, solution.handle_ptr->get_stream()); auto h_objective_weight = in_fj.objective_weight.value(solution.handle_ptr->get_stream()); for (auto& cpu_fj : ls_cpu_fj) { diff --git a/cpp/src/mip/local_search/local_search.cuh b/cpp/src/mip/local_search/local_search.cuh index 076212a89..e48771a04 100644 --- a/cpp/src/mip/local_search/local_search.cuh +++ b/cpp/src/mip/local_search/local_search.cuh @@ -121,7 +121,7 @@ class local_search_t { std::mt19937 rng; std::array, 8> ls_cpu_fj; - std::array, 1> scratch_cpu_fj; + std::array, 2> scratch_cpu_fj; cpu_fj_thread_t scratch_cpu_fj_on_lp_opt; problem_t problem_with_objective_cut; bool cutting_plane_added_for_active_run{false}; From e2059b5f066a8fc2294244252ebeff662e4f53c5 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Tue, 21 Oct 2025 08:51:17 +0000 Subject: [PATCH 30/87] restoring tuning parameters --- cpp/src/dual_simplex/presolve.cpp | 2 +- cpp/src/mip/diversity/lns/rins.cu | 71 +++++++++++++++--------------- cpp/src/mip/diversity/lns/rins.cuh | 6 +-- 3 files changed, 40 insertions(+), 39 deletions(-) diff --git a/cpp/src/dual_simplex/presolve.cpp b/cpp/src/dual_simplex/presolve.cpp index a0a606617..d3ddc0a05 100644 --- a/cpp/src/dual_simplex/presolve.cpp +++ b/cpp/src/dual_simplex/presolve.cpp @@ -1381,7 +1381,7 @@ void uncrush_primal_solution(const user_problem_t& user_problem, { user_solution.resize(user_problem.num_cols); assert(problem.num_cols >= user_problem.num_cols); - // assert(solution.size() >= user_problem.num_cols); + assert(solution.size() >= user_problem.num_cols); std::copy(solution.begin(), solution.begin() + std::min((i_t)solution.size(), user_problem.num_cols), user_solution.data()); diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index e4d67cfca..f0b7978b1 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -191,50 +191,51 @@ void rins_t::run_rins() vars_to_fix.resize(end - vars_to_fix.begin(), problem_ptr->handle_ptr->get_stream()); f_t fractional_ratio = (f_t)(vars_to_fix.size()) / (f_t)problem_ptr->n_integer_vars; - // establish fixing priorities by looking at the number of equal integer assignments - // on other diverse solutions - rmm::device_uvector similarity_scores(vars_to_fix.size(), - problem_ptr->handle_ptr->get_stream()); - thrust::uninitialized_fill(problem_ptr->handle_ptr->get_thrust_policy(), - similarity_scores.begin(), - similarity_scores.end(), - 0); - for (i_t sol_idx = 1; sol_idx < (i_t)pop.size(); sol_idx++) { - auto& other_sol = pop[sol_idx]; - thrust::for_each( - problem_ptr->handle_ptr->get_thrust_policy(), - vars_to_fix.begin(), - vars_to_fix.end(), - [similarity_scores = similarity_scores.data(), - sol = best_sol.assignment.data(), - other_sol = other_sol.assignment.data(), - pb = problem_ptr->view()] __device__(i_t var_idx) { - if (pb.is_integer_var(var_idx) && pb.integer_equal(sol[var_idx], other_sol[var_idx])) { - similarity_scores[var_idx]++; - } - }); - } - thrust::default_random_engine g(seed + node_count); // shuffle fixing order thrust::shuffle( problem_ptr->handle_ptr->get_thrust_policy(), vars_to_fix.begin(), vars_to_fix.end(), g); - // sort variables to fix by similarity score, keep the shuffle on equivalent vars w/ stable sort - thrust::stable_sort( - problem_ptr->handle_ptr->get_thrust_policy(), - vars_to_fix.begin(), - vars_to_fix.end(), - [seed = this->seed, similarity_scores = similarity_scores.data()] __device__(i_t var_idx_1, - i_t var_idx_2) { - raft::random::PCGenerator rng(seed, var_idx_1, var_idx_2); - return similarity_scores[var_idx_1] > similarity_scores[var_idx_2]; - }); + // // establish fixing priorities by looking at the number of equal integer assignments + // // on other diverse solutions + // rmm::device_uvector similarity_scores(vars_to_fix.size(), + // problem_ptr->handle_ptr->get_stream()); + // thrust::uninitialized_fill(problem_ptr->handle_ptr->get_thrust_policy(), + // similarity_scores.begin(), + // similarity_scores.end(), + // 0); + // for (i_t sol_idx = 1; sol_idx < (i_t)pop.size(); sol_idx++) { + // auto& other_sol = pop[sol_idx]; + // thrust::for_each( + // problem_ptr->handle_ptr->get_thrust_policy(), + // vars_to_fix.begin(), + // vars_to_fix.end(), + // [similarity_scores = similarity_scores.data(), + // sol = best_sol.assignment.data(), + // other_sol = other_sol.assignment.data(), + // pb = problem_ptr->view()] __device__(i_t var_idx) { + // if (pb.is_integer_var(var_idx) && pb.integer_equal(sol[var_idx], other_sol[var_idx])) { + // similarity_scores[var_idx]++; + // } + // }); + // } + + // // sort variables to fix by similarity score, keep the shuffle on equivalent vars w/ stable + // sort thrust::stable_sort( + // problem_ptr->handle_ptr->get_thrust_policy(), + // vars_to_fix.begin(), + // vars_to_fix.end(), + // [seed = this->seed, similarity_scores = similarity_scores.data()] __device__(i_t var_idx_1, + // i_t var_idx_2) + // { + // raft::random::PCGenerator rng(seed, var_idx_1, var_idx_2); + // return similarity_scores[var_idx_1] > similarity_scores[var_idx_2]; + // }); // fix n first according to fractional ratio f_t rins_ratio = fixrate; - i_t n_to_fix = std::max((int)(vars_to_fix.size() * std::min(rins_ratio, fractional_ratio)), 0); + i_t n_to_fix = std::max((int)(vars_to_fix.size() * rins_ratio), 0); vars_to_fix.resize(n_to_fix, problem_ptr->handle_ptr->get_stream()); thrust::sort( problem_ptr->handle_ptr->get_thrust_policy(), vars_to_fix.begin(), vars_to_fix.end()); diff --git a/cpp/src/mip/diversity/lns/rins.cuh b/cpp/src/mip/diversity/lns/rins.cuh index 5e0510986..e8334c2be 100644 --- a/cpp/src/mip/diversity/lns/rins.cuh +++ b/cpp/src/mip/diversity/lns/rins.cuh @@ -39,9 +39,9 @@ class diversity_manager_t; struct rins_settings_t { int node_freq = 100; int nodes_after_later_improvement = 200; - double min_fixrate = 0.3; + double min_fixrate = 0.1; double max_fixrate = 0.8; - double default_fixrate = 0.6; + double default_fixrate = 0.5; double min_time_limit = 10.; double max_time_limit = 20.; double default_time_limit = 6.; @@ -92,7 +92,7 @@ class rins_t { std::vector lp_optimal_solution; - f_t fixrate{0.6}; + f_t fixrate{0.5}; i_t total_calls{0}; i_t total_success{0}; f_t time_limit{10.}; From a9250d4921723e5605179f4d1981839fa7271b50 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Tue, 21 Oct 2025 08:52:22 +0000 Subject: [PATCH 31/87] restoring --- cpp/src/mip/diversity/lns/rins.cu | 50 ++++++------------------------- 1 file changed, 9 insertions(+), 41 deletions(-) diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index f0b7978b1..a933dbbb1 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -191,47 +191,15 @@ void rins_t::run_rins() vars_to_fix.resize(end - vars_to_fix.begin(), problem_ptr->handle_ptr->get_stream()); f_t fractional_ratio = (f_t)(vars_to_fix.size()) / (f_t)problem_ptr->n_integer_vars; - thrust::default_random_engine g(seed + node_count); - - // shuffle fixing order - thrust::shuffle( - problem_ptr->handle_ptr->get_thrust_policy(), vars_to_fix.begin(), vars_to_fix.end(), g); - - // // establish fixing priorities by looking at the number of equal integer assignments - // // on other diverse solutions - // rmm::device_uvector similarity_scores(vars_to_fix.size(), - // problem_ptr->handle_ptr->get_stream()); - // thrust::uninitialized_fill(problem_ptr->handle_ptr->get_thrust_policy(), - // similarity_scores.begin(), - // similarity_scores.end(), - // 0); - // for (i_t sol_idx = 1; sol_idx < (i_t)pop.size(); sol_idx++) { - // auto& other_sol = pop[sol_idx]; - // thrust::for_each( - // problem_ptr->handle_ptr->get_thrust_policy(), - // vars_to_fix.begin(), - // vars_to_fix.end(), - // [similarity_scores = similarity_scores.data(), - // sol = best_sol.assignment.data(), - // other_sol = other_sol.assignment.data(), - // pb = problem_ptr->view()] __device__(i_t var_idx) { - // if (pb.is_integer_var(var_idx) && pb.integer_equal(sol[var_idx], other_sol[var_idx])) { - // similarity_scores[var_idx]++; - // } - // }); - // } - - // // sort variables to fix by similarity score, keep the shuffle on equivalent vars w/ stable - // sort thrust::stable_sort( - // problem_ptr->handle_ptr->get_thrust_policy(), - // vars_to_fix.begin(), - // vars_to_fix.end(), - // [seed = this->seed, similarity_scores = similarity_scores.data()] __device__(i_t var_idx_1, - // i_t var_idx_2) - // { - // raft::random::PCGenerator rng(seed, var_idx_1, var_idx_2); - // return similarity_scores[var_idx_1] > similarity_scores[var_idx_2]; - // }); + // sort by fixing priority + thrust::sort(problem_ptr->handle_ptr->get_thrust_policy(), + vars_to_fix.begin(), + vars_to_fix.end(), + [assignment = best_sol.assignment.data(), pb = problem_ptr->view()] __device__( + i_t var_1, i_t var_2) { + return get_fractionality_of_val(assignment[var_1]) < + get_fractionality_of_val(assignment[var_2]); + }); // fix n first according to fractional ratio f_t rins_ratio = fixrate; From 2504de8155b415a881ac163066c7d072f3d5e793 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Tue, 21 Oct 2025 08:53:29 +0000 Subject: [PATCH 32/87] restoring --- cpp/src/mip/diversity/lns/rins.cu | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index a933dbbb1..0f47f1ab1 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -330,6 +330,8 @@ void rins_t::run_rins() time_limit = std::min(time_limit + 2, settings.max_time_limit); } else if (branch_and_bound_status == dual_simplex::mip_status_t::INFEASIBLE) { CUOPT_LOG_DEBUG("RINS submip infeasible"); + fixrate = std::min(fixrate + 0.05, settings.max_fixrate); + time_limit = std::min(time_limit + 2, settings.max_time_limit); // do goldilocks update, decreasing fixrate fixrate = std::max(fixrate - 0.05, settings.min_fixrate); } else { From 15e8217a6e01d397018de83727e40b3532c05a63 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Tue, 21 Oct 2025 08:57:08 +0000 Subject: [PATCH 33/87] no restart --- cpp/src/mip/local_search/local_search.cu | 58 +++++++++++------------ cpp/src/mip/local_search/local_search.cuh | 2 +- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/cpp/src/mip/local_search/local_search.cu b/cpp/src/mip/local_search/local_search.cu index 5a426003d..bd012f772 100644 --- a/cpp/src/mip/local_search/local_search.cu +++ b/cpp/src/mip/local_search/local_search.cu @@ -161,35 +161,35 @@ bool local_search_t::do_fj_solve(solution_t& solution, timer_t timer(time_limit); - if (pop_ptr && pop_ptr->is_feasible() && - pop_ptr->best_feasible().get_objective() < local_search_best_obj) { - CUOPT_LOG_DEBUG( - "******* Local search obj %g vs best overall %g, should perform a restart (new best)", - context.problem_ptr->get_user_obj_from_solver_obj(local_search_best_obj), - context.problem_ptr->get_user_obj_from_solver_obj(pop_ptr->best_feasible().get_objective())); - local_search_best_obj = pop_ptr->best_feasible().get_objective(); - - std::vector default_weights(context.problem_ptr->n_constraints, 1.); - - scratch_cpu_fj[1].stop_cpu_solver(); - scratch_cpu_fj[1].wait_for_cpu_solver(); - scratch_cpu_fj[1].fj_cpu = - fj.create_cpu_climber(pop_ptr->best_feasible(), default_weights, default_weights, 0.); - scratch_cpu_fj[1].fj_cpu->log_prefix = "******* scratch " + std::to_string(1) + ": "; - scratch_cpu_fj[1].fj_cpu->improvement_callback = [this](f_t obj, - const std::vector& h_vec) { - pop_ptr->add_external_solution(h_vec, obj, solution_origin_t::CPUFJ); - if (scratch_cpu_fj[1].fj_cpu->h_best_objective < local_search_best_obj) { - local_search_best_obj = obj; - CUOPT_LOG_DEBUG("******* New local search best obj %g, best overall %g", - context.problem_ptr->get_user_obj_from_solver_obj(obj), - context.problem_ptr->get_user_obj_from_solver_obj( - pop_ptr->is_feasible() ? pop_ptr->best_feasible().get_objective() - : std::numeric_limits::max())); - } - }; - scratch_cpu_fj[1].start_cpu_solver(); - } + // if (pop_ptr && pop_ptr->is_feasible() && + // pop_ptr->best_feasible().get_objective() < local_search_best_obj) { + // CUOPT_LOG_DEBUG( + // "******* Local search obj %g vs best overall %g, should perform a restart (new best)", + // context.problem_ptr->get_user_obj_from_solver_obj(local_search_best_obj), + // context.problem_ptr->get_user_obj_from_solver_obj(pop_ptr->best_feasible().get_objective())); + // local_search_best_obj = pop_ptr->best_feasible().get_objective(); + + // std::vector default_weights(context.problem_ptr->n_constraints, 1.); + + // scratch_cpu_fj[1].stop_cpu_solver(); + // scratch_cpu_fj[1].wait_for_cpu_solver(); + // scratch_cpu_fj[1].fj_cpu = + // fj.create_cpu_climber(pop_ptr->best_feasible(), default_weights, default_weights, 0.); + // scratch_cpu_fj[1].fj_cpu->log_prefix = "******* scratch " + std::to_string(1) + ": + // "; scratch_cpu_fj[1].fj_cpu->improvement_callback = [this](f_t obj, + // const std::vector& h_vec) { + // pop_ptr->add_external_solution(h_vec, obj, solution_origin_t::CPUFJ); + // if (scratch_cpu_fj[1].fj_cpu->h_best_objective < local_search_best_obj) { + // local_search_best_obj = obj; + // CUOPT_LOG_DEBUG("******* New local search best obj %g, best overall %g", + // context.problem_ptr->get_user_obj_from_solver_obj(obj), + // context.problem_ptr->get_user_obj_from_solver_obj( + // pop_ptr->is_feasible() ? pop_ptr->best_feasible().get_objective() + // : std::numeric_limits::max())); + // } + // }; + // scratch_cpu_fj[1].start_cpu_solver(); + // } auto h_weights = cuopt::host_copy(in_fj.cstr_weights, solution.handle_ptr->get_stream()); auto h_objective_weight = in_fj.objective_weight.value(solution.handle_ptr->get_stream()); diff --git a/cpp/src/mip/local_search/local_search.cuh b/cpp/src/mip/local_search/local_search.cuh index e48771a04..076212a89 100644 --- a/cpp/src/mip/local_search/local_search.cuh +++ b/cpp/src/mip/local_search/local_search.cuh @@ -121,7 +121,7 @@ class local_search_t { std::mt19937 rng; std::array, 8> ls_cpu_fj; - std::array, 2> scratch_cpu_fj; + std::array, 1> scratch_cpu_fj; cpu_fj_thread_t scratch_cpu_fj_on_lp_opt; problem_t problem_with_objective_cut; bool cutting_plane_added_for_active_run{false}; From 686ad396b5d4e5b3094e55cc4818574a6df895a8 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Tue, 21 Oct 2025 10:31:39 +0000 Subject: [PATCH 34/87] full restore --- cpp/src/dual_simplex/branch_and_bound.cpp | 2 +- cpp/src/dual_simplex/presolve.cpp | 4 +-- .../dual_simplex/simplex_solver_settings.hpp | 2 +- cpp/src/mip/diversity/diversity_manager.cu | 2 -- cpp/src/mip/diversity/lns/rins.cu | 14 ++++----- cpp/src/mip/diversity/lns/rins.cuh | 2 +- cpp/src/mip/local_search/local_search.cu | 30 ------------------- cpp/src/mip/solver.cu | 7 ++--- 8 files changed, 14 insertions(+), 49 deletions(-) diff --git a/cpp/src/dual_simplex/branch_and_bound.cpp b/cpp/src/dual_simplex/branch_and_bound.cpp index 1887bccd0..8ecd69d43 100644 --- a/cpp/src/dual_simplex/branch_and_bound.cpp +++ b/cpp/src/dual_simplex/branch_and_bound.cpp @@ -642,7 +642,7 @@ node_status_t branch_and_bound_t::solve_node(search_tree_t& if (settings_.node_processed_callback != nullptr) { std::vector original_x; uncrush_primal_solution(original_problem_, original_lp_, leaf_solution.x, original_x); - settings_.node_processed_callback(node_ptr->node_id, original_x, leaf_objective); + settings_.node_processed_callback(original_x, leaf_objective); } if (leaf_num_fractional == 0) { diff --git a/cpp/src/dual_simplex/presolve.cpp b/cpp/src/dual_simplex/presolve.cpp index d3ddc0a05..7159eeea9 100644 --- a/cpp/src/dual_simplex/presolve.cpp +++ b/cpp/src/dual_simplex/presolve.cpp @@ -1382,9 +1382,7 @@ void uncrush_primal_solution(const user_problem_t& user_problem, user_solution.resize(user_problem.num_cols); assert(problem.num_cols >= user_problem.num_cols); assert(solution.size() >= user_problem.num_cols); - std::copy(solution.begin(), - solution.begin() + std::min((i_t)solution.size(), user_problem.num_cols), - user_solution.data()); + std::copy(solution.begin(), solution.begin() + user_problem.num_cols, user_solution.data()); } template diff --git a/cpp/src/dual_simplex/simplex_solver_settings.hpp b/cpp/src/dual_simplex/simplex_solver_settings.hpp index f3f032c27..99b86030b 100644 --- a/cpp/src/dual_simplex/simplex_solver_settings.hpp +++ b/cpp/src/dual_simplex/simplex_solver_settings.hpp @@ -146,7 +146,7 @@ struct simplex_solver_settings_t { i_t num_diving_threads; // number of threads dedicated to diving i_t inside_mip; // 0 if outside MIP, 1 if inside MIP at root node, 2 if inside MIP at leaf node std::function&, f_t)> solution_callback; - std::function&, f_t)> node_processed_callback; + std::function&, f_t)> node_processed_callback; std::function heuristic_preemption_callback; std::function&, std::vector&, f_t)> set_simplex_solution_callback; mutable logger_t log; diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index c6fe18ad8..198054ea2 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -465,8 +465,6 @@ solution_t diversity_manager_t::run_solver() } main_loop(); - auto new_sol_vector = population.get_external_solutions(); - population.add_solutions_from_vec(std::move(new_sol_vector)); return population.best_feasible(); }; diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index 0f47f1ab1..b95ea8e84 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -114,13 +114,13 @@ void rins_t::new_best_incumbent_callback(const std::vector& solut // node_callback may be called from different threads(i think?). need lock protection template -void rins_t::node_callback(i_t node_id, const std::vector& solution, f_t objective) +void rins_t::node_callback(const std::vector& solution, f_t objective) { - node_count++; - if (!enabled) return; - std::lock_guard lock(rins_mutex); + // std::lock_guard lock(rins_mutex); + + node_count++; // CUOPT_LOG_INFO("RINS callback node count %d, node count at last improvement %d, node count at // last rins %d", node_count.load(), node_count_at_last_improvement.load(), // node_count_at_last_rins.load()); @@ -385,10 +385,10 @@ void rins_t::run_rins() total_success++; } cuopt_assert(best_sol.assignment.size() == sol_size_before_rins, "Assignment size mismatch"); - cuopt_assert(best_sol.assignment.size() == problem_ptr->n_variables, - "Assignment size mismatch"); // TODO: figure out WHY??? 1 - if (best_sol.get_objective() < dm.population.best_feasible_objective) + if ((int)best_sol.assignment.size() == sol_size_before_rins && + (int)best_sol.assignment.size() == problem_ptr->n_variables && + best_sol.get_objective() < dm.population.best_feasible_objective) dm.population.add_external_solution( best_sol.get_host_assignment(), best_sol.get_objective(), solution_origin_t::RINS); } diff --git a/cpp/src/mip/diversity/lns/rins.cuh b/cpp/src/mip/diversity/lns/rins.cuh index e8334c2be..82ba0bddc 100644 --- a/cpp/src/mip/diversity/lns/rins.cuh +++ b/cpp/src/mip/diversity/lns/rins.cuh @@ -80,7 +80,7 @@ class rins_t { diversity_manager_t& dm, rins_settings_t settings = rins_settings_t()); - void node_callback(i_t node_id, const std::vector& solution, f_t objective); + void node_callback(const std::vector& solution, f_t objective); void new_best_incumbent_callback(const std::vector& solution); void run_rins(); diff --git a/cpp/src/mip/local_search/local_search.cu b/cpp/src/mip/local_search/local_search.cu index bd012f772..b4c89f072 100644 --- a/cpp/src/mip/local_search/local_search.cu +++ b/cpp/src/mip/local_search/local_search.cu @@ -161,36 +161,6 @@ bool local_search_t::do_fj_solve(solution_t& solution, timer_t timer(time_limit); - // if (pop_ptr && pop_ptr->is_feasible() && - // pop_ptr->best_feasible().get_objective() < local_search_best_obj) { - // CUOPT_LOG_DEBUG( - // "******* Local search obj %g vs best overall %g, should perform a restart (new best)", - // context.problem_ptr->get_user_obj_from_solver_obj(local_search_best_obj), - // context.problem_ptr->get_user_obj_from_solver_obj(pop_ptr->best_feasible().get_objective())); - // local_search_best_obj = pop_ptr->best_feasible().get_objective(); - - // std::vector default_weights(context.problem_ptr->n_constraints, 1.); - - // scratch_cpu_fj[1].stop_cpu_solver(); - // scratch_cpu_fj[1].wait_for_cpu_solver(); - // scratch_cpu_fj[1].fj_cpu = - // fj.create_cpu_climber(pop_ptr->best_feasible(), default_weights, default_weights, 0.); - // scratch_cpu_fj[1].fj_cpu->log_prefix = "******* scratch " + std::to_string(1) + ": - // "; scratch_cpu_fj[1].fj_cpu->improvement_callback = [this](f_t obj, - // const std::vector& h_vec) { - // pop_ptr->add_external_solution(h_vec, obj, solution_origin_t::CPUFJ); - // if (scratch_cpu_fj[1].fj_cpu->h_best_objective < local_search_best_obj) { - // local_search_best_obj = obj; - // CUOPT_LOG_DEBUG("******* New local search best obj %g, best overall %g", - // context.problem_ptr->get_user_obj_from_solver_obj(obj), - // context.problem_ptr->get_user_obj_from_solver_obj( - // pop_ptr->is_feasible() ? pop_ptr->best_feasible().get_objective() - // : std::numeric_limits::max())); - // } - // }; - // scratch_cpu_fj[1].start_cpu_solver(); - // } - auto h_weights = cuopt::host_copy(in_fj.cstr_weights, solution.handle_ptr->get_stream()); auto h_objective_weight = in_fj.objective_weight.value(solution.handle_ptr->get_stream()); for (auto& cpu_fj : ls_cpu_fj) { diff --git a/cpp/src/mip/solver.cu b/cpp/src/mip/solver.cu index c0e99cad3..9e0a690a1 100644 --- a/cpp/src/mip/solver.cu +++ b/cpp/src/mip/solver.cu @@ -84,9 +84,9 @@ struct branch_and_bound_solution_helper_t { dm->set_simplex_solution(solution, dual_solution, objective); } - void node_processed_callback(i_t node_id, std::vector& solution, f_t objective) + void node_processed_callback(std::vector& solution, f_t objective) { - dm->rins.node_callback(node_id, solution, objective); + dm->rins.node_callback(solution, objective); } void preempt_heuristic_solver() { dm->population.preempt_heuristic_solver(); } @@ -211,8 +211,7 @@ solution_t mip_solver_t::run_solver() std::bind(&branch_and_bound_solution_helper_t::node_processed_callback, &solution_helper, std::placeholders::_1, - std::placeholders::_2, - std::placeholders::_3); + std::placeholders::_2); // Create the branch and bound object branch_and_bound = std::make_unique>( From 170463133f23b4c37c4c2c6723a3f15180f683fc Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Tue, 21 Oct 2025 10:32:22 +0000 Subject: [PATCH 35/87] bump 1 --- cpp/src/mip/diversity/diversity_manager.cu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index 198054ea2..5b544b13d 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -471,7 +471,7 @@ solution_t diversity_manager_t::run_solver() template void diversity_manager_t::diversity_step() { - // TODO when the solver is faster, increase this number + // TODO when the solver is faster, increase this number 1 const i_t max_iterations_without_improvement = diversity_config.max_iterations_without_improvement; bool improved = true; From 3cf1c098ad4f96dece97506b844d0677eb5aca82 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Tue, 21 Oct 2025 10:32:32 +0000 Subject: [PATCH 36/87] bump 2 --- cpp/src/mip/diversity/diversity_manager.cu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index 5b544b13d..c23df5be3 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -471,7 +471,7 @@ solution_t diversity_manager_t::run_solver() template void diversity_manager_t::diversity_step() { - // TODO when the solver is faster, increase this number 1 + // TODO when the solver is faster, increase this number 2 const i_t max_iterations_without_improvement = diversity_config.max_iterations_without_improvement; bool improved = true; From b95679339498c48c5eda96d2da9e00e4f451a94e Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Tue, 21 Oct 2025 10:32:49 +0000 Subject: [PATCH 37/87] bump 3 --- cpp/src/mip/diversity/diversity_manager.cu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index c23df5be3..3acd55169 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -471,7 +471,7 @@ solution_t diversity_manager_t::run_solver() template void diversity_manager_t::diversity_step() { - // TODO when the solver is faster, increase this number 2 + // TODO when the solver is faster, increase this number 3 const i_t max_iterations_without_improvement = diversity_config.max_iterations_without_improvement; bool improved = true; From 72e060594a6bf427e521ba10636758bf7b2ee4e6 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Tue, 21 Oct 2025 14:16:50 +0000 Subject: [PATCH 38/87] deadlock fix --- cpp/src/mip/diversity/diversity_manager.cuh | 3 +- cpp/src/mip/diversity/lns/rins.cu | 6 +++- cpp/src/mip/feasibility_jump/fj_cpu.cu | 35 ++++++++++++--------- cpp/src/mip/feasibility_jump/fj_cpu.cuh | 3 +- 4 files changed, 30 insertions(+), 17 deletions(-) diff --git a/cpp/src/mip/diversity/diversity_manager.cuh b/cpp/src/mip/diversity/diversity_manager.cuh index 6ef3c5cef..8b8f7632a 100644 --- a/cpp/src/mip/diversity/diversity_manager.cuh +++ b/cpp/src/mip/diversity/diversity_manager.cuh @@ -95,7 +95,6 @@ class diversity_manager_t { rmm::device_uvector lp_dual_optimal_solution; std::atomic simplex_solution_exists{false}; local_search_t ls; - rins_t rins; cuopt::timer_t timer; bound_prop_recombiner_t bound_prop_recombiner; fp_recombiner_t fp_recombiner; @@ -114,6 +113,8 @@ class diversity_manager_t { // atomic for signalling pdlp to stop volatile int global_concurrent_halt{0}; + rins_t rins; + bool run_only_ls_recombiner{false}; bool run_only_bp_recombiner{false}; bool run_only_fp_recombiner{false}; diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index b95ea8e84..648fb7f71 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -223,6 +223,7 @@ void rins_t::run_rins() total_calls++; node_count_at_last_rins = node_count.load(); + time_limit = std::min(time_limit, dm.timer.remaining_time()); CUOPT_LOG_DEBUG("Running RINS on solution with objective %g, fixing %d/%d", best_sol.get_user_objective(), vars_to_fix.size(), @@ -265,7 +266,10 @@ void rins_t::run_rins() cpu_fj_thread_t cpu_fj_thread; cpu_fj_thread.fj_cpu = fj.create_cpu_climber( fj_solution, default_weights, default_weights, 0., fj_settings_t{}, true); - cpu_fj_thread.fj_ptr = &fj; + cpu_fj_thread.fj_ptr = &fj; + cpu_fj_thread.fj_cpu->log_prefix = "[RINS] "; + // cpu_fj_thread.fj_cpu->log_interval = 100; + cpu_fj_thread.time_limit = time_limit; cpu_fj_thread.start_cpu_solver(); // run sub-mip diff --git a/cpp/src/mip/feasibility_jump/fj_cpu.cu b/cpp/src/mip/feasibility_jump/fj_cpu.cu index 5471a9c67..d2499aa80 100644 --- a/cpp/src/mip/feasibility_jump/fj_cpu.cu +++ b/cpp/src/mip/feasibility_jump/fj_cpu.cu @@ -1070,30 +1070,35 @@ template void cpu_fj_thread_t::cpu_worker_thread() { while (!cpu_thread_terminate) { - // Wait for start signal { std::unique_lock lock(cpu_mutex); cpu_cv.wait(lock, [this] { return cpu_thread_should_start || cpu_thread_terminate; }); + + if (cpu_thread_terminate) break; + + cpu_thread_done = false; + cpu_thread_should_start = false; } - if (cpu_thread_terminate) break; + // Run CPU solver (outside lock) + bool solution_found = fj_ptr->cpu_solve(*fj_cpu, time_limit); - // Run CPU solver { - raft::common::nvtx::range fun_scope("Running CPU FJ"); - cpu_fj_solution_found = fj_ptr->cpu_solve(*fj_cpu); + std::lock_guard lock(cpu_mutex); + cpu_fj_solution_found = solution_found; + cpu_thread_done = true; } - - cpu_thread_should_start = false; - cpu_thread_done = true; } } template void cpu_fj_thread_t::kill_cpu_solver() { - cpu_thread_terminate = true; - if (fj_cpu) fj_cpu->halted = true; + { + std::lock_guard lock(cpu_mutex); + cpu_thread_terminate = true; + if (fj_cpu) fj_cpu->halted = true; + } cpu_cv.notify_one(); cpu_worker.join(); } @@ -1102,10 +1107,12 @@ template void cpu_fj_thread_t::start_cpu_solver() { cuopt_assert(fj_cpu != nullptr, "fj_cpu must not be null"); - // Reset flags - cpu_thread_done = false; - cpu_thread_should_start = true; - fj_cpu->halted = false; + { + std::lock_guard lock(cpu_mutex); + cpu_thread_done = false; + cpu_thread_should_start = true; + fj_cpu->halted = false; + } cpu_cv.notify_one(); } diff --git a/cpp/src/mip/feasibility_jump/fj_cpu.cuh b/cpp/src/mip/feasibility_jump/fj_cpu.cuh index cfee4c4c4..8e856ce45 100644 --- a/cpp/src/mip/feasibility_jump/fj_cpu.cuh +++ b/cpp/src/mip/feasibility_jump/fj_cpu.cuh @@ -143,7 +143,8 @@ struct cpu_fj_thread_t { std::atomic cpu_thread_should_start{false}; std::atomic cpu_thread_done{false}; std::atomic cpu_thread_terminate{false}; - bool cpu_fj_solution_found{false}; + std::atomic cpu_fj_solution_found{false}; + f_t time_limit{+std::numeric_limits::infinity()}; std::unique_ptr> fj_cpu; fj_t* fj_ptr{nullptr}; }; From 3c90ce666205b87758466dd50b54b68ae02c0c98 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Tue, 21 Oct 2025 16:11:11 +0000 Subject: [PATCH 39/87] bump 1 --- cpp/src/mip/feasibility_jump/fj_cpu.cu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/mip/feasibility_jump/fj_cpu.cu b/cpp/src/mip/feasibility_jump/fj_cpu.cu index d2499aa80..8be0d3127 100644 --- a/cpp/src/mip/feasibility_jump/fj_cpu.cu +++ b/cpp/src/mip/feasibility_jump/fj_cpu.cu @@ -1080,7 +1080,7 @@ void cpu_fj_thread_t::cpu_worker_thread() cpu_thread_should_start = false; } - // Run CPU solver (outside lock) + // Run CPU solver (outside lock) 1 bool solution_found = fj_ptr->cpu_solve(*fj_cpu, time_limit); { From 44a85498f84749bcd2dbd7a84ba96ee34ce17ae3 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Tue, 21 Oct 2025 16:11:31 +0000 Subject: [PATCH 40/87] bump2 --- cpp/src/mip/feasibility_jump/fj_cpu.cu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/mip/feasibility_jump/fj_cpu.cu b/cpp/src/mip/feasibility_jump/fj_cpu.cu index 8be0d3127..d70032676 100644 --- a/cpp/src/mip/feasibility_jump/fj_cpu.cu +++ b/cpp/src/mip/feasibility_jump/fj_cpu.cu @@ -1080,7 +1080,7 @@ void cpu_fj_thread_t::cpu_worker_thread() cpu_thread_should_start = false; } - // Run CPU solver (outside lock) 1 + // Run CPU solver (outside lock) 2 bool solution_found = fj_ptr->cpu_solve(*fj_cpu, time_limit); { From 7ba4e0bd4bf0042ea3267a2d1cee1c8cf2631529 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Tue, 21 Oct 2025 16:11:51 +0000 Subject: [PATCH 41/87] bump3 --- cpp/src/mip/feasibility_jump/fj_cpu.cu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/mip/feasibility_jump/fj_cpu.cu b/cpp/src/mip/feasibility_jump/fj_cpu.cu index d70032676..895ddc627 100644 --- a/cpp/src/mip/feasibility_jump/fj_cpu.cu +++ b/cpp/src/mip/feasibility_jump/fj_cpu.cu @@ -1080,7 +1080,7 @@ void cpu_fj_thread_t::cpu_worker_thread() cpu_thread_should_start = false; } - // Run CPU solver (outside lock) 2 + // Run CPU solver (outside lock) 3 bool solution_found = fj_ptr->cpu_solve(*fj_cpu, time_limit); { From bbb0d79ba51eeefd4f52d3493489b7ff2f931d79 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Tue, 21 Oct 2025 17:57:57 +0000 Subject: [PATCH 42/87] Revert "restoring" This reverts commit 24785f526f1525755c518556b82315eb70379ea6. --- cpp/src/mip/diversity/lns/rins.cu | 50 +++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index 648fb7f71..d51ca8cf8 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -191,15 +191,47 @@ void rins_t::run_rins() vars_to_fix.resize(end - vars_to_fix.begin(), problem_ptr->handle_ptr->get_stream()); f_t fractional_ratio = (f_t)(vars_to_fix.size()) / (f_t)problem_ptr->n_integer_vars; - // sort by fixing priority - thrust::sort(problem_ptr->handle_ptr->get_thrust_policy(), - vars_to_fix.begin(), - vars_to_fix.end(), - [assignment = best_sol.assignment.data(), pb = problem_ptr->view()] __device__( - i_t var_1, i_t var_2) { - return get_fractionality_of_val(assignment[var_1]) < - get_fractionality_of_val(assignment[var_2]); - }); + thrust::default_random_engine g(seed + node_count); + + // shuffle fixing order + thrust::shuffle( + problem_ptr->handle_ptr->get_thrust_policy(), vars_to_fix.begin(), vars_to_fix.end(), g); + + // // establish fixing priorities by looking at the number of equal integer assignments + // // on other diverse solutions + // rmm::device_uvector similarity_scores(vars_to_fix.size(), + // problem_ptr->handle_ptr->get_stream()); + // thrust::uninitialized_fill(problem_ptr->handle_ptr->get_thrust_policy(), + // similarity_scores.begin(), + // similarity_scores.end(), + // 0); + // for (i_t sol_idx = 1; sol_idx < (i_t)pop.size(); sol_idx++) { + // auto& other_sol = pop[sol_idx]; + // thrust::for_each( + // problem_ptr->handle_ptr->get_thrust_policy(), + // vars_to_fix.begin(), + // vars_to_fix.end(), + // [similarity_scores = similarity_scores.data(), + // sol = best_sol.assignment.data(), + // other_sol = other_sol.assignment.data(), + // pb = problem_ptr->view()] __device__(i_t var_idx) { + // if (pb.is_integer_var(var_idx) && pb.integer_equal(sol[var_idx], other_sol[var_idx])) { + // similarity_scores[var_idx]++; + // } + // }); + // } + + // // sort variables to fix by similarity score, keep the shuffle on equivalent vars w/ stable + // sort thrust::stable_sort( + // problem_ptr->handle_ptr->get_thrust_policy(), + // vars_to_fix.begin(), + // vars_to_fix.end(), + // [seed = this->seed, similarity_scores = similarity_scores.data()] __device__(i_t var_idx_1, + // i_t var_idx_2) + // { + // raft::random::PCGenerator rng(seed, var_idx_1, var_idx_2); + // return similarity_scores[var_idx_1] > similarity_scores[var_idx_2]; + // }); // fix n first according to fractional ratio f_t rins_ratio = fixrate; From 093477590f619a1c95dae14bdf23621ccf1d2aec Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Tue, 21 Oct 2025 17:58:25 +0000 Subject: [PATCH 43/87] Revert "restoring" This reverts commit a5796a719e6d752f2a974948b59d479b59fe4589. --- cpp/src/mip/diversity/lns/rins.cu | 2 -- 1 file changed, 2 deletions(-) diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index d51ca8cf8..7b2d01e51 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -366,8 +366,6 @@ void rins_t::run_rins() time_limit = std::min(time_limit + 2, settings.max_time_limit); } else if (branch_and_bound_status == dual_simplex::mip_status_t::INFEASIBLE) { CUOPT_LOG_DEBUG("RINS submip infeasible"); - fixrate = std::min(fixrate + 0.05, settings.max_fixrate); - time_limit = std::min(time_limit + 2, settings.max_time_limit); // do goldilocks update, decreasing fixrate fixrate = std::max(fixrate - 0.05, settings.min_fixrate); } else { From 7b19151c1bedd0162ce30c38bf1f798d1f154c80 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Tue, 21 Oct 2025 18:02:18 +0000 Subject: [PATCH 44/87] tmp --- cpp/src/dual_simplex/presolve.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cpp/src/dual_simplex/presolve.cpp b/cpp/src/dual_simplex/presolve.cpp index 7159eeea9..d3ddc0a05 100644 --- a/cpp/src/dual_simplex/presolve.cpp +++ b/cpp/src/dual_simplex/presolve.cpp @@ -1382,7 +1382,9 @@ void uncrush_primal_solution(const user_problem_t& user_problem, user_solution.resize(user_problem.num_cols); assert(problem.num_cols >= user_problem.num_cols); assert(solution.size() >= user_problem.num_cols); - std::copy(solution.begin(), solution.begin() + user_problem.num_cols, user_solution.data()); + std::copy(solution.begin(), + solution.begin() + std::min((i_t)solution.size(), user_problem.num_cols), + user_solution.data()); } template From c9ff2699e38b25d369b0fda5ec414ded2d33f732 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Wed, 22 Oct 2025 09:55:28 +0000 Subject: [PATCH 45/87] fix cpufj sol queue bug --- cpp/src/mip/diversity/population.cu | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/cpp/src/mip/diversity/population.cu b/cpp/src/mip/diversity/population.cu index 4f8845dc1..5a841bc33 100644 --- a/cpp/src/mip/diversity/population.cu +++ b/cpp/src/mip/diversity/population.cu @@ -164,17 +164,14 @@ void population_t::add_external_solution(const std::vector& solut } // Prevent CPUFJ scratch solutions from flooding the queue - if (external_solution_queue_cpufj.size() >= 10) { + if (external_solution_queue_cpufj.size() > 10) { auto worst_obj_it = std::max_element(external_solution_queue_cpufj.begin(), external_solution_queue_cpufj.end(), [](const external_solution_t& a, const external_solution_t& b) { return a.objective < b.objective; }); - if (objective > worst_obj_it->objective) return; - auto worst_obj_idx = std::distance(external_solution_queue_cpufj.begin(), worst_obj_it); - - external_solution_queue_cpufj.erase(external_solution_queue_cpufj.begin() + worst_obj_idx); + external_solution_queue_cpufj.erase(worst_obj_it); } CUOPT_LOG_DEBUG("%s added a solution to population, solution queue size %lu with objective %g", From badbdd26515b8c5933c18d729b60434526ee721c Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Wed, 22 Oct 2025 10:15:34 +0000 Subject: [PATCH 46/87] cleanup --- cpp/src/dual_simplex/branch_and_bound.cpp | 13 +++--- cpp/src/mip/diversity/diversity_manager.cu | 2 +- cpp/src/mip/diversity/lns/rins.cu | 44 ++++++------------- cpp/src/mip/diversity/lns/rins.cuh | 3 +- .../recombiners/bound_prop_recombiner.cuh | 6 +-- cpp/src/mip/solver.cu | 2 +- 6 files changed, 27 insertions(+), 43 deletions(-) diff --git a/cpp/src/dual_simplex/branch_and_bound.cpp b/cpp/src/dual_simplex/branch_and_bound.cpp index 8ecd69d43..b9e739229 100644 --- a/cpp/src/dual_simplex/branch_and_bound.cpp +++ b/cpp/src/dual_simplex/branch_and_bound.cpp @@ -854,7 +854,7 @@ void branch_and_bound_t::explore_subtree(i_t task_id, return; } if (stats_.nodes_explored >= settings_.node_limit) { - status_ = mip_exploration_status_t::TIME_LIMIT; + status_ = mip_exploration_status_t::NODE_LIMIT; return; } @@ -1034,12 +1034,11 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut std::string log_prefix) { logger_t log; - log.log = false; - log.log_prefix = log_prefix; - settings_.log.log_prefix = log_prefix; - status_ = mip_exploration_status_t::UNSET; - stats_.nodes_unexplored = 0; - stats_.nodes_explored = 0; + log.log = false; + log.log_prefix = settings_.log.log_prefix = log_prefix; + status_ = mip_exploration_status_t::UNSET; + stats_.nodes_unexplored = 0; + stats_.nodes_explored = 0; if (guess_.size() != 0) { std::vector crushed_guess; diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index 733f7be91..5481c2679 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -418,7 +418,7 @@ solution_t diversity_manager_t::run_solver() run_fj_alone(sol); return sol; } - rins.enabled = true; + rins.enable(); generate_solution(timer.remaining_time(), false); if (timer.check_time_limit()) { diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index 263475e08..f3b975f82 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -63,10 +63,7 @@ void rins_thread_t::cpu_worker_thread() // Run CPU solver { raft::common::nvtx::range fun_scope("Running RINS"); - // bleh - // printf("-------- RINS triggered\n"); rins_ptr->run_rins(); - // printf("-------- RINS done\n"); } cpu_thread_should_start = false; @@ -108,7 +105,6 @@ bool rins_thread_t::wait_for_cpu_solver() template void rins_t::new_best_incumbent_callback(const std::vector& solution) { - // printf("-------- new best incumbent callback w/ solution %d\n", (int)solution.size()); node_count_at_last_improvement = node_count.load(); } @@ -118,35 +114,29 @@ void rins_t::node_callback(const std::vector& solution, f_t objec { if (!enabled) return; - // std::lock_guard lock(rins_mutex); - node_count++; - // CUOPT_LOG_INFO("RINS callback node count %d, node count at last improvement %d, node count at - // last rins %d", node_count.load(), node_count_at_last_improvement.load(), - // node_count_at_last_rins.load()); - // printf( - // "-------- node processed w/ solution %d and objective %e\n", (int)solution.size(), - // objective); if (node_count - node_count_at_last_improvement < settings.nodes_after_later_improvement) return; if (node_count - node_count_at_last_rins > settings.node_freq) { - // printf("-------- rins triggered at node %d\n", node_count.load()); + std::lock_guard lock(rins_mutex); if (!rins_thread->cpu_thread_should_start && dm.population.current_size() > 0 && dm.population.is_feasible()) { lp_optimal_solution = solution; rins_thread->start_cpu_solver(); - } else { - // printf("-------- rins thread not done\n"); } } } +template +void rins_t::enable() +{ + enabled = true; +} + template void rins_t::run_rins() { - // solutions may have been generated by b&b or cpufj already. take a look and RINS if so - // RINS if (total_calls == 0) cudaSetDevice(context.handle_ptr->get_device()); if (!dm.population.is_feasible()) return; @@ -157,7 +147,7 @@ void rins_t::run_rins() if (pop.size() > 0) { auto best_sol = pop[0]; // also scour through the external solution queue to potentially - // nab better solutiions early + // nab better solutions early auto queues = std::array{std::ref(dm.population.external_solution_queue), std::ref(dm.population.external_solution_queue_cpufj)}; for (auto& queue : queues) { @@ -167,7 +157,7 @@ void rins_t::run_rins() best_sol.compute_feasibility(); } } - // TODO: gpu operations should probably be done on a separate stream here + if (!best_sol.get_feasible()) { return; } i_t sol_size_before_rins = best_sol.assignment.size(); auto lp_opt_device = @@ -258,7 +248,6 @@ void rins_t::run_rins() trivial_presolve(fixed_problem); fixed_problem.check_problem_representation(true); - // TODO: try running CPUFJ in a thread as well? mip_solver_context_t fj_context( best_sol.handle_ptr, &fixed_problem, context.settings, context.scaling); fj_t fj(fj_context); @@ -270,8 +259,7 @@ void rins_t::run_rins() fj_solution, default_weights, default_weights, 0., fj_settings_t{}, true); cpu_fj_thread.fj_ptr = &fj; cpu_fj_thread.fj_cpu->log_prefix = "[RINS] "; - // cpu_fj_thread.fj_cpu->log_interval = 100; - cpu_fj_thread.time_limit = time_limit; + cpu_fj_thread.time_limit = time_limit; cpu_fj_thread.start_cpu_solver(); // run sub-mip @@ -288,15 +276,11 @@ void rins_t::run_rins() // on branch_and_bound_settings.print_presolve_stats = false; branch_and_bound_settings.absolute_mip_gap_tol = context.settings.tolerances.absolute_mip_gap; - // branch_and_bound_settings.relative_mip_gap_tol = - // context.settings.tolerances.relative_mip_gap; branch_and_bound_settings.relative_mip_gap_tol = 0.03; // 3% branch_and_bound_settings.integer_tol = context.settings.tolerances.integrality_tolerance; branch_and_bound_settings.num_threads = 2; branch_and_bound_settings.num_bfs_threads = 1; branch_and_bound_settings.num_diving_threads = 1; - branch_and_bound_settings.solution_callback = [this](std::vector& solution, - f_t objective) {}; dual_simplex::branch_and_bound_t branch_and_bound(branch_and_bound_problem, branch_and_bound_settings); branch_and_bound.set_initial_guess(cuopt::host_copy(fixed_assignment)); @@ -389,12 +373,12 @@ void rins_t::run_rins() total_success++; } cuopt_assert(best_sol.assignment.size() == sol_size_before_rins, "Assignment size mismatch"); - // TODO: figure out WHY??? 1 - if ((int)best_sol.assignment.size() == sol_size_before_rins && - (int)best_sol.assignment.size() == problem_ptr->n_variables && - best_sol.get_objective() < dm.population.best_feasible_objective) + cuopt_assert(best_sol.assignment.size() == problem_ptr->n_variables, + "Assignment size mismatch"); + if (best_sol.get_objective() < dm.population.best_feasible_objective) { dm.population.add_external_solution( best_sol.get_host_assignment(), best_sol.get_objective(), solution_origin_t::RINS); + } } } CUOPT_LOG_DEBUG("RINS calls/successes %d/%d", total_calls, total_success); diff --git a/cpp/src/mip/diversity/lns/rins.cuh b/cpp/src/mip/diversity/lns/rins.cuh index 0774671cd..97c9f62fe 100644 --- a/cpp/src/mip/diversity/lns/rins.cuh +++ b/cpp/src/mip/diversity/lns/rins.cuh @@ -60,7 +60,7 @@ struct rins_thread_t { void cpu_worker_thread(); void start_cpu_solver(); void stop_cpu_solver(); - bool wait_for_cpu_solver(); // return feasibility + bool wait_for_cpu_solver(); void kill_cpu_solver(); std::thread cpu_worker; @@ -83,6 +83,7 @@ class rins_t { void node_callback(const std::vector& solution, f_t objective); void new_best_incumbent_callback(const std::vector& solution); + void enable(); void run_rins(); diff --git a/cpp/src/mip/diversity/recombiners/bound_prop_recombiner.cuh b/cpp/src/mip/diversity/recombiners/bound_prop_recombiner.cuh index e96913ffa..6aca26fb6 100644 --- a/cpp/src/mip/diversity/recombiners/bound_prop_recombiner.cuh +++ b/cpp/src/mip/diversity/recombiners/bound_prop_recombiner.cuh @@ -213,9 +213,9 @@ class bound_prop_recombiner_t : public recombiner_t { offspring.assignment = std::move(old_assignment); offspring.handle_ptr->sync_stream(); offspring.unfix_variables(fixed_assignment, variable_map); - // cuopt_func_call(bool feasible_after_unfix = offspring.get_feasible()); - // cuopt_assert(feasible_after_unfix == feasible_after_bounds_prop, - // "Feasible after unfix should be same as feasible after bounds prop!"); + cuopt_func_call(bool feasible_after_unfix = offspring.get_feasible()); + cuopt_assert(feasible_after_unfix == feasible_after_bounds_prop, + "Feasible after unfix should be same as feasible after bounds prop!"); a.handle_ptr->sync_stream(); } else { timer_t timer(bp_recombiner_config_t::bounds_prop_time_limit); diff --git a/cpp/src/mip/solver.cu b/cpp/src/mip/solver.cu index 9e0a690a1..08786aedd 100644 --- a/cpp/src/mip/solver.cu +++ b/cpp/src/mip/solver.cu @@ -231,7 +231,7 @@ solution_t mip_solver_t::run_solver() &dual_simplex::branch_and_bound_t::solve, branch_and_bound.get(), std::ref(branch_and_bound_solution), - "aaaaaaa"); + ""); } // Start the primal heuristics From 43a71d52bb094eae18ae0a65ee515dc821e3c48b Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Wed, 22 Oct 2025 10:16:51 +0000 Subject: [PATCH 47/87] bump --- cpp/src/mip/solver.cu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/mip/solver.cu b/cpp/src/mip/solver.cu index 08786aedd..96755626d 100644 --- a/cpp/src/mip/solver.cu +++ b/cpp/src/mip/solver.cu @@ -225,7 +225,7 @@ solution_t mip_solver_t::run_solver() // Fork a thread for branch and bound // std::async and std::future allow us to get the return value of bb::solve() - // without having to manually manage the thread + // without having to manually manage the thread 1 // std::future.get() performs a join() operation to wait until the return status is available branch_and_bound_status_future = std::async(std::launch::async, &dual_simplex::branch_and_bound_t::solve, From 5831e6d8455a34b2b56a5162dcf5df01e179e92a Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Wed, 22 Oct 2025 10:17:04 +0000 Subject: [PATCH 48/87] bump 2 --- cpp/src/mip/solver.cu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/mip/solver.cu b/cpp/src/mip/solver.cu index 96755626d..a07ac49d4 100644 --- a/cpp/src/mip/solver.cu +++ b/cpp/src/mip/solver.cu @@ -225,7 +225,7 @@ solution_t mip_solver_t::run_solver() // Fork a thread for branch and bound // std::async and std::future allow us to get the return value of bb::solve() - // without having to manually manage the thread 1 + // without having to manually manage the thread 2 // std::future.get() performs a join() operation to wait until the return status is available branch_and_bound_status_future = std::async(std::launch::async, &dual_simplex::branch_and_bound_t::solve, From e6d9bab692d65acf99bce1e8e5715eeaf4e8158f Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Wed, 22 Oct 2025 10:17:16 +0000 Subject: [PATCH 49/87] bump 3 --- cpp/src/mip/solver.cu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/mip/solver.cu b/cpp/src/mip/solver.cu index a07ac49d4..0e9bd16f4 100644 --- a/cpp/src/mip/solver.cu +++ b/cpp/src/mip/solver.cu @@ -225,7 +225,7 @@ solution_t mip_solver_t::run_solver() // Fork a thread for branch and bound // std::async and std::future allow us to get the return value of bb::solve() - // without having to manually manage the thread 2 + // without having to manually manage the thread 3 // std::future.get() performs a join() operation to wait until the return status is available branch_and_bound_status_future = std::async(std::launch::async, &dual_simplex::branch_and_bound_t::solve, From ab210a0ebdf243be8386caacf72303f91e3ba3b1 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Wed, 22 Oct 2025 13:41:44 +0000 Subject: [PATCH 50/87] minor cleanup --- cpp/src/mip/diversity/lns/rins.cu | 1 - cpp/src/mip/diversity/lns/rins.cuh | 2 +- cpp/src/mip/feasibility_jump/fj_cpu.cu | 1 - cpp/src/mip/solver.cu | 2 +- 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index f3b975f82..4dc5eec41 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -108,7 +108,6 @@ void rins_t::new_best_incumbent_callback(const std::vector& solut node_count_at_last_improvement = node_count.load(); } -// node_callback may be called from different threads(i think?). need lock protection template void rins_t::node_callback(const std::vector& solution, f_t objective) { diff --git a/cpp/src/mip/diversity/lns/rins.cuh b/cpp/src/mip/diversity/lns/rins.cuh index 97c9f62fe..03e10bf7f 100644 --- a/cpp/src/mip/diversity/lns/rins.cuh +++ b/cpp/src/mip/diversity/lns/rins.cuh @@ -100,7 +100,7 @@ class rins_t { f_t time_limit{10.}; i_t seed; - bool enabled{false}; + std::atomic enabled{false}; std::atomic node_count{0}; std::atomic node_count_at_last_rins{0}; diff --git a/cpp/src/mip/feasibility_jump/fj_cpu.cu b/cpp/src/mip/feasibility_jump/fj_cpu.cu index 895ddc627..4f71c71e7 100644 --- a/cpp/src/mip/feasibility_jump/fj_cpu.cu +++ b/cpp/src/mip/feasibility_jump/fj_cpu.cu @@ -1080,7 +1080,6 @@ void cpu_fj_thread_t::cpu_worker_thread() cpu_thread_should_start = false; } - // Run CPU solver (outside lock) 3 bool solution_found = fj_ptr->cpu_solve(*fj_cpu, time_limit); { diff --git a/cpp/src/mip/solver.cu b/cpp/src/mip/solver.cu index 0e9bd16f4..08786aedd 100644 --- a/cpp/src/mip/solver.cu +++ b/cpp/src/mip/solver.cu @@ -225,7 +225,7 @@ solution_t mip_solver_t::run_solver() // Fork a thread for branch and bound // std::async and std::future allow us to get the return value of bb::solve() - // without having to manually manage the thread 3 + // without having to manually manage the thread // std::future.get() performs a join() operation to wait until the return status is available branch_and_bound_status_future = std::async(std::launch::async, &dual_simplex::branch_and_bound_t::solve, From 8198b44c1493d2acc39d34b0d9c00b2f88fc748e Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Wed, 22 Oct 2025 14:35:08 +0000 Subject: [PATCH 51/87] address review comments --- cpp/src/dual_simplex/branch_and_bound.cpp | 4 ++ .../dual_simplex/simplex_solver_settings.hpp | 2 +- cpp/src/mip/diversity/lns/rins.cu | 37 ++++++++++++------- cpp/src/mip/feasibility_jump/fj_cpu.cuh | 1 + cpp/src/mip/solver.cu | 2 +- 5 files changed, 30 insertions(+), 16 deletions(-) diff --git a/cpp/src/dual_simplex/branch_and_bound.cpp b/cpp/src/dual_simplex/branch_and_bound.cpp index b9e739229..5d74262fc 100644 --- a/cpp/src/dual_simplex/branch_and_bound.cpp +++ b/cpp/src/dual_simplex/branch_and_bound.cpp @@ -448,6 +448,10 @@ mip_status_t branch_and_bound_t::set_final_solution(mip_solution_t&, f_t)> solution_callback; - std::function&, f_t)> node_processed_callback; + std::function&, f_t)> node_processed_callback; std::function heuristic_preemption_callback; std::function&, std::vector&, f_t)> set_simplex_solution_callback; mutable logger_t log; diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index 4dc5eec41..b62c81b2b 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -31,6 +31,7 @@ rins_t::rins_t(mip_solver_context_t& context_, : context(context_), problem_ptr(context.problem_ptr), dm(dm_), settings(settings_) { fixrate = settings.default_fixrate; + time_limit = settings.default_time_limit; rins_thread = std::make_unique>(); rins_thread->rins_ptr = this; seed = cuopt::seed_generator::get_seed(); @@ -56,34 +57,44 @@ void rins_thread_t::cpu_worker_thread() { std::unique_lock lock(cpu_mutex); cpu_cv.wait(lock, [this] { return cpu_thread_should_start || cpu_thread_terminate; }); - } - if (cpu_thread_terminate) break; + if (cpu_thread_terminate) break; + + cpu_thread_should_start = false; + } - // Run CPU solver + // Run RINS { raft::common::nvtx::range fun_scope("Running RINS"); rins_ptr->run_rins(); } - cpu_thread_should_start = false; - cpu_thread_done = true; + { + std::lock_guard lock(cpu_mutex); + cpu_thread_done = true; + } } } template void rins_thread_t::kill_cpu_solver() { - cpu_thread_terminate = true; + { + std::lock_guard lock(cpu_mutex); + cpu_thread_terminate = true; + } cpu_cv.notify_one(); cpu_worker.join(); } template void rins_thread_t::start_cpu_solver() -{ // Reset flags - cpu_thread_done = false; - cpu_thread_should_start = true; +{ + { + std::lock_guard lock(cpu_mutex); + cpu_thread_done = false; + cpu_thread_should_start = true; + } cpu_cv.notify_one(); } @@ -119,7 +130,7 @@ void rins_t::node_callback(const std::vector& solution, f_t objec if (node_count - node_count_at_last_rins > settings.node_freq) { std::lock_guard lock(rins_mutex); - if (!rins_thread->cpu_thread_should_start && dm.population.current_size() > 0 && + if (!rins_thread->cpu_thread_done && dm.population.current_size() > 0 && dm.population.is_feasible()) { lp_optimal_solution = solution; rins_thread->start_cpu_solver(); @@ -230,7 +241,7 @@ void rins_t::run_rins() // should probably just do an spmv to get the objective instead. ugly mess of copies solution_t best_sol_fixed_space(fixed_problem); - best_sol_fixed_space.copy_new_assignment(host_copy(fixed_assignment)); + best_sol_fixed_space.copy_new_assignment(cuopt::host_copy(fixed_assignment)); best_sol_fixed_space.compute_feasibility(); CUOPT_LOG_DEBUG("RINS best sol fixed space objective %g", best_sol_fixed_space.get_user_objective()); @@ -349,9 +360,7 @@ void rins_t::run_rins() best_sol.handle_ptr->sync_stream(); std::swap(fixed_assignment, post_processed_solution); - CUOPT_LOG_DEBUG("RINS FJ solution improved objective from %g to %g", - best_sol.get_user_objective(), - fj_solution.get_user_objective()); + CUOPT_LOG_DEBUG("RINS FJ solution improved objective"); rins_solution_found = true; } } diff --git a/cpp/src/mip/feasibility_jump/fj_cpu.cuh b/cpp/src/mip/feasibility_jump/fj_cpu.cuh index 8e856ce45..059b32ff0 100644 --- a/cpp/src/mip/feasibility_jump/fj_cpu.cuh +++ b/cpp/src/mip/feasibility_jump/fj_cpu.cuh @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include diff --git a/cpp/src/mip/solver.cu b/cpp/src/mip/solver.cu index 08786aedd..aa31999a9 100644 --- a/cpp/src/mip/solver.cu +++ b/cpp/src/mip/solver.cu @@ -84,7 +84,7 @@ struct branch_and_bound_solution_helper_t { dm->set_simplex_solution(solution, dual_solution, objective); } - void node_processed_callback(std::vector& solution, f_t objective) + void node_processed_callback(const std::vector& solution, f_t objective) { dm->rins.node_callback(solution, objective); } From 4ffa79a38aaa3bd6d6887cb2cd2b89da27d73bad Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Wed, 22 Oct 2025 14:47:38 +0000 Subject: [PATCH 52/87] fix typo --- cpp/src/mip/diversity/lns/rins.cu | 2 +- cpp/src/mip/diversity/lns/rins.cuh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index b62c81b2b..68851d3ad 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -130,7 +130,7 @@ void rins_t::node_callback(const std::vector& solution, f_t objec if (node_count - node_count_at_last_rins > settings.node_freq) { std::lock_guard lock(rins_mutex); - if (!rins_thread->cpu_thread_done && dm.population.current_size() > 0 && + if (rins_thread->cpu_thread_done && dm.population.current_size() > 0 && dm.population.is_feasible()) { lp_optimal_solution = solution; rins_thread->start_cpu_solver(); diff --git a/cpp/src/mip/diversity/lns/rins.cuh b/cpp/src/mip/diversity/lns/rins.cuh index 03e10bf7f..f0dde047b 100644 --- a/cpp/src/mip/diversity/lns/rins.cuh +++ b/cpp/src/mip/diversity/lns/rins.cuh @@ -68,7 +68,7 @@ struct rins_thread_t { std::condition_variable cpu_cv; std::atomic should_stop{false}; std::atomic cpu_thread_should_start{false}; - std::atomic cpu_thread_done{false}; + std::atomic cpu_thread_done{true}; std::atomic cpu_thread_terminate{false}; rins_t* rins_ptr{nullptr}; From 07ee172b8c895c3b4087d0f44db6586f6fdaec69 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Wed, 22 Oct 2025 14:54:22 +0000 Subject: [PATCH 53/87] node limit --- cpp/src/mip/diversity/lns/rins.cu | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index 68851d3ad..64ebd2179 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -282,8 +282,7 @@ void rins_t::run_rins() branch_and_bound_solution.resize(branch_and_bound_problem.num_cols); // Fill in the settings for branch and bound branch_and_bound_settings.time_limit = time_limit; - // branch_and_bound_settings.node_limit = 5000 + node_count / 100; // try harder as time goes - // on + branch_and_bound_settings.node_limit = 5000 + node_count / 100; // try harder as time goes on branch_and_bound_settings.print_presolve_stats = false; branch_and_bound_settings.absolute_mip_gap_tol = context.settings.tolerances.absolute_mip_gap; branch_and_bound_settings.relative_mip_gap_tol = 0.03; // 3% From c4c89d39a222740deffd8e2ac026a9da369d95f3 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Wed, 22 Oct 2025 15:00:37 +0000 Subject: [PATCH 54/87] more threads --- cpp/src/mip/diversity/lns/rins.cu | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index 64ebd2179..c402ca0ef 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -286,10 +286,10 @@ void rins_t::run_rins() branch_and_bound_settings.print_presolve_stats = false; branch_and_bound_settings.absolute_mip_gap_tol = context.settings.tolerances.absolute_mip_gap; branch_and_bound_settings.relative_mip_gap_tol = 0.03; // 3% - branch_and_bound_settings.integer_tol = context.settings.tolerances.integrality_tolerance; - branch_and_bound_settings.num_threads = 2; - branch_and_bound_settings.num_bfs_threads = 1; - branch_and_bound_settings.num_diving_threads = 1; + branch_and_bound_settings.integer_tol = context.settings.tolerances.integrality_tolerance; + // branch_and_bound_settings.num_threads = 2; + branch_and_bound_settings.num_bfs_threads = 8; + branch_and_bound_settings.num_diving_threads = 8; dual_simplex::branch_and_bound_t branch_and_bound(branch_and_bound_problem, branch_and_bound_settings); branch_and_bound.set_initial_guess(cuopt::host_copy(fixed_assignment)); From 3c03846f72fb9bc5c66674ab8cf9c82e4ebceb82 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Wed, 22 Oct 2025 16:41:23 +0000 Subject: [PATCH 55/87] restore --- cpp/src/mip/diversity/lns/rins.cu | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index c402ca0ef..68851d3ad 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -282,14 +282,15 @@ void rins_t::run_rins() branch_and_bound_solution.resize(branch_and_bound_problem.num_cols); // Fill in the settings for branch and bound branch_and_bound_settings.time_limit = time_limit; - branch_and_bound_settings.node_limit = 5000 + node_count / 100; // try harder as time goes on + // branch_and_bound_settings.node_limit = 5000 + node_count / 100; // try harder as time goes + // on branch_and_bound_settings.print_presolve_stats = false; branch_and_bound_settings.absolute_mip_gap_tol = context.settings.tolerances.absolute_mip_gap; branch_and_bound_settings.relative_mip_gap_tol = 0.03; // 3% - branch_and_bound_settings.integer_tol = context.settings.tolerances.integrality_tolerance; - // branch_and_bound_settings.num_threads = 2; - branch_and_bound_settings.num_bfs_threads = 8; - branch_and_bound_settings.num_diving_threads = 8; + branch_and_bound_settings.integer_tol = context.settings.tolerances.integrality_tolerance; + branch_and_bound_settings.num_threads = 2; + branch_and_bound_settings.num_bfs_threads = 1; + branch_and_bound_settings.num_diving_threads = 1; dual_simplex::branch_and_bound_t branch_and_bound(branch_and_bound_problem, branch_and_bound_settings); branch_and_bound.set_initial_guess(cuopt::host_copy(fixed_assignment)); From a7d18cf55809a8f194d00785386702efcfba8a48 Mon Sep 17 00:00:00 2001 From: Ramakrishnap <42624703+rgsl888prabhu@users.noreply.github.com> Date: Wed, 22 Oct 2025 17:18:05 -0500 Subject: [PATCH 56/87] Update pyproject.toml --- python/libcuopt/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/libcuopt/pyproject.toml b/python/libcuopt/pyproject.toml index 62f8011dc..34bd8ccfe 100644 --- a/python/libcuopt/pyproject.toml +++ b/python/libcuopt/pyproject.toml @@ -66,7 +66,7 @@ libcuopt = "libcuopt" select = [ "distro-too-large-compressed", ] -max_allowed_size_compressed = '585M' +max_allowed_size_compressed = '600M' [project.scripts] cuopt_cli = "libcuopt._cli_wrapper:main" From 50ab8dc9e999456e38e40833282ff3f1d1831bf2 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Thu, 23 Oct 2025 08:26:26 +0000 Subject: [PATCH 57/87] fix b&b assert --- cpp/src/dual_simplex/branch_and_bound.cpp | 19 +++++++++++++------ cpp/src/dual_simplex/solution.hpp | 12 +++++++++--- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/cpp/src/dual_simplex/branch_and_bound.cpp b/cpp/src/dual_simplex/branch_and_bound.cpp index 5d74262fc..52698875c 100644 --- a/cpp/src/dual_simplex/branch_and_bound.cpp +++ b/cpp/src/dual_simplex/branch_and_bound.cpp @@ -279,6 +279,7 @@ void branch_and_bound_t::set_new_solution(const std::vector& solu original_lp_, settings_, var_types_, crushed_solution, primal_err, bound_err, num_fractional); if (is_feasible) { upper_bound_ = obj; + incumbent_.set_incumbent_solution(obj, crushed_solution); } else { attempt_repair = true; constexpr bool verbose = false; @@ -453,15 +454,20 @@ mip_status_t branch_and_bound_t::set_final_solution(mip_solution_t::set_final_solution(mip_solution_t class mip_solution_t { public: - mip_solution_t(i_t n) : x(n), objective(std::numeric_limits::quiet_NaN()), lower_bound(-inf) + mip_solution_t(i_t n) + : x(n), + objective(std::numeric_limits::quiet_NaN()), + lower_bound(-inf), + has_incumbent(false) { } @@ -69,8 +73,9 @@ class mip_solution_t { void set_incumbent_solution(f_t primal_objective, const std::vector& primal_solution) { - x = primal_solution; - objective = primal_objective; + x = primal_solution; + objective = primal_objective; + has_incumbent = true; } // Primal solution vector @@ -79,6 +84,7 @@ class mip_solution_t { f_t lower_bound; i_t nodes_explored; i_t simplex_iterations; + bool has_incumbent; }; } // namespace cuopt::linear_programming::dual_simplex From e43898986cd014b4f7d662bf5cde71817b8a4220 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Thu, 23 Oct 2025 16:38:36 +0000 Subject: [PATCH 58/87] fix unneeded mutex call --- cpp/src/mip/diversity/lns/rins.cu | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index 68851d3ad..2c084764b 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -129,6 +129,8 @@ void rins_t::node_callback(const std::vector& solution, f_t objec if (node_count - node_count_at_last_improvement < settings.nodes_after_later_improvement) return; if (node_count - node_count_at_last_rins > settings.node_freq) { + // 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()) { From 1e8043da2c5a949f21b89452300d359f95e7636d Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Thu, 23 Oct 2025 16:42:11 +0000 Subject: [PATCH 59/87] w/ stable3 --- cpp/src/mip/relaxed_lp/relaxed_lp.cu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/mip/relaxed_lp/relaxed_lp.cu b/cpp/src/mip/relaxed_lp/relaxed_lp.cu index e8f9ca2a2..08f7be413 100644 --- a/cpp/src/mip/relaxed_lp/relaxed_lp.cu +++ b/cpp/src/mip/relaxed_lp/relaxed_lp.cu @@ -62,7 +62,7 @@ optimization_problem_solution_t get_relaxed_lp_solution( pdlp_settings.concurrent_halt = settings.concurrent_halt; pdlp_settings.per_constraint_residual = settings.per_constraint_residual; pdlp_settings.first_primal_feasible = settings.return_first_feasible; - pdlp_settings.pdlp_solver_mode = pdlp_solver_mode_t::Stable2; + pdlp_settings.pdlp_solver_mode = pdlp_solver_mode_t::Stable3; set_pdlp_solver_mode(pdlp_settings); // TODO: set Stable3 here? pdlp_solver_t lp_solver(op_problem, pdlp_settings); From 2d1571a3940d4ce550260b63031b48c2dd1041fb Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Thu, 23 Oct 2025 16:42:51 +0000 Subject: [PATCH 60/87] bump 1 --- cpp/src/mip/relaxed_lp/relaxed_lp.cu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/mip/relaxed_lp/relaxed_lp.cu b/cpp/src/mip/relaxed_lp/relaxed_lp.cu index 08f7be413..336e2ee4a 100644 --- a/cpp/src/mip/relaxed_lp/relaxed_lp.cu +++ b/cpp/src/mip/relaxed_lp/relaxed_lp.cu @@ -62,7 +62,7 @@ optimization_problem_solution_t get_relaxed_lp_solution( pdlp_settings.concurrent_halt = settings.concurrent_halt; pdlp_settings.per_constraint_residual = settings.per_constraint_residual; pdlp_settings.first_primal_feasible = settings.return_first_feasible; - pdlp_settings.pdlp_solver_mode = pdlp_solver_mode_t::Stable3; + pdlp_settings.pdlp_solver_mode = pdlp_solver_mode_t::Stable3; // 1 set_pdlp_solver_mode(pdlp_settings); // TODO: set Stable3 here? pdlp_solver_t lp_solver(op_problem, pdlp_settings); From 8df41b4abf8cdc088c16e705b09f0f171982957a Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Thu, 23 Oct 2025 16:43:16 +0000 Subject: [PATCH 61/87] bump 2 --- cpp/src/mip/relaxed_lp/relaxed_lp.cu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/mip/relaxed_lp/relaxed_lp.cu b/cpp/src/mip/relaxed_lp/relaxed_lp.cu index 336e2ee4a..c220e063f 100644 --- a/cpp/src/mip/relaxed_lp/relaxed_lp.cu +++ b/cpp/src/mip/relaxed_lp/relaxed_lp.cu @@ -62,7 +62,7 @@ optimization_problem_solution_t get_relaxed_lp_solution( pdlp_settings.concurrent_halt = settings.concurrent_halt; pdlp_settings.per_constraint_residual = settings.per_constraint_residual; pdlp_settings.first_primal_feasible = settings.return_first_feasible; - pdlp_settings.pdlp_solver_mode = pdlp_solver_mode_t::Stable3; // 1 + pdlp_settings.pdlp_solver_mode = pdlp_solver_mode_t::Stable3; // 2 set_pdlp_solver_mode(pdlp_settings); // TODO: set Stable3 here? pdlp_solver_t lp_solver(op_problem, pdlp_settings); From bb74f5ab5dfdcc8bb1bc4707f0f8ac7428fbb776 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Thu, 23 Oct 2025 16:43:49 +0000 Subject: [PATCH 62/87] bump 3 --- cpp/src/mip/relaxed_lp/relaxed_lp.cu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/mip/relaxed_lp/relaxed_lp.cu b/cpp/src/mip/relaxed_lp/relaxed_lp.cu index c220e063f..61738921c 100644 --- a/cpp/src/mip/relaxed_lp/relaxed_lp.cu +++ b/cpp/src/mip/relaxed_lp/relaxed_lp.cu @@ -62,7 +62,7 @@ optimization_problem_solution_t get_relaxed_lp_solution( pdlp_settings.concurrent_halt = settings.concurrent_halt; pdlp_settings.per_constraint_residual = settings.per_constraint_residual; pdlp_settings.first_primal_feasible = settings.return_first_feasible; - pdlp_settings.pdlp_solver_mode = pdlp_solver_mode_t::Stable3; // 2 + pdlp_settings.pdlp_solver_mode = pdlp_solver_mode_t::Stable3; // 3 set_pdlp_solver_mode(pdlp_settings); // TODO: set Stable3 here? pdlp_solver_t lp_solver(op_problem, pdlp_settings); From 95b0eb9ec42ad575f8969d36a0371ed164eddf3b Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Fri, 24 Oct 2025 12:18:52 +0000 Subject: [PATCH 63/87] RINS on separate stream --- cpp/src/mip/diversity/lns/rins.cu | 108 ++++++++++++++++++----------- cpp/src/mip/diversity/lns/rins.cuh | 5 ++ cpp/src/mip/problem/problem.cu | 84 +++++++++++----------- cpp/src/mip/problem/problem.cuh | 2 +- 4 files changed, 113 insertions(+), 86 deletions(-) diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index 2c084764b..e09e4fb40 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -30,11 +30,8 @@ rins_t::rins_t(mip_solver_context_t& context_, rins_settings_t settings_) : context(context_), problem_ptr(context.problem_ptr), dm(dm_), settings(settings_) { - fixrate = settings.default_fixrate; - time_limit = settings.default_time_limit; - rins_thread = std::make_unique>(); - rins_thread->rins_ptr = this; - seed = cuopt::seed_generator::get_seed(); + fixrate = settings.default_fixrate; + time_limit = settings.default_time_limit; } template @@ -143,7 +140,12 @@ void rins_t::node_callback(const std::vector& solution, f_t objec template void rins_t::enable() { - enabled = true; + rins_thread = std::make_unique>(); + rins_thread->rins_ptr = this; + seed = cuopt::seed_generator::get_seed(); + problem_copy = std::make_unique>(*problem_ptr, /*deep_copy=*/true); + problem_copy->handle_ptr = &rins_handle; + enabled = true; } template @@ -154,33 +156,56 @@ void rins_t::run_rins() if (!dm.population.is_feasible()) return; cuopt_assert(lp_optimal_solution.size() == problem_ptr->n_variables, "Assignment size mismatch"); - - auto pop = dm.population.population_to_vector(); - if (pop.size() > 0) { - auto best_sol = pop[0]; + cuopt_assert(problem_copy->handle_ptr == &rins_handle, "Handle mismatch"); + cuopt_assert(problem_copy->n_variables == problem_ptr->n_variables, "Problem size mismatch"); + cuopt_assert(problem_copy->n_constraints == problem_ptr->n_constraints, "Problem size mismatch"); + cuopt_assert(problem_copy->n_integer_vars == problem_ptr->n_integer_vars, + "Problem size mismatch"); + cuopt_assert(problem_copy->n_binary_vars == problem_ptr->n_binary_vars, "Problem size mismatch"); + + if (dm.population.current_size() > 0) { + solution_t best_sol(*problem_copy); + // copy the best from the population into a solution_t in the RINS stream + { + std::lock_guard lock(dm.population.write_mutex); + auto& best_feasible_ref = dm.population.best_feasible(); + cuopt_assert(best_feasible_ref.assignment.size() == best_sol.assignment.size(), + "Assignment size mismatch"); + cuopt_assert(best_feasible_ref.get_feasible(), "Best feasible is not feasible"); + expand_device_copy( + best_sol.assignment, best_feasible_ref.assignment, rins_handle.get_stream()); + best_sol.handle_ptr = &rins_handle; + best_sol.problem_ptr = problem_copy.get(); + best_sol.compute_feasibility(); + } // also scour through the external solution queue to potentially // nab better solutions early - auto queues = std::array{std::ref(dm.population.external_solution_queue), - std::ref(dm.population.external_solution_queue_cpufj)}; - for (auto& queue : queues) { - for (auto& h_entry : queue.get()) { - if (h_entry.objective >= best_sol.get_objective()) { continue; } - best_sol.copy_new_assignment(h_entry.solution); - best_sol.compute_feasibility(); + { + std::lock_guard lock(dm.population.solution_mutex); + auto queues = std::array{std::ref(dm.population.external_solution_queue), + std::ref(dm.population.external_solution_queue_cpufj)}; + for (auto& queue : queues) { + for (auto& h_entry : queue.get()) { + if (h_entry.objective >= best_sol.get_objective()) { continue; } + best_sol.copy_new_assignment(h_entry.solution); + best_sol.compute_feasibility(); + printf("RINS External solution is feas? %d, excess %g\n", + best_sol.get_feasible(), + best_sol.get_total_excess()); + } } } + cuopt_assert(best_sol.handle_ptr == &rins_handle, "Handle mismatch"); if (!best_sol.get_feasible()) { return; } i_t sol_size_before_rins = best_sol.assignment.size(); - auto lp_opt_device = - cuopt::device_copy(this->lp_optimal_solution, problem_ptr->handle_ptr->get_stream()); + auto lp_opt_device = cuopt::device_copy(this->lp_optimal_solution, rins_handle.get_stream()); cuopt_assert(lp_opt_device.size() == problem_ptr->n_variables, "Assignment size mismatch"); cuopt_assert(best_sol.assignment.size() == problem_ptr->n_variables, "Assignment size mismatch"); - rmm::device_uvector vars_to_fix(problem_ptr->n_integer_vars, - problem_ptr->handle_ptr->get_stream()); - auto end = thrust::copy_if(problem_ptr->handle_ptr->get_thrust_policy(), + rmm::device_uvector vars_to_fix(problem_ptr->n_integer_vars, rins_handle.get_stream()); + auto end = thrust::copy_if(rins_handle.get_thrust_policy(), thrust::make_counting_iterator(i_t(0)), thrust::make_counting_iterator(problem_ptr->n_variables), vars_to_fix.begin(), @@ -190,7 +215,7 @@ void rins_t::run_rins() if (!pb.is_integer_var(var_idx)) return false; return pb.integer_equal(lpopt[var_idx], incumbent[var_idx]); }); - vars_to_fix.resize(end - vars_to_fix.begin(), problem_ptr->handle_ptr->get_stream()); + vars_to_fix.resize(end - vars_to_fix.begin(), rins_handle.get_stream()); f_t fractional_ratio = (f_t)(vars_to_fix.size()) / (f_t)problem_ptr->n_integer_vars; // abort if the fractional ratio is too low @@ -202,17 +227,15 @@ void rins_t::run_rins() thrust::default_random_engine g(seed + node_count); // shuffle fixing order - thrust::shuffle( - problem_ptr->handle_ptr->get_thrust_policy(), vars_to_fix.begin(), vars_to_fix.end(), g); + thrust::shuffle(rins_handle.get_thrust_policy(), vars_to_fix.begin(), vars_to_fix.end(), g); // fix n first according to fractional ratio f_t rins_ratio = fixrate; i_t n_to_fix = std::max((int)(vars_to_fix.size() * rins_ratio), 0); - vars_to_fix.resize(n_to_fix, problem_ptr->handle_ptr->get_stream()); - thrust::sort( - problem_ptr->handle_ptr->get_thrust_policy(), vars_to_fix.begin(), vars_to_fix.end()); + vars_to_fix.resize(n_to_fix, rins_handle.get_stream()); + thrust::sort(rins_handle.get_thrust_policy(), vars_to_fix.begin(), vars_to_fix.end()); - cuopt_assert(thrust::all_of(problem_ptr->handle_ptr->get_thrust_policy(), + cuopt_assert(thrust::all_of(rins_handle.get_thrust_policy(), vars_to_fix.begin(), vars_to_fix.end(), [pb = problem_ptr->view()] __device__(i_t var_idx) { @@ -243,7 +266,9 @@ void rins_t::run_rins() // should probably just do an spmv to get the objective instead. ugly mess of copies solution_t best_sol_fixed_space(fixed_problem); - best_sol_fixed_space.copy_new_assignment(cuopt::host_copy(fixed_assignment)); + cuopt_assert(best_sol_fixed_space.handle_ptr == &rins_handle, "Handle mismatch"); + best_sol_fixed_space.copy_new_assignment( + cuopt::host_copy(fixed_assignment, rins_handle.get_stream())); best_sol_fixed_space.compute_feasibility(); CUOPT_LOG_DEBUG("RINS best sol fixed space objective %g", best_sol_fixed_space.get_user_objective()); @@ -255,13 +280,13 @@ void rins_t::run_rins() fixed_problem.add_cutting_plane_at_objective(objective_cut); } - fixed_problem.presolve_data.reset_additional_vars(fixed_problem, best_sol.handle_ptr); - fixed_problem.presolve_data.initialize_var_mapping(fixed_problem, best_sol.handle_ptr); + fixed_problem.presolve_data.reset_additional_vars(fixed_problem, &rins_handle); + fixed_problem.presolve_data.initialize_var_mapping(fixed_problem, &rins_handle); trivial_presolve(fixed_problem); fixed_problem.check_problem_representation(true); mip_solver_context_t fj_context( - best_sol.handle_ptr, &fixed_problem, context.settings, context.scaling); + &rins_handle, &fixed_problem, context.settings, context.scaling); fj_t fj(fj_context); solution_t fj_solution(fixed_problem); fj_solution.copy_new_assignment(cuopt::host_copy(fixed_assignment)); @@ -276,7 +301,7 @@ void rins_t::run_rins() // run sub-mip namespace dual_simplex = cuopt::linear_programming::dual_simplex; - dual_simplex::user_problem_t branch_and_bound_problem(best_sol.handle_ptr); + dual_simplex::user_problem_t branch_and_bound_problem(&rins_handle); dual_simplex::simplex_solver_settings_t branch_and_bound_settings; dual_simplex::mip_solution_t branch_and_bound_solution(1); dual_simplex::mip_status_t branch_and_bound_status = dual_simplex::mip_status_t::UNSET; @@ -295,7 +320,8 @@ void rins_t::run_rins() branch_and_bound_settings.num_diving_threads = 1; dual_simplex::branch_and_bound_t branch_and_bound(branch_and_bound_problem, branch_and_bound_settings); - branch_and_bound.set_initial_guess(cuopt::host_copy(fixed_assignment)); + branch_and_bound.set_initial_guess( + cuopt::host_copy(fixed_assignment, rins_handle.get_stream())); branch_and_bound_status = branch_and_bound.solve(branch_and_bound_solution, "[RINS] "); bool rins_solution_found = false; @@ -307,15 +333,15 @@ void rins_t::run_rins() int(branch_and_bound_status)); // first post process the trivial presolve on a device vector rmm::device_uvector post_processed_solution(branch_and_bound_solution.x.size(), - best_sol.handle_ptr->get_stream()); + rins_handle.get_stream()); raft::copy(post_processed_solution.data(), branch_and_bound_solution.x.data(), branch_and_bound_solution.x.size(), - best_sol.handle_ptr->get_stream()); + rins_handle.get_stream()); fixed_problem.post_process_assignment(post_processed_solution, false); cuopt_assert(post_processed_solution.size() == fixed_assignment.size(), "Assignment size mismatch"); - best_sol.handle_ptr->sync_stream(); + rins_handle.sync_stream(); std::swap(fixed_assignment, post_processed_solution); rins_solution_found = true; @@ -351,15 +377,15 @@ void rins_t::run_rins() std::isnan(branch_and_bound_solution.objective)) { // first post process the trivial presolve on a device vector rmm::device_uvector post_processed_solution(branch_and_bound_solution.x.size(), - best_sol.handle_ptr->get_stream()); + rins_handle.get_stream()); raft::copy(post_processed_solution.data(), cpu_fj_thread.fj_cpu->h_best_assignment.data(), cpu_fj_thread.fj_cpu->h_best_assignment.size(), - best_sol.handle_ptr->get_stream()); + rins_handle.get_stream()); fixed_problem.post_process_assignment(post_processed_solution, false); cuopt_assert(post_processed_solution.size() == fixed_assignment.size(), "Assignment size mismatch"); - best_sol.handle_ptr->sync_stream(); + rins_handle.sync_stream(); std::swap(fixed_assignment, post_processed_solution); CUOPT_LOG_DEBUG("RINS FJ solution improved objective"); diff --git a/cpp/src/mip/diversity/lns/rins.cuh b/cpp/src/mip/diversity/lns/rins.cuh index f0dde047b..ac7ad0160 100644 --- a/cpp/src/mip/diversity/lns/rins.cuh +++ b/cpp/src/mip/diversity/lns/rins.cuh @@ -92,6 +92,11 @@ class rins_t { diversity_manager_t& dm; rins_settings_t settings; + // need a separate handle for RINS to operate on a separate stream and prevent graph capture + // issues + std::unique_ptr> problem_copy; + raft::handle_t rins_handle; + std::vector lp_optimal_solution; f_t fixrate{0.5}; diff --git a/cpp/src/mip/problem/problem.cu b/cpp/src/mip/problem/problem.cu index 61781bd5c..d5f726ada 100644 --- a/cpp/src/mip/problem/problem.cu +++ b/cpp/src/mip/problem/problem.cu @@ -198,7 +198,7 @@ problem_t::problem_t(const problem_t& problem_) } template -problem_t::problem_t(const problem_t& problem_, bool no_deep_copy) +problem_t::problem_t(const problem_t& problem_, bool deep_copy) : original_problem_ptr(problem_.original_problem_ptr), tolerances(problem_.tolerances), handle_ptr(problem_.handle_ptr), @@ -213,70 +213,66 @@ problem_t::problem_t(const problem_t& problem_, bool no_deep empty(problem_.empty), is_binary_pb(problem_.is_binary_pb), // Copy constructor used by PDLP and MIP - // PDLP uses the version with no_deep_copy = false which deep copy some fields but doesn't + // PDLP uses the version with deep_copy = false which deep copy some fields but doesn't // allocate others that are not needed in PDLP presolve_data( - (!no_deep_copy) + (!deep_copy) ? std::move(presolve_data_t{*problem_.original_problem_ptr, handle_ptr->get_stream()}) : std::move(presolve_data_t{problem_.presolve_data, handle_ptr->get_stream()})), original_ids(problem_.original_ids), reverse_original_ids(problem_.reverse_original_ids), reverse_coefficients( - (!no_deep_copy) + (!deep_copy) ? rmm::device_uvector(problem_.reverse_coefficients, handle_ptr->get_stream()) - : rmm::device_uvector(problem_.reverse_coefficients.size(), handle_ptr->get_stream())), + : rmm::device_uvector(problem_.reverse_coefficients, handle_ptr->get_stream())), reverse_constraints( - (!no_deep_copy) + (!deep_copy) ? rmm::device_uvector(problem_.reverse_constraints, handle_ptr->get_stream()) - : rmm::device_uvector(problem_.reverse_constraints.size(), handle_ptr->get_stream())), + : rmm::device_uvector(problem_.reverse_constraints, handle_ptr->get_stream())), reverse_offsets( - (!no_deep_copy) - ? rmm::device_uvector(problem_.reverse_offsets, handle_ptr->get_stream()) - : rmm::device_uvector(problem_.reverse_offsets.size(), handle_ptr->get_stream())), - coefficients( - (!no_deep_copy) - ? rmm::device_uvector(problem_.coefficients, handle_ptr->get_stream()) - : rmm::device_uvector(problem_.coefficients.size(), handle_ptr->get_stream())), - variables((!no_deep_copy) + (!deep_copy) ? rmm::device_uvector(problem_.reverse_offsets, handle_ptr->get_stream()) + : rmm::device_uvector(problem_.reverse_offsets, handle_ptr->get_stream())), + coefficients((!deep_copy) + ? rmm::device_uvector(problem_.coefficients, handle_ptr->get_stream()) + : rmm::device_uvector(problem_.coefficients, handle_ptr->get_stream())), + variables((!deep_copy) ? rmm::device_uvector(problem_.variables, handle_ptr->get_stream()) - : rmm::device_uvector(problem_.variables.size(), handle_ptr->get_stream())), - offsets((!no_deep_copy) - ? rmm::device_uvector(problem_.offsets, handle_ptr->get_stream()) - : rmm::device_uvector(problem_.offsets.size(), handle_ptr->get_stream())), + : rmm::device_uvector(problem_.variables, handle_ptr->get_stream())), + offsets((!deep_copy) ? rmm::device_uvector(problem_.offsets, handle_ptr->get_stream()) + : rmm::device_uvector(problem_.offsets, handle_ptr->get_stream())), objective_coefficients( - (!no_deep_copy) + (!deep_copy) ? rmm::device_uvector(problem_.objective_coefficients, handle_ptr->get_stream()) - : rmm::device_uvector(problem_.objective_coefficients.size(), - handle_ptr->get_stream())), + : rmm::device_uvector(problem_.objective_coefficients, handle_ptr->get_stream())), variable_bounds( - (!no_deep_copy) - ? rmm::device_uvector(problem_.variable_bounds, handle_ptr->get_stream()) - : rmm::device_uvector(problem_.variable_bounds.size(), handle_ptr->get_stream())), + (!deep_copy) ? rmm::device_uvector(problem_.variable_bounds, handle_ptr->get_stream()) + : rmm::device_uvector(problem_.variable_bounds, handle_ptr->get_stream())), constraint_lower_bounds( - (!no_deep_copy) + (!deep_copy) ? rmm::device_uvector(problem_.constraint_lower_bounds, handle_ptr->get_stream()) - : rmm::device_uvector(problem_.constraint_lower_bounds.size(), - handle_ptr->get_stream())), + : rmm::device_uvector(problem_.constraint_lower_bounds, handle_ptr->get_stream())), constraint_upper_bounds( - (!no_deep_copy) + (!deep_copy) ? rmm::device_uvector(problem_.constraint_upper_bounds, handle_ptr->get_stream()) - : rmm::device_uvector(problem_.constraint_upper_bounds.size(), - handle_ptr->get_stream())), + : rmm::device_uvector(problem_.constraint_upper_bounds, handle_ptr->get_stream())), combined_bounds( - (!no_deep_copy) - ? rmm::device_uvector(problem_.combined_bounds, handle_ptr->get_stream()) - : rmm::device_uvector(problem_.combined_bounds.size(), handle_ptr->get_stream())), + (!deep_copy) ? rmm::device_uvector(problem_.combined_bounds, handle_ptr->get_stream()) + : rmm::device_uvector(problem_.combined_bounds, handle_ptr->get_stream())), variable_types( - (!no_deep_copy) - ? rmm::device_uvector(problem_.variable_types, handle_ptr->get_stream()) - : rmm::device_uvector(problem_.variable_types.size(), handle_ptr->get_stream())), - integer_indices((!no_deep_copy) ? 0 : problem_.integer_indices.size(), - handle_ptr->get_stream()), - binary_indices((!no_deep_copy) ? 0 : problem_.binary_indices.size(), handle_ptr->get_stream()), - nonbinary_indices((!no_deep_copy) ? 0 : problem_.nonbinary_indices.size(), - handle_ptr->get_stream()), - is_binary_variable((!no_deep_copy) ? 0 : problem_.is_binary_variable.size(), - handle_ptr->get_stream()), + (!deep_copy) ? rmm::device_uvector(problem_.variable_types, handle_ptr->get_stream()) + : rmm::device_uvector(problem_.variable_types, handle_ptr->get_stream())), + integer_indices( + (!deep_copy) ? rmm::device_uvector(0, handle_ptr->get_stream()) + : rmm::device_uvector(problem_.integer_indices, handle_ptr->get_stream())), + binary_indices((!deep_copy) + ? rmm::device_uvector(0, handle_ptr->get_stream()) + : rmm::device_uvector(problem_.binary_indices, handle_ptr->get_stream())), + nonbinary_indices((!deep_copy) ? rmm::device_uvector(0, handle_ptr->get_stream()) + : rmm::device_uvector(problem_.nonbinary_indices, + handle_ptr->get_stream())), + is_binary_variable((!deep_copy) ? rmm::device_uvector(0, handle_ptr->get_stream()) + : rmm::device_uvector(problem_.is_binary_variable, + handle_ptr->get_stream())), related_variables(problem_.related_variables, handle_ptr->get_stream()), related_variables_offsets(problem_.related_variables_offsets, handle_ptr->get_stream()), var_names(problem_.var_names), diff --git a/cpp/src/mip/problem/problem.cuh b/cpp/src/mip/problem/problem.cuh index 1834e9e1e..cc1e0d927 100644 --- a/cpp/src/mip/problem/problem.cuh +++ b/cpp/src/mip/problem/problem.cuh @@ -62,7 +62,7 @@ class problem_t { problem_t() = delete; // copy constructor problem_t(const problem_t& problem); - problem_t(const problem_t& problem, bool no_deep_copy); + problem_t(const problem_t& problem, bool deep_copy); problem_t(problem_t&& problem) = default; problem_t& operator=(problem_t&&) = default; void op_problem_cstr_body(const optimization_problem_t& problem_); From da9a2c0ca6e44960dbca78a4664a8d1941e8dc30 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Fri, 24 Oct 2025 12:25:54 +0000 Subject: [PATCH 64/87] bump 1 --- cpp/src/mip/diversity/population.cu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/mip/diversity/population.cu b/cpp/src/mip/diversity/population.cu index 5a841bc33..2d5510826 100644 --- a/cpp/src/mip/diversity/population.cu +++ b/cpp/src/mip/diversity/population.cu @@ -648,7 +648,7 @@ void population_t::eradicate_similar(size_t start_index, solution_t::max()) - indices[count++] = indices[i]; // here count is incremented + indices[count++] = indices[i]; // here count is incremented 1 indices.erase(indices.begin() + count, indices.end()); cuopt_assert(test_invariant(), "Population invariant doesn't hold"); From dab3534b295244313d11a0bce0028a90a04e9ec4 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Fri, 24 Oct 2025 12:26:07 +0000 Subject: [PATCH 65/87] bump 2 --- cpp/src/mip/diversity/population.cu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/mip/diversity/population.cu b/cpp/src/mip/diversity/population.cu index 2d5510826..b100cd33a 100644 --- a/cpp/src/mip/diversity/population.cu +++ b/cpp/src/mip/diversity/population.cu @@ -648,7 +648,7 @@ void population_t::eradicate_similar(size_t start_index, solution_t::max()) - indices[count++] = indices[i]; // here count is incremented 1 + indices[count++] = indices[i]; // here count is incremented 2 indices.erase(indices.begin() + count, indices.end()); cuopt_assert(test_invariant(), "Population invariant doesn't hold"); From 7971a8aa0f8d5f6e0c93ca5651da69dcf4be45c5 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Fri, 24 Oct 2025 12:26:20 +0000 Subject: [PATCH 66/87] bump 3 --- cpp/src/mip/diversity/population.cu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/mip/diversity/population.cu b/cpp/src/mip/diversity/population.cu index b100cd33a..5a841bc33 100644 --- a/cpp/src/mip/diversity/population.cu +++ b/cpp/src/mip/diversity/population.cu @@ -648,7 +648,7 @@ void population_t::eradicate_similar(size_t start_index, solution_t::max()) - indices[count++] = indices[i]; // here count is incremented 2 + indices[count++] = indices[i]; // here count is incremented indices.erase(indices.begin() + count, indices.end()); cuopt_assert(test_invariant(), "Population invariant doesn't hold"); From 5630d85cbb0d359d4ebed51568ee5032baf62658 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Thu, 30 Oct 2025 15:14:31 +0000 Subject: [PATCH 67/87] baseline 1 --- cpp/src/mip/relaxed_lp/relaxed_lp.cu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/mip/relaxed_lp/relaxed_lp.cu b/cpp/src/mip/relaxed_lp/relaxed_lp.cu index 61738921c..e8f9ca2a2 100644 --- a/cpp/src/mip/relaxed_lp/relaxed_lp.cu +++ b/cpp/src/mip/relaxed_lp/relaxed_lp.cu @@ -62,7 +62,7 @@ optimization_problem_solution_t get_relaxed_lp_solution( pdlp_settings.concurrent_halt = settings.concurrent_halt; pdlp_settings.per_constraint_residual = settings.per_constraint_residual; pdlp_settings.first_primal_feasible = settings.return_first_feasible; - pdlp_settings.pdlp_solver_mode = pdlp_solver_mode_t::Stable3; // 3 + pdlp_settings.pdlp_solver_mode = pdlp_solver_mode_t::Stable2; set_pdlp_solver_mode(pdlp_settings); // TODO: set Stable3 here? pdlp_solver_t lp_solver(op_problem, pdlp_settings); From 93e1ec9508a2abf0b06eecf39f9bbf98b69024c3 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Thu, 30 Oct 2025 15:18:07 +0000 Subject: [PATCH 68/87] bump 2 --- cpp/src/mip/solve.cu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/mip/solve.cu b/cpp/src/mip/solve.cu index 44ab24a6a..274ac8dca 100644 --- a/cpp/src/mip/solve.cu +++ b/cpp/src/mip/solve.cu @@ -53,7 +53,7 @@ namespace cuopt::linear_programming { // This serves as both a warm up but also a mandatory initial call to setup cuSparse and cuBLAS static void init_handler(const raft::handle_t* handle_ptr) { - // Init cuBlas / cuSparse context here to avoid having it during solving time + // Init cuBlas / cuSparse context here to avoid having it during solving time 1 RAFT_CUBLAS_TRY(raft::linalg::detail::cublassetpointermode( handle_ptr->get_cublas_handle(), CUBLAS_POINTER_MODE_DEVICE, handle_ptr->get_stream())); RAFT_CUSPARSE_TRY(raft::sparse::detail::cusparsesetpointermode( From 34df2af9259fe5b7c55af07a6f20146acd4a025a Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Thu, 30 Oct 2025 15:18:23 +0000 Subject: [PATCH 69/87] bump 3 --- cpp/src/mip/solve.cu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/mip/solve.cu b/cpp/src/mip/solve.cu index 274ac8dca..f6d6ca381 100644 --- a/cpp/src/mip/solve.cu +++ b/cpp/src/mip/solve.cu @@ -53,7 +53,7 @@ namespace cuopt::linear_programming { // This serves as both a warm up but also a mandatory initial call to setup cuSparse and cuBLAS static void init_handler(const raft::handle_t* handle_ptr) { - // Init cuBlas / cuSparse context here to avoid having it during solving time 1 + // Init cuBlas / cuSparse context here to avoid having it during solving time 2 RAFT_CUBLAS_TRY(raft::linalg::detail::cublassetpointermode( handle_ptr->get_cublas_handle(), CUBLAS_POINTER_MODE_DEVICE, handle_ptr->get_stream())); RAFT_CUSPARSE_TRY(raft::sparse::detail::cusparsesetpointermode( From 9036ed7cd3c80becf271bcec7bdc5cf31344afd3 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Thu, 30 Oct 2025 16:57:23 +0000 Subject: [PATCH 70/87] review comments --- cpp/src/mip/diversity/diversity_manager.cu | 2 +- cpp/src/mip/diversity/lns/rins.cu | 526 ++++++++------------ cpp/src/mip/diversity/lns/rins.cuh | 29 +- cpp/src/mip/diversity/population.cuh | 3 +- cpp/src/mip/feasibility_jump/fj_cpu.cu | 65 +-- cpp/src/mip/feasibility_jump/fj_cpu.cuh | 22 +- cpp/src/mip/local_search/local_search.cu | 4 +- cpp/src/mip/solver.cu | 1 + cpp/src/mip/solver_context.cuh | 7 + cpp/src/mip/utilities/cpu_worker_thread.cuh | 117 +++++ 10 files changed, 367 insertions(+), 409 deletions(-) create mode 100644 cpp/src/mip/utilities/cpu_worker_thread.cuh diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index 5481c2679..50acef3b9 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -734,7 +734,7 @@ void diversity_manager_t::set_simplex_solution(const std::vector& { CUOPT_LOG_DEBUG("Setting simplex solution with objective %f", objective); using sol_t = solution_t; - cudaSetDevice(context.handle_ptr->get_device()); + RAFT_CUDA_TRY(cudaSetDevice(context.handle_ptr->get_device())); context.handle_ptr->sync_stream(); cuopt_func_call(sol_t new_sol(*problem_ptr)); cuopt_assert(new_sol.assignment.size() == solution.size(), "Assignment size mismatch"); diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index e09e4fb40..2b270711f 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -35,79 +35,10 @@ rins_t::rins_t(mip_solver_context_t& context_, } template -rins_thread_t::rins_thread_t() +void rins_thread_t::run_worker() { - cpu_worker = std::thread(&rins_thread_t::cpu_worker_thread, this); -} - -template -rins_thread_t::~rins_thread_t() -{ - if (!cpu_thread_terminate) { kill_cpu_solver(); } -} - -template -void rins_thread_t::cpu_worker_thread() -{ - while (!cpu_thread_terminate) { - // Wait for start signal - { - std::unique_lock lock(cpu_mutex); - cpu_cv.wait(lock, [this] { return cpu_thread_should_start || cpu_thread_terminate; }); - - if (cpu_thread_terminate) break; - - cpu_thread_should_start = false; - } - - // Run RINS - { - raft::common::nvtx::range fun_scope("Running RINS"); - rins_ptr->run_rins(); - } - - { - std::lock_guard lock(cpu_mutex); - cpu_thread_done = true; - } - } -} - -template -void rins_thread_t::kill_cpu_solver() -{ - { - std::lock_guard lock(cpu_mutex); - cpu_thread_terminate = true; - } - cpu_cv.notify_one(); - cpu_worker.join(); -} - -template -void rins_thread_t::start_cpu_solver() -{ - { - std::lock_guard lock(cpu_mutex); - cpu_thread_done = false; - cpu_thread_should_start = true; - } - cpu_cv.notify_one(); -} - -template -void rins_thread_t::stop_cpu_solver() -{ -} - -template -bool rins_thread_t::wait_for_cpu_solver() -{ - while (!cpu_thread_done && !cpu_thread_terminate) { - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - } - - return true; + raft::common::nvtx::range fun_scope("Running RINS"); + rins_ptr->run_rins(); } template @@ -151,7 +82,10 @@ void rins_t::enable() template void rins_t::run_rins() { - if (total_calls == 0) cudaSetDevice(context.handle_ptr->get_device()); + if (total_calls == 0) RAFT_CUDA_TRY(cudaSetDevice(context.handle_ptr->get_device())); + + auto external_solution_size = dm.population.get_external_solution_size(); + if (external_solution_size > 0) dm.population.add_external_solutions_to_population(); if (!dm.population.is_feasible()) return; @@ -162,261 +96,229 @@ void rins_t::run_rins() cuopt_assert(problem_copy->n_integer_vars == problem_ptr->n_integer_vars, "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"); - if (dm.population.current_size() > 0) { - solution_t best_sol(*problem_copy); - // copy the best from the population into a solution_t in the RINS stream - { - std::lock_guard lock(dm.population.write_mutex); - auto& best_feasible_ref = dm.population.best_feasible(); - cuopt_assert(best_feasible_ref.assignment.size() == best_sol.assignment.size(), - "Assignment size mismatch"); - cuopt_assert(best_feasible_ref.get_feasible(), "Best feasible is not feasible"); - expand_device_copy( - best_sol.assignment, best_feasible_ref.assignment, rins_handle.get_stream()); - best_sol.handle_ptr = &rins_handle; - best_sol.problem_ptr = problem_copy.get(); - best_sol.compute_feasibility(); - } - // also scour through the external solution queue to potentially - // nab better solutions early - { - std::lock_guard lock(dm.population.solution_mutex); - auto queues = std::array{std::ref(dm.population.external_solution_queue), - std::ref(dm.population.external_solution_queue_cpufj)}; - for (auto& queue : queues) { - for (auto& h_entry : queue.get()) { - if (h_entry.objective >= best_sol.get_objective()) { continue; } - best_sol.copy_new_assignment(h_entry.solution); - best_sol.compute_feasibility(); - printf("RINS External solution is feas? %d, excess %g\n", - best_sol.get_feasible(), - best_sol.get_total_excess()); - } - } - } - cuopt_assert(best_sol.handle_ptr == &rins_handle, "Handle mismatch"); - - if (!best_sol.get_feasible()) { return; } - i_t sol_size_before_rins = best_sol.assignment.size(); - auto lp_opt_device = cuopt::device_copy(this->lp_optimal_solution, rins_handle.get_stream()); - cuopt_assert(lp_opt_device.size() == problem_ptr->n_variables, "Assignment size mismatch"); - cuopt_assert(best_sol.assignment.size() == problem_ptr->n_variables, + solution_t best_sol(*problem_copy); + // copy the best from the population into a solution_t in the RINS stream + { + std::lock_guard lock(dm.population.write_mutex); + auto& best_feasible_ref = dm.population.best_feasible(); + cuopt_assert(best_feasible_ref.assignment.size() == best_sol.assignment.size(), "Assignment size mismatch"); + cuopt_assert(best_feasible_ref.get_feasible(), "Best feasible is not feasible"); + expand_device_copy(best_sol.assignment, best_feasible_ref.assignment, rins_handle.get_stream()); + best_sol.handle_ptr = &rins_handle; + best_sol.problem_ptr = problem_copy.get(); + best_sol.compute_feasibility(); + } + cuopt_assert(best_sol.handle_ptr == &rins_handle, "Handle mismatch"); + + cuopt_assert(best_sol.get_feasible(), "Best solution is not feasible"); + if (!best_sol.get_feasible()) { return; } + + i_t sol_size_before_rins = best_sol.assignment.size(); + auto lp_opt_device = cuopt::device_copy(this->lp_optimal_solution, rins_handle.get_stream()); + cuopt_assert(lp_opt_device.size() == problem_ptr->n_variables, "Assignment size mismatch"); + cuopt_assert(best_sol.assignment.size() == problem_ptr->n_variables, "Assignment size mismatch"); + + rmm::device_uvector vars_to_fix(problem_ptr->n_integer_vars, rins_handle.get_stream()); + auto end = thrust::copy_if(rins_handle.get_thrust_policy(), + problem_ptr->integer_indices.begin(), + problem_ptr->integer_indices.end(), + vars_to_fix.begin(), + [lpopt = lp_opt_device.data(), + pb = problem_ptr->view(), + incumbent = best_sol.assignment.data()] __device__(i_t var_idx) { + return pb.integer_equal(lpopt[var_idx], incumbent[var_idx]); + }); + vars_to_fix.resize(end - vars_to_fix.begin(), rins_handle.get_stream()); + f_t fractional_ratio = (f_t)(vars_to_fix.size()) / (f_t)problem_ptr->n_integer_vars; + + // abort if the fractional ratio is too low + if (fractional_ratio < settings.min_fractional_ratio) { + CUOPT_LOG_DEBUG("RINS fractional ratio too low, aborting"); + return; + } - rmm::device_uvector vars_to_fix(problem_ptr->n_integer_vars, rins_handle.get_stream()); - auto end = thrust::copy_if(rins_handle.get_thrust_policy(), - thrust::make_counting_iterator(i_t(0)), - thrust::make_counting_iterator(problem_ptr->n_variables), - vars_to_fix.begin(), - [lpopt = lp_opt_device.data(), - incumbent = best_sol.assignment.data(), - pb = problem_ptr->view()] __device__(i_t var_idx) { - if (!pb.is_integer_var(var_idx)) return false; - return pb.integer_equal(lpopt[var_idx], incumbent[var_idx]); - }); - vars_to_fix.resize(end - vars_to_fix.begin(), rins_handle.get_stream()); - f_t fractional_ratio = (f_t)(vars_to_fix.size()) / (f_t)problem_ptr->n_integer_vars; - - // abort if the fractional ratio is too low - if (fractional_ratio < settings.min_fractional_ratio) { - CUOPT_LOG_DEBUG("RINS fractional ratio too low, aborting"); - return; - } - - thrust::default_random_engine g(seed + node_count); - - // shuffle fixing order - thrust::shuffle(rins_handle.get_thrust_policy(), vars_to_fix.begin(), vars_to_fix.end(), g); - - // fix n first according to fractional ratio - f_t rins_ratio = fixrate; - i_t n_to_fix = std::max((int)(vars_to_fix.size() * rins_ratio), 0); - vars_to_fix.resize(n_to_fix, rins_handle.get_stream()); - thrust::sort(rins_handle.get_thrust_policy(), vars_to_fix.begin(), vars_to_fix.end()); + thrust::default_random_engine g(seed + node_count); - cuopt_assert(thrust::all_of(rins_handle.get_thrust_policy(), - vars_to_fix.begin(), - vars_to_fix.end(), - [pb = problem_ptr->view()] __device__(i_t var_idx) { - return pb.is_integer_var(var_idx); - }), - "All variables to fix must be integer variables"); + // shuffle fixing order + thrust::shuffle(rins_handle.get_thrust_policy(), vars_to_fix.begin(), vars_to_fix.end(), g); - if (n_to_fix == 0) { - CUOPT_LOG_DEBUG("RINS no variables to fix"); - return; - } + // fix n first according to fractional ratio + f_t rins_ratio = fixrate; + i_t n_to_fix = std::max((int)(vars_to_fix.size() * rins_ratio), 0); + vars_to_fix.resize(n_to_fix, rins_handle.get_stream()); + thrust::sort(rins_handle.get_thrust_policy(), vars_to_fix.begin(), vars_to_fix.end()); - total_calls++; - node_count_at_last_rins = node_count.load(); - time_limit = std::min(time_limit, dm.timer.remaining_time()); - CUOPT_LOG_DEBUG("Running RINS on solution with objective %g, fixing %d/%d", - best_sol.get_user_objective(), - vars_to_fix.size(), - problem_ptr->n_integer_vars); - CUOPT_LOG_DEBUG("RINS fixrate %g time limit %g", fixrate, time_limit); - CUOPT_LOG_DEBUG("RINS fractional ratio %g%%", fractional_ratio * 100); - - f_t prev_obj = best_sol.get_user_objective(); - - auto [fixed_problem, fixed_assignment, variable_map] = best_sol.fix_variables(vars_to_fix); - CUOPT_LOG_DEBUG( - "new var count %d var_count %d", fixed_problem.n_variables, problem_ptr->n_integer_vars); - - // should probably just do an spmv to get the objective instead. ugly mess of copies - solution_t best_sol_fixed_space(fixed_problem); - cuopt_assert(best_sol_fixed_space.handle_ptr == &rins_handle, "Handle mismatch"); - best_sol_fixed_space.copy_new_assignment( - cuopt::host_copy(fixed_assignment, rins_handle.get_stream())); - best_sol_fixed_space.compute_feasibility(); - CUOPT_LOG_DEBUG("RINS best sol fixed space objective %g", - best_sol_fixed_space.get_user_objective()); - - if (settings.objective_cut) { - f_t objective_cut = - best_sol_fixed_space.get_objective() - - std::max(std::abs(0.001 * best_sol_fixed_space.get_objective()), OBJECTIVE_EPSILON); - fixed_problem.add_cutting_plane_at_objective(objective_cut); - } + cuopt_assert(thrust::all_of(rins_handle.get_thrust_policy(), + vars_to_fix.begin(), + vars_to_fix.end(), + [pb = problem_ptr->view()] __device__(i_t var_idx) { + return pb.is_integer_var(var_idx); + }), + "All variables to fix must be integer variables"); - fixed_problem.presolve_data.reset_additional_vars(fixed_problem, &rins_handle); - fixed_problem.presolve_data.initialize_var_mapping(fixed_problem, &rins_handle); - trivial_presolve(fixed_problem); - fixed_problem.check_problem_representation(true); - - mip_solver_context_t fj_context( - &rins_handle, &fixed_problem, context.settings, context.scaling); - fj_t fj(fj_context); - solution_t fj_solution(fixed_problem); - fj_solution.copy_new_assignment(cuopt::host_copy(fixed_assignment)); - std::vector default_weights(fixed_problem.n_constraints, 1.); - cpu_fj_thread_t cpu_fj_thread; - cpu_fj_thread.fj_cpu = fj.create_cpu_climber( - fj_solution, default_weights, default_weights, 0., fj_settings_t{}, true); - cpu_fj_thread.fj_ptr = &fj; - cpu_fj_thread.fj_cpu->log_prefix = "[RINS] "; - cpu_fj_thread.time_limit = time_limit; - cpu_fj_thread.start_cpu_solver(); - - // run sub-mip - namespace dual_simplex = cuopt::linear_programming::dual_simplex; - dual_simplex::user_problem_t branch_and_bound_problem(&rins_handle); - dual_simplex::simplex_solver_settings_t branch_and_bound_settings; - dual_simplex::mip_solution_t branch_and_bound_solution(1); - dual_simplex::mip_status_t branch_and_bound_status = dual_simplex::mip_status_t::UNSET; - fixed_problem.get_host_user_problem(branch_and_bound_problem); - branch_and_bound_solution.resize(branch_and_bound_problem.num_cols); - // Fill in the settings for branch and bound - branch_and_bound_settings.time_limit = time_limit; - // branch_and_bound_settings.node_limit = 5000 + node_count / 100; // try harder as time goes - // on - branch_and_bound_settings.print_presolve_stats = false; - branch_and_bound_settings.absolute_mip_gap_tol = context.settings.tolerances.absolute_mip_gap; - branch_and_bound_settings.relative_mip_gap_tol = 0.03; // 3% - branch_and_bound_settings.integer_tol = context.settings.tolerances.integrality_tolerance; - branch_and_bound_settings.num_threads = 2; - branch_and_bound_settings.num_bfs_threads = 1; - branch_and_bound_settings.num_diving_threads = 1; - dual_simplex::branch_and_bound_t branch_and_bound(branch_and_bound_problem, - branch_and_bound_settings); - branch_and_bound.set_initial_guess( - cuopt::host_copy(fixed_assignment, rins_handle.get_stream())); - branch_and_bound_status = branch_and_bound.solve(branch_and_bound_solution, "[RINS] "); - bool rins_solution_found = false; - - if (!std::isnan(branch_and_bound_solution.objective)) { - cuopt_assert(fixed_assignment.size() == branch_and_bound_solution.x.size(), - "Assignment size mismatch"); - CUOPT_LOG_DEBUG("RINS solution found. Objective %.16e. Status %d", - branch_and_bound_solution.objective, - int(branch_and_bound_status)); - // first post process the trivial presolve on a device vector - rmm::device_uvector post_processed_solution(branch_and_bound_solution.x.size(), - rins_handle.get_stream()); - raft::copy(post_processed_solution.data(), - branch_and_bound_solution.x.data(), - branch_and_bound_solution.x.size(), - rins_handle.get_stream()); - fixed_problem.post_process_assignment(post_processed_solution, false); - cuopt_assert(post_processed_solution.size() == fixed_assignment.size(), - "Assignment size mismatch"); - rins_handle.sync_stream(); - std::swap(fixed_assignment, post_processed_solution); + if (n_to_fix == 0) { + CUOPT_LOG_DEBUG("RINS no variables to fix"); + return; + } - rins_solution_found = true; - } - if (branch_and_bound_status == dual_simplex::mip_status_t::OPTIMAL) { - CUOPT_LOG_DEBUG("RINS submip optimal"); - // do goldilocks update - fixrate = std::max(fixrate - 0.05, settings.min_fixrate); - time_limit = std::max(time_limit - 2, settings.min_time_limit); - } else if (branch_and_bound_status == dual_simplex::mip_status_t::TIME_LIMIT) { - CUOPT_LOG_DEBUG("RINS submip time limit"); - // do goldilocks update - fixrate = std::min(fixrate + 0.05, settings.max_fixrate); - time_limit = std::min(time_limit + 2, settings.max_time_limit); - } else if (branch_and_bound_status == dual_simplex::mip_status_t::INFEASIBLE) { - CUOPT_LOG_DEBUG("RINS submip infeasible"); - // do goldilocks update, decreasing fixrate - fixrate = std::max(fixrate - 0.05, settings.min_fixrate); - } else { - CUOPT_LOG_DEBUG("RINS solution not found"); - // do goldilocks update - fixrate = std::min(fixrate + 0.05, settings.max_fixrate); - time_limit = std::min(time_limit + 2, settings.max_time_limit); - } + total_calls++; + node_count_at_last_rins = node_count.load(); + time_limit = std::min(time_limit, dm.timer.remaining_time()); + CUOPT_LOG_DEBUG("Running RINS on solution with objective %g, fixing %d/%d", + best_sol.get_user_objective(), + vars_to_fix.size(), + problem_ptr->n_integer_vars); + CUOPT_LOG_DEBUG("RINS fixrate %g time limit %g", fixrate, time_limit); + CUOPT_LOG_DEBUG("RINS fractional ratio %g%%", fractional_ratio * 100); + + f_t prev_obj = best_sol.get_user_objective(); + + auto [fixed_problem, fixed_assignment, variable_map] = best_sol.fix_variables(vars_to_fix); + CUOPT_LOG_DEBUG( + "new var count %d var_count %d", fixed_problem.n_variables, problem_ptr->n_integer_vars); + + // should probably just do an spmv to get the objective instead. ugly mess of copies + solution_t best_sol_fixed_space(fixed_problem); + cuopt_assert(best_sol_fixed_space.handle_ptr == &rins_handle, "Handle mismatch"); + best_sol_fixed_space.copy_new_assignment( + cuopt::host_copy(fixed_assignment, rins_handle.get_stream())); + best_sol_fixed_space.compute_feasibility(); + CUOPT_LOG_DEBUG("RINS best sol fixed space objective %g", + best_sol_fixed_space.get_user_objective()); + + if (settings.objective_cut) { + f_t objective_cut = + best_sol_fixed_space.get_objective() - + std::max(std::abs(0.001 * best_sol_fixed_space.get_objective()), OBJECTIVE_EPSILON); + fixed_problem.add_cutting_plane_at_objective(objective_cut); + } - cpu_fj_thread.stop_cpu_solver(); - bool fj_solution_found = cpu_fj_thread.wait_for_cpu_solver(); - CUOPT_LOG_DEBUG("RINS FJ ran for %d iterations", cpu_fj_thread.fj_cpu->iterations); - if (fj_solution_found) { - CUOPT_LOG_DEBUG("RINS FJ solution found. Objective %.16e", - cpu_fj_thread.fj_cpu->h_best_objective); - if (cpu_fj_thread.fj_cpu->h_best_objective < branch_and_bound_solution.objective || - std::isnan(branch_and_bound_solution.objective)) { - // first post process the trivial presolve on a device vector - rmm::device_uvector post_processed_solution(branch_and_bound_solution.x.size(), - rins_handle.get_stream()); - raft::copy(post_processed_solution.data(), - cpu_fj_thread.fj_cpu->h_best_assignment.data(), - cpu_fj_thread.fj_cpu->h_best_assignment.size(), - rins_handle.get_stream()); - fixed_problem.post_process_assignment(post_processed_solution, false); - cuopt_assert(post_processed_solution.size() == fixed_assignment.size(), - "Assignment size mismatch"); - rins_handle.sync_stream(); - std::swap(fixed_assignment, post_processed_solution); - - CUOPT_LOG_DEBUG("RINS FJ solution improved objective"); - rins_solution_found = true; - } - } - cpu_fj_thread.kill_cpu_solver(); + fixed_problem.presolve_data.reset_additional_vars(fixed_problem, &rins_handle); + fixed_problem.presolve_data.initialize_var_mapping(fixed_problem, &rins_handle); + trivial_presolve(fixed_problem); + fixed_problem.check_problem_representation(true); + + std::vector> rins_solution_queue; + + mip_solver_context_t fj_context( + &rins_handle, &fixed_problem, context.settings, context.scaling); + fj_t fj(fj_context); + solution_t fj_solution(fixed_problem); + fj_solution.copy_new_assignment(cuopt::host_copy(fixed_assignment)); + std::vector default_weights(fixed_problem.n_constraints, 1.); + cpu_fj_thread_t cpu_fj_thread; + cpu_fj_thread.fj_cpu = + fj.create_cpu_climber(fj_solution, default_weights, default_weights, 0., fj_settings_t{}, true); + cpu_fj_thread.fj_ptr = &fj; + cpu_fj_thread.fj_cpu->log_prefix = "[RINS] "; + cpu_fj_thread.time_limit = time_limit; + cpu_fj_thread.start_cpu_solver(); + + f_t lower_bound = context.branch_and_bound_ptr ? context.branch_and_bound_ptr->get_lower_bound() + : -std::numeric_limits::infinity(); + f_t current_mip_gap = compute_rel_mip_gap(prev_obj, lower_bound); + + // run sub-mip + namespace dual_simplex = cuopt::linear_programming::dual_simplex; + dual_simplex::user_problem_t branch_and_bound_problem(&rins_handle); + dual_simplex::simplex_solver_settings_t branch_and_bound_settings; + dual_simplex::mip_solution_t branch_and_bound_solution(1); + dual_simplex::mip_status_t branch_and_bound_status = dual_simplex::mip_status_t::UNSET; + fixed_problem.get_host_user_problem(branch_and_bound_problem); + branch_and_bound_solution.resize(branch_and_bound_problem.num_cols); + // Fill in the settings for branch and bound + branch_and_bound_settings.time_limit = time_limit; + // branch_and_bound_settings.node_limit = 5000 + node_count / 100; // try harder as time goes + // on + branch_and_bound_settings.print_presolve_stats = false; + branch_and_bound_settings.absolute_mip_gap_tol = context.settings.tolerances.absolute_mip_gap; + branch_and_bound_settings.relative_mip_gap_tol = + std::min(current_mip_gap, (f_t)settings.target_mip_gap); + branch_and_bound_settings.integer_tol = context.settings.tolerances.integrality_tolerance; + branch_and_bound_settings.num_threads = 2; + branch_and_bound_settings.num_bfs_threads = 1; + branch_and_bound_settings.num_diving_threads = 1; + branch_and_bound_settings.solution_callback = [this, &rins_solution_queue]( + std::vector& solution, f_t objective) { + rins_solution_queue.push_back(solution); + }; + dual_simplex::branch_and_bound_t branch_and_bound(branch_and_bound_problem, + branch_and_bound_settings); + branch_and_bound.set_initial_guess(cuopt::host_copy(fixed_assignment, rins_handle.get_stream())); + branch_and_bound_status = branch_and_bound.solve(branch_and_bound_solution, "[RINS] "); + + if (!std::isnan(branch_and_bound_solution.objective)) { + CUOPT_LOG_DEBUG("RINS submip solution found. Objective %.16e. Status %d", + branch_and_bound_solution.objective, + int(branch_and_bound_status)); + cuopt_assert(rins_solution_queue.size() > 0, "RINS solution queue is unexpectedly empty"); + } + if (branch_and_bound_status == dual_simplex::mip_status_t::OPTIMAL) { + CUOPT_LOG_DEBUG("RINS submip optimal"); + // do goldilocks update + fixrate = std::max(fixrate - 0.05, settings.min_fixrate); + time_limit = std::max(time_limit - 2, settings.min_time_limit); + } else if (branch_and_bound_status == dual_simplex::mip_status_t::TIME_LIMIT) { + CUOPT_LOG_DEBUG("RINS submip time limit"); + // do goldilocks update + fixrate = std::min(fixrate + 0.05, settings.max_fixrate); + time_limit = std::min(time_limit + 2, settings.max_time_limit); + } else if (branch_and_bound_status == dual_simplex::mip_status_t::INFEASIBLE) { + CUOPT_LOG_DEBUG("RINS submip infeasible"); + // do goldilocks update, decreasing fixrate + fixrate = std::max(fixrate - 0.05, settings.min_fixrate); + } else { + CUOPT_LOG_DEBUG("RINS solution not found"); + // do goldilocks update + fixrate = std::min(fixrate + 0.05, settings.max_fixrate); + time_limit = std::min(time_limit + 2, settings.max_time_limit); + } - // unfix the assignment on given result no matter if it is feasible - best_sol.unfix_variables(fixed_assignment, variable_map); + cpu_fj_thread.stop_cpu_solver(); + bool fj_solution_found = cpu_fj_thread.wait_for_cpu_solver(); + CUOPT_LOG_DEBUG("RINS FJ ran for %d iterations", cpu_fj_thread.fj_cpu->iterations); + if (fj_solution_found) { + CUOPT_LOG_DEBUG("RINS FJ solution found. Objective %.16e", + cpu_fj_thread.fj_cpu->h_best_objective); + rins_solution_queue.push_back(cpu_fj_thread.fj_cpu->h_best_assignment); + } + cpu_fj_thread.kill_cpu_worker(); + + bool improvement_found = false; + for (auto& fixed_sol : rins_solution_queue) { + cuopt_assert(fixed_assignment.size() == fixed_sol.size(), "Assignment size mismatch"); + rmm::device_uvector post_processed_solution(fixed_sol.size(), rins_handle.get_stream()); + raft::copy( + post_processed_solution.data(), fixed_sol.data(), fixed_sol.size(), rins_handle.get_stream()); + fixed_problem.post_process_assignment(post_processed_solution, false); + cuopt_assert(post_processed_solution.size() == fixed_assignment.size(), + "Assignment size mismatch"); + rins_handle.sync_stream(); + + rmm::device_uvector unfixed_assignment(post_processed_solution.size(), + rins_handle.get_stream()); + raft::copy(unfixed_assignment.data(), + post_processed_solution.data(), + post_processed_solution.size(), + rins_handle.get_stream()); + best_sol.unfix_variables(unfixed_assignment, variable_map); best_sol.compute_feasibility(); - if (rins_solution_found && best_sol.get_feasible()) { - cuopt_assert(best_sol.test_number_all_integer(), "All must be integers after offspring"); - CUOPT_LOG_DEBUG("RINS Solution: feasible: %d, objective: %g", - best_sol.get_feasible(), - best_sol.get_user_objective()); - if (best_sol.get_user_objective() < prev_obj) { - CUOPT_LOG_DEBUG("RINS solution improved objective from %g to %g", - prev_obj, - best_sol.get_user_objective()); - total_success++; - } + + if (best_sol.get_feasible()) { + cuopt_assert(best_sol.test_number_all_integer(), "All must be integers after RINS"); + if (best_sol.get_user_objective() < prev_obj) { improvement_found = true; } cuopt_assert(best_sol.assignment.size() == sol_size_before_rins, "Assignment size mismatch"); cuopt_assert(best_sol.assignment.size() == problem_ptr->n_variables, "Assignment size mismatch"); - if (best_sol.get_objective() < dm.population.best_feasible_objective) { - dm.population.add_external_solution( - best_sol.get_host_assignment(), best_sol.get_objective(), solution_origin_t::RINS); - } + dm.population.add_external_solution( + best_sol.get_host_assignment(), best_sol.get_objective(), solution_origin_t::RINS); } } + + if (improvement_found) total_success++; CUOPT_LOG_DEBUG("RINS calls/successes %d/%d", total_calls, total_success); } diff --git a/cpp/src/mip/diversity/lns/rins.cuh b/cpp/src/mip/diversity/lns/rins.cuh index ac7ad0160..a057bdd65 100644 --- a/cpp/src/mip/diversity/lns/rins.cuh +++ b/cpp/src/mip/diversity/lns/rins.cuh @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -43,9 +44,10 @@ struct rins_settings_t { double max_fixrate = 0.8; double default_fixrate = 0.5; double min_fractional_ratio = 0.3; - double min_time_limit = 10.; + double min_time_limit = 3.; double max_time_limit = 20.; - double default_time_limit = 6.; + double default_time_limit = 3.; + double target_mip_gap = 0.03; bool objective_cut = true; }; @@ -53,23 +55,11 @@ template class rins_t; template -struct rins_thread_t { - rins_thread_t(); - ~rins_thread_t(); - - void cpu_worker_thread(); - void start_cpu_solver(); - void stop_cpu_solver(); - bool wait_for_cpu_solver(); - void kill_cpu_solver(); - - std::thread cpu_worker; - std::mutex cpu_mutex; - std::condition_variable cpu_cv; - std::atomic should_stop{false}; - std::atomic cpu_thread_should_start{false}; - std::atomic cpu_thread_done{true}; - std::atomic cpu_thread_terminate{false}; +struct rins_thread_t : public cpu_worker_thread_base_t> { + void run_worker(); + void on_terminate() {} + void on_start() {} + bool get_result() { return true; } rins_t* rins_ptr{nullptr}; }; @@ -106,6 +96,7 @@ class rins_t { i_t seed; std::atomic enabled{false}; + std::atomic lower_bound{0.}; std::atomic node_count{0}; std::atomic node_count_at_last_rins{0}; diff --git a/cpp/src/mip/diversity/population.cuh b/cpp/src/mip/diversity/population.cuh index c590c62df..01332728a 100644 --- a/cpp/src/mip/diversity/population.cuh +++ b/cpp/src/mip/diversity/population.cuh @@ -35,14 +35,13 @@ namespace cuopt::linear_programming::detail { template class diversity_manager_t; -enum class solution_origin_t { BRANCH_AND_BOUND, CPUFJ, RENS, RINS, EXTERNAL }; +enum class solution_origin_t { BRANCH_AND_BOUND, CPUFJ, RINS, EXTERNAL }; constexpr const char* solution_origin_to_string(solution_origin_t origin) { switch (origin) { case solution_origin_t::BRANCH_AND_BOUND: return "B&B"; case solution_origin_t::CPUFJ: return "CPUFJ"; - case solution_origin_t::RENS: return "RENS"; case solution_origin_t::RINS: return "RINS"; case solution_origin_t::EXTERNAL: return "injected"; default: return "unknown"; diff --git a/cpp/src/mip/feasibility_jump/fj_cpu.cu b/cpp/src/mip/feasibility_jump/fj_cpu.cu index 4f71c71e7..3c3ac5e9b 100644 --- a/cpp/src/mip/feasibility_jump/fj_cpu.cu +++ b/cpp/src/mip/feasibility_jump/fj_cpu.cu @@ -1055,64 +1055,23 @@ bool fj_t::cpu_solve(fj_cpu_climber_t& fj_cpu, f_t in_time_l } template -cpu_fj_thread_t::cpu_fj_thread_t() +void cpu_fj_thread_t::run_worker() { - cpu_worker = std::thread(&cpu_fj_thread_t::cpu_worker_thread, this); + bool solution_found = fj_ptr->cpu_solve(*fj_cpu, time_limit); + cpu_fj_solution_found = solution_found; } template -cpu_fj_thread_t::~cpu_fj_thread_t() +void cpu_fj_thread_t::on_terminate() { - if (!cpu_thread_terminate) { kill_cpu_solver(); } + if (fj_cpu) fj_cpu->halted = true; } template -void cpu_fj_thread_t::cpu_worker_thread() -{ - while (!cpu_thread_terminate) { - { - std::unique_lock lock(cpu_mutex); - cpu_cv.wait(lock, [this] { return cpu_thread_should_start || cpu_thread_terminate; }); - - if (cpu_thread_terminate) break; - - cpu_thread_done = false; - cpu_thread_should_start = false; - } - - bool solution_found = fj_ptr->cpu_solve(*fj_cpu, time_limit); - - { - std::lock_guard lock(cpu_mutex); - cpu_fj_solution_found = solution_found; - cpu_thread_done = true; - } - } -} - -template -void cpu_fj_thread_t::kill_cpu_solver() -{ - { - std::lock_guard lock(cpu_mutex); - cpu_thread_terminate = true; - if (fj_cpu) fj_cpu->halted = true; - } - cpu_cv.notify_one(); - cpu_worker.join(); -} - -template -void cpu_fj_thread_t::start_cpu_solver() +void cpu_fj_thread_t::on_start() { cuopt_assert(fj_cpu != nullptr, "fj_cpu must not be null"); - { - std::lock_guard lock(cpu_mutex); - cpu_thread_done = false; - cpu_thread_should_start = true; - fj_cpu->halted = false; - } - cpu_cv.notify_one(); + fj_cpu->halted = false; } template @@ -1121,16 +1080,6 @@ void cpu_fj_thread_t::stop_cpu_solver() fj_cpu->halted = true; } -template -bool cpu_fj_thread_t::wait_for_cpu_solver() -{ - while (!cpu_thread_done && !cpu_thread_terminate) { - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - } - - return cpu_fj_solution_found; -} - #if MIP_INSTANTIATE_FLOAT template class fj_t; template class cpu_fj_thread_t; diff --git a/cpp/src/mip/feasibility_jump/fj_cpu.cuh b/cpp/src/mip/feasibility_jump/fj_cpu.cuh index 059b32ff0..cdce44ca7 100644 --- a/cpp/src/mip/feasibility_jump/fj_cpu.cuh +++ b/cpp/src/mip/feasibility_jump/fj_cpu.cuh @@ -27,6 +27,7 @@ #include #include +#include namespace cuopt::linear_programming::detail { @@ -127,23 +128,14 @@ struct fj_cpu_climber_t { }; template -struct cpu_fj_thread_t { - cpu_fj_thread_t(); - ~cpu_fj_thread_t(); +struct cpu_fj_thread_t : public cpu_worker_thread_base_t> { + void run_worker(); + void on_terminate(); + void on_start(); + bool get_result() { return cpu_fj_solution_found; } - void cpu_worker_thread(); - void start_cpu_solver(); void stop_cpu_solver(); - bool wait_for_cpu_solver(); // return feasibility - void kill_cpu_solver(); - - std::thread cpu_worker; - std::mutex cpu_mutex; - std::condition_variable cpu_cv; - std::atomic should_stop{false}; - std::atomic cpu_thread_should_start{false}; - std::atomic cpu_thread_done{false}; - std::atomic cpu_thread_terminate{false}; + std::atomic cpu_fj_solution_found{false}; f_t time_limit{+std::numeric_limits::infinity()}; std::unique_ptr> fj_cpu; diff --git a/cpp/src/mip/local_search/local_search.cu b/cpp/src/mip/local_search/local_search.cu index ab0b9fc25..d808b00cb 100644 --- a/cpp/src/mip/local_search/local_search.cu +++ b/cpp/src/mip/local_search/local_search.cu @@ -146,9 +146,9 @@ template void local_search_t::stop_cpufj_scratch_threads() { for (auto& cpu_fj : scratch_cpu_fj) { - cpu_fj.kill_cpu_solver(); + cpu_fj.kill_cpu_worker(); } - scratch_cpu_fj_on_lp_opt.kill_cpu_solver(); + scratch_cpu_fj_on_lp_opt.kill_cpu_worker(); } template diff --git a/cpp/src/mip/solver.cu b/cpp/src/mip/solver.cu index aa31999a9..8f4e8023a 100644 --- a/cpp/src/mip/solver.cu +++ b/cpp/src/mip/solver.cu @@ -164,6 +164,7 @@ solution_t mip_solver_t::run_solver() std::unique_ptr> branch_and_bound; branch_and_bound_solution_helper_t solution_helper(&dm, branch_and_bound_settings); dual_simplex::mip_solution_t branch_and_bound_solution(1); + context.branch_and_bound_ptr = branch_and_bound.get(); if (!context.settings.heuristics_only) { // Convert the presolved problem to dual_simplex::user_problem_t diff --git a/cpp/src/mip/solver_context.cuh b/cpp/src/mip/solver_context.cuh index 1f7f67623..f3a3244c1 100644 --- a/cpp/src/mip/solver_context.cuh +++ b/cpp/src/mip/solver_context.cuh @@ -23,6 +23,12 @@ #pragma once +// forward declare +namespace cuopt::linear_programming::dual_simplex { +template +class branch_and_bound_t; +} + namespace cuopt::linear_programming::detail { // Aggregate structure containing the global context of the solving process for convenience: @@ -42,6 +48,7 @@ struct mip_solver_context_t { raft::handle_t const* const handle_ptr; problem_t* problem_ptr; + dual_simplex::branch_and_bound_t* branch_and_bound_ptr{nullptr}; const mip_solver_settings_t settings; pdlp_initial_scaling_strategy_t& scaling; solver_stats_t stats; diff --git a/cpp/src/mip/utilities/cpu_worker_thread.cuh b/cpp/src/mip/utilities/cpu_worker_thread.cuh new file mode 100644 index 000000000..5544867d5 --- /dev/null +++ b/cpp/src/mip/utilities/cpu_worker_thread.cuh @@ -0,0 +1,117 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights + * reserved. SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include + +namespace cuopt::linear_programming::detail { + +template +class cpu_worker_thread_base_t { + public: + cpu_worker_thread_base_t(); + ~cpu_worker_thread_base_t(); + + void start_cpu_solver(); + bool wait_for_cpu_solver(); + void kill_cpu_worker(); + + void cpu_worker_thread(); + + std::thread cpu_worker; + std::mutex cpu_mutex; + std::condition_variable cpu_cv; + std::atomic should_stop{false}; + std::atomic cpu_thread_should_start{false}; + std::atomic cpu_thread_done{true}; + std::atomic cpu_thread_terminate{false}; +}; + +template +cpu_worker_thread_base_t::cpu_worker_thread_base_t() +{ + cpu_worker = std::thread(&cpu_worker_thread_base_t::cpu_worker_thread, this); +} + +template +cpu_worker_thread_base_t::~cpu_worker_thread_base_t() +{ + if (!cpu_thread_terminate) { kill_cpu_worker(); } +} + +template +void cpu_worker_thread_base_t::cpu_worker_thread() +{ + while (!cpu_thread_terminate) { + { + std::unique_lock lock(cpu_mutex); + cpu_cv.wait(lock, [this] { return cpu_thread_should_start || cpu_thread_terminate; }); + + if (cpu_thread_terminate) break; + + cpu_thread_done = false; + cpu_thread_should_start = false; + } + + static_cast(this)->run_worker(); + + { + std::lock_guard lock(cpu_mutex); + cpu_thread_done = true; + } + } +} + +template +void cpu_worker_thread_base_t::kill_cpu_worker() +{ + { + std::lock_guard lock(cpu_mutex); + cpu_thread_terminate = true; + static_cast(this)->on_terminate(); + } + cpu_cv.notify_one(); + cpu_worker.join(); +} + +template +void cpu_worker_thread_base_t::start_cpu_solver() +{ + { + std::lock_guard lock(cpu_mutex); + cpu_thread_done = false; + cpu_thread_should_start = true; + static_cast(this)->on_start(); + } + cpu_cv.notify_one(); +} + +template +bool cpu_worker_thread_base_t::wait_for_cpu_solver() +{ + while (!cpu_thread_done && !cpu_thread_terminate) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + + return static_cast(this)->get_result(); +} + +} // namespace cuopt::linear_programming::detail From 994621da3cacef660a3c7a5aba1b62bebb7c308f Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Thu, 30 Oct 2025 17:43:36 +0000 Subject: [PATCH 71/87] fix problem copy --- cpp/src/mip/diversity/lns/rins.cu | 8 +-- cpp/src/mip/problem/problem.cu | 86 ++++++++++++++++--------------- cpp/src/mip/problem/problem.cuh | 2 +- 3 files changed, 50 insertions(+), 46 deletions(-) diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index 2b270711f..35c4af15c 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -71,10 +71,10 @@ void rins_t::node_callback(const std::vector& solution, f_t objec template void rins_t::enable() { - rins_thread = std::make_unique>(); - rins_thread->rins_ptr = this; - seed = cuopt::seed_generator::get_seed(); - problem_copy = std::make_unique>(*problem_ptr, /*deep_copy=*/true); + rins_thread = std::make_unique>(); + rins_thread->rins_ptr = this; + seed = cuopt::seed_generator::get_seed(); + problem_copy = std::make_unique>(*problem_ptr); problem_copy->handle_ptr = &rins_handle; enabled = true; } diff --git a/cpp/src/mip/problem/problem.cu b/cpp/src/mip/problem/problem.cu index aef2bc9f2..3efaa10d8 100644 --- a/cpp/src/mip/problem/problem.cu +++ b/cpp/src/mip/problem/problem.cu @@ -204,7 +204,7 @@ problem_t::problem_t(const problem_t& problem_) } template -problem_t::problem_t(const problem_t& problem_, bool deep_copy) +problem_t::problem_t(const problem_t& problem_, bool no_deep_copy) : original_problem_ptr(problem_.original_problem_ptr), tolerances(problem_.tolerances), handle_ptr(problem_.handle_ptr), @@ -219,66 +219,70 @@ problem_t::problem_t(const problem_t& problem_, bool deep_co empty(problem_.empty), is_binary_pb(problem_.is_binary_pb), // Copy constructor used by PDLP and MIP - // PDLP uses the version with deep_copy = false which deep copy some fields but doesn't + // PDLP uses the version with no_deep_copy = false which deep copy some fields but doesn't // allocate others that are not needed in PDLP presolve_data( - (!deep_copy) + (!no_deep_copy) ? std::move(presolve_data_t{*problem_.original_problem_ptr, handle_ptr->get_stream()}) : std::move(presolve_data_t{problem_.presolve_data, handle_ptr->get_stream()})), original_ids(problem_.original_ids), reverse_original_ids(problem_.reverse_original_ids), reverse_coefficients( - (!deep_copy) + (!no_deep_copy) ? rmm::device_uvector(problem_.reverse_coefficients, handle_ptr->get_stream()) - : rmm::device_uvector(problem_.reverse_coefficients, handle_ptr->get_stream())), + : rmm::device_uvector(problem_.reverse_coefficients.size(), handle_ptr->get_stream())), reverse_constraints( - (!deep_copy) + (!no_deep_copy) ? rmm::device_uvector(problem_.reverse_constraints, handle_ptr->get_stream()) - : rmm::device_uvector(problem_.reverse_constraints, handle_ptr->get_stream())), + : rmm::device_uvector(problem_.reverse_constraints.size(), handle_ptr->get_stream())), reverse_offsets( - (!deep_copy) ? rmm::device_uvector(problem_.reverse_offsets, handle_ptr->get_stream()) - : rmm::device_uvector(problem_.reverse_offsets, handle_ptr->get_stream())), - coefficients((!deep_copy) - ? rmm::device_uvector(problem_.coefficients, handle_ptr->get_stream()) - : rmm::device_uvector(problem_.coefficients, handle_ptr->get_stream())), - variables((!deep_copy) + (!no_deep_copy) + ? rmm::device_uvector(problem_.reverse_offsets, handle_ptr->get_stream()) + : rmm::device_uvector(problem_.reverse_offsets.size(), handle_ptr->get_stream())), + coefficients( + (!no_deep_copy) + ? rmm::device_uvector(problem_.coefficients, handle_ptr->get_stream()) + : rmm::device_uvector(problem_.coefficients.size(), handle_ptr->get_stream())), + variables((!no_deep_copy) ? rmm::device_uvector(problem_.variables, handle_ptr->get_stream()) - : rmm::device_uvector(problem_.variables, handle_ptr->get_stream())), - offsets((!deep_copy) ? rmm::device_uvector(problem_.offsets, handle_ptr->get_stream()) - : rmm::device_uvector(problem_.offsets, handle_ptr->get_stream())), + : rmm::device_uvector(problem_.variables.size(), handle_ptr->get_stream())), + offsets((!no_deep_copy) + ? rmm::device_uvector(problem_.offsets, handle_ptr->get_stream()) + : rmm::device_uvector(problem_.offsets.size(), handle_ptr->get_stream())), objective_coefficients( - (!deep_copy) + (!no_deep_copy) ? rmm::device_uvector(problem_.objective_coefficients, handle_ptr->get_stream()) - : rmm::device_uvector(problem_.objective_coefficients, handle_ptr->get_stream())), + : rmm::device_uvector(problem_.objective_coefficients.size(), + handle_ptr->get_stream())), variable_bounds( - (!deep_copy) ? rmm::device_uvector(problem_.variable_bounds, handle_ptr->get_stream()) - : rmm::device_uvector(problem_.variable_bounds, handle_ptr->get_stream())), + (!no_deep_copy) + ? rmm::device_uvector(problem_.variable_bounds, handle_ptr->get_stream()) + : rmm::device_uvector(problem_.variable_bounds.size(), handle_ptr->get_stream())), constraint_lower_bounds( - (!deep_copy) + (!no_deep_copy) ? rmm::device_uvector(problem_.constraint_lower_bounds, handle_ptr->get_stream()) - : rmm::device_uvector(problem_.constraint_lower_bounds, handle_ptr->get_stream())), + : rmm::device_uvector(problem_.constraint_lower_bounds.size(), + handle_ptr->get_stream())), constraint_upper_bounds( - (!deep_copy) + (!no_deep_copy) ? rmm::device_uvector(problem_.constraint_upper_bounds, handle_ptr->get_stream()) - : rmm::device_uvector(problem_.constraint_upper_bounds, handle_ptr->get_stream())), + : rmm::device_uvector(problem_.constraint_upper_bounds.size(), + handle_ptr->get_stream())), combined_bounds( - (!deep_copy) ? rmm::device_uvector(problem_.combined_bounds, handle_ptr->get_stream()) - : rmm::device_uvector(problem_.combined_bounds, handle_ptr->get_stream())), + (!no_deep_copy) + ? rmm::device_uvector(problem_.combined_bounds, handle_ptr->get_stream()) + : rmm::device_uvector(problem_.combined_bounds.size(), handle_ptr->get_stream())), variable_types( - (!deep_copy) ? rmm::device_uvector(problem_.variable_types, handle_ptr->get_stream()) - : rmm::device_uvector(problem_.variable_types, handle_ptr->get_stream())), - integer_indices( - (!deep_copy) ? rmm::device_uvector(0, handle_ptr->get_stream()) - : rmm::device_uvector(problem_.integer_indices, handle_ptr->get_stream())), - binary_indices((!deep_copy) - ? rmm::device_uvector(0, handle_ptr->get_stream()) - : rmm::device_uvector(problem_.binary_indices, handle_ptr->get_stream())), - nonbinary_indices((!deep_copy) ? rmm::device_uvector(0, handle_ptr->get_stream()) - : rmm::device_uvector(problem_.nonbinary_indices, - handle_ptr->get_stream())), - is_binary_variable((!deep_copy) ? rmm::device_uvector(0, handle_ptr->get_stream()) - : rmm::device_uvector(problem_.is_binary_variable, - handle_ptr->get_stream())), + (!no_deep_copy) + ? rmm::device_uvector(problem_.variable_types, handle_ptr->get_stream()) + : rmm::device_uvector(problem_.variable_types.size(), handle_ptr->get_stream())), + integer_indices((!no_deep_copy) ? 0 : problem_.integer_indices.size(), + handle_ptr->get_stream()), + binary_indices((!no_deep_copy) ? 0 : problem_.binary_indices.size(), handle_ptr->get_stream()), + nonbinary_indices((!no_deep_copy) ? 0 : problem_.nonbinary_indices.size(), + handle_ptr->get_stream()), + is_binary_variable((!no_deep_copy) ? 0 : problem_.is_binary_variable.size(), + handle_ptr->get_stream()), related_variables(problem_.related_variables, handle_ptr->get_stream()), related_variables_offsets(problem_.related_variables_offsets, handle_ptr->get_stream()), var_names(problem_.var_names), @@ -1186,7 +1190,7 @@ problem_t problem_t::get_problem_after_fixing_vars( raft::common::nvtx::range fun_scope("get_problem_after_fixing_vars"); auto start_time = std::chrono::high_resolution_clock::now(); cuopt_assert(n_variables == assignment.size(), "Assignment size issue"); - problem_t problem(*this, true); + problem_t problem(*this); CUOPT_LOG_DEBUG("Fixing %d variables", variables_to_fix.size()); // we will gather from this and scatter back to the original problem variable_map.resize(assignment.size() - variables_to_fix.size(), handle_ptr->get_stream()); diff --git a/cpp/src/mip/problem/problem.cuh b/cpp/src/mip/problem/problem.cuh index b70397679..f0d044640 100644 --- a/cpp/src/mip/problem/problem.cuh +++ b/cpp/src/mip/problem/problem.cuh @@ -62,7 +62,7 @@ class problem_t { problem_t() = delete; // copy constructor problem_t(const problem_t& problem); - problem_t(const problem_t& problem, bool deep_copy); + problem_t(const problem_t& problem, bool no_deep_copy); problem_t(problem_t&& problem) = default; problem_t& operator=(problem_t&&) = default; void op_problem_cstr_body(const optimization_problem_t& problem_); From 122fb22b1d1ae8fcc82a7c79dce7ee5482bcb5e7 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Thu, 30 Oct 2025 17:43:59 +0000 Subject: [PATCH 72/87] bump 1 --- cpp/src/mip/diversity/lns/rins.cu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index 35c4af15c..393f1bcfe 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -75,7 +75,7 @@ void rins_t::enable() rins_thread->rins_ptr = this; seed = cuopt::seed_generator::get_seed(); problem_copy = std::make_unique>(*problem_ptr); - problem_copy->handle_ptr = &rins_handle; + problem_copy->handle_ptr = &rins_handle; // 1 enabled = true; } From 1e53e2ba68a44b371b73d87cb5fd5bb14713aa83 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Thu, 30 Oct 2025 17:45:12 +0000 Subject: [PATCH 73/87] restore --- cpp/src/mip/diversity/lns/rins.cu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index 393f1bcfe..35c4af15c 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -75,7 +75,7 @@ void rins_t::enable() rins_thread->rins_ptr = this; seed = cuopt::seed_generator::get_seed(); problem_copy = std::make_unique>(*problem_ptr); - problem_copy->handle_ptr = &rins_handle; // 1 + problem_copy->handle_ptr = &rins_handle; enabled = true; } From 63a19c25de53489feced0551475fe72ce3b61840 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Fri, 31 Oct 2025 10:02:15 +0000 Subject: [PATCH 74/87] fix cpu worker destructor crash --- cpp/src/mip/diversity/lns/rins.cu | 10 ++++++-- cpp/src/mip/diversity/lns/rins.cuh | 2 ++ cpp/src/mip/feasibility_jump/fj_cpu.cu | 6 +++++ cpp/src/mip/feasibility_jump/fj_cpu.cuh | 2 ++ cpp/src/mip/local_search/local_search.cu | 4 +-- cpp/src/mip/utilities/cpu_worker_thread.cuh | 27 ++++++++++++++++++--- 6 files changed, 43 insertions(+), 8 deletions(-) diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index 35c4af15c..d8c40e6fa 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -34,6 +34,12 @@ rins_t::rins_t(mip_solver_context_t& context_, time_limit = settings.default_time_limit; } +template +rins_thread_t::~rins_thread_t() +{ + this->request_termination(); +} + template void rins_thread_t::run_worker() { @@ -136,7 +142,7 @@ void rins_t::run_rins() // abort if the fractional ratio is too low if (fractional_ratio < settings.min_fractional_ratio) { - CUOPT_LOG_DEBUG("RINS fractional ratio too low, aborting"); + CUOPT_LOG_TRACE("RINS fractional ratio too low, aborting"); return; } @@ -285,7 +291,7 @@ void rins_t::run_rins() cpu_fj_thread.fj_cpu->h_best_objective); rins_solution_queue.push_back(cpu_fj_thread.fj_cpu->h_best_assignment); } - cpu_fj_thread.kill_cpu_worker(); + // Thread will be automatically terminated and joined by destructor bool improvement_found = false; for (auto& fixed_sol : rins_solution_queue) { diff --git a/cpp/src/mip/diversity/lns/rins.cuh b/cpp/src/mip/diversity/lns/rins.cuh index a057bdd65..fcc724ebc 100644 --- a/cpp/src/mip/diversity/lns/rins.cuh +++ b/cpp/src/mip/diversity/lns/rins.cuh @@ -56,6 +56,8 @@ class rins_t; template struct rins_thread_t : public cpu_worker_thread_base_t> { + ~rins_thread_t(); + void run_worker(); void on_terminate() {} void on_start() {} diff --git a/cpp/src/mip/feasibility_jump/fj_cpu.cu b/cpp/src/mip/feasibility_jump/fj_cpu.cu index 3c3ac5e9b..ca200083c 100644 --- a/cpp/src/mip/feasibility_jump/fj_cpu.cu +++ b/cpp/src/mip/feasibility_jump/fj_cpu.cu @@ -1054,6 +1054,12 @@ bool fj_t::cpu_solve(fj_cpu_climber_t& fj_cpu, f_t in_time_l return fj_cpu.feasible_found; } +template +cpu_fj_thread_t::~cpu_fj_thread_t() +{ + this->request_termination(); +} + template void cpu_fj_thread_t::run_worker() { diff --git a/cpp/src/mip/feasibility_jump/fj_cpu.cuh b/cpp/src/mip/feasibility_jump/fj_cpu.cuh index cdce44ca7..da50d6ae5 100644 --- a/cpp/src/mip/feasibility_jump/fj_cpu.cuh +++ b/cpp/src/mip/feasibility_jump/fj_cpu.cuh @@ -129,6 +129,8 @@ struct fj_cpu_climber_t { template struct cpu_fj_thread_t : public cpu_worker_thread_base_t> { + ~cpu_fj_thread_t(); + void run_worker(); void on_terminate(); void on_start(); diff --git a/cpp/src/mip/local_search/local_search.cu b/cpp/src/mip/local_search/local_search.cu index d808b00cb..66910db7b 100644 --- a/cpp/src/mip/local_search/local_search.cu +++ b/cpp/src/mip/local_search/local_search.cu @@ -146,9 +146,9 @@ template void local_search_t::stop_cpufj_scratch_threads() { for (auto& cpu_fj : scratch_cpu_fj) { - cpu_fj.kill_cpu_worker(); + cpu_fj.request_termination(); } - scratch_cpu_fj_on_lp_opt.kill_cpu_worker(); + scratch_cpu_fj_on_lp_opt.request_termination(); } template diff --git a/cpp/src/mip/utilities/cpu_worker_thread.cuh b/cpp/src/mip/utilities/cpu_worker_thread.cuh index 5544867d5..a6f133a7b 100644 --- a/cpp/src/mip/utilities/cpu_worker_thread.cuh +++ b/cpp/src/mip/utilities/cpu_worker_thread.cuh @@ -32,8 +32,13 @@ class cpu_worker_thread_base_t { void start_cpu_solver(); bool wait_for_cpu_solver(); - void kill_cpu_worker(); + // Derived classes MUST call this in their destructor before the base destructor runs. + // This ensures on_terminate() is called while the derived object is still fully alive. + void request_termination(); + + // Internal method for thread management - safe to call during destruction + void join_worker(); void cpu_worker_thread(); std::thread cpu_worker; @@ -54,7 +59,8 @@ cpu_worker_thread_base_t::cpu_worker_thread_base_t() template cpu_worker_thread_base_t::~cpu_worker_thread_base_t() { - if (!cpu_thread_terminate) { kill_cpu_worker(); } + // Note: We don't call on_terminate() here since the derived object is already destroyed. + join_worker(); } template @@ -81,15 +87,28 @@ void cpu_worker_thread_base_t::cpu_worker_thread() } template -void cpu_worker_thread_base_t::kill_cpu_worker() +void cpu_worker_thread_base_t::request_termination() { { std::lock_guard lock(cpu_mutex); + if (cpu_thread_terminate) return; cpu_thread_terminate = true; static_cast(this)->on_terminate(); } cpu_cv.notify_one(); - cpu_worker.join(); + join_worker(); +} + +template +void cpu_worker_thread_base_t::join_worker() +{ + if (!cpu_thread_terminate) { + std::lock_guard lock(cpu_mutex); + cpu_thread_terminate = true; + cpu_cv.notify_one(); + } + + if (cpu_worker.joinable()) { cpu_worker.join(); } } template From 0a17d9d0fedbe0c2a587c8cee98a699ab96a2035 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Fri, 31 Oct 2025 10:15:26 +0000 Subject: [PATCH 75/87] fix leftovers and typo --- cpp/src/mip/problem/problem.cu | 2 +- cpp/src/mip/solve.cu | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/src/mip/problem/problem.cu b/cpp/src/mip/problem/problem.cu index 3efaa10d8..79b39912f 100644 --- a/cpp/src/mip/problem/problem.cu +++ b/cpp/src/mip/problem/problem.cu @@ -1190,7 +1190,7 @@ problem_t problem_t::get_problem_after_fixing_vars( raft::common::nvtx::range fun_scope("get_problem_after_fixing_vars"); auto start_time = std::chrono::high_resolution_clock::now(); cuopt_assert(n_variables == assignment.size(), "Assignment size issue"); - problem_t problem(*this); + problem_t problem(*this, true); CUOPT_LOG_DEBUG("Fixing %d variables", variables_to_fix.size()); // we will gather from this and scatter back to the original problem variable_map.resize(assignment.size() - variables_to_fix.size(), handle_ptr->get_stream()); diff --git a/cpp/src/mip/solve.cu b/cpp/src/mip/solve.cu index f6d6ca381..44ab24a6a 100644 --- a/cpp/src/mip/solve.cu +++ b/cpp/src/mip/solve.cu @@ -53,7 +53,7 @@ namespace cuopt::linear_programming { // This serves as both a warm up but also a mandatory initial call to setup cuSparse and cuBLAS static void init_handler(const raft::handle_t* handle_ptr) { - // Init cuBlas / cuSparse context here to avoid having it during solving time 2 + // Init cuBlas / cuSparse context here to avoid having it during solving time RAFT_CUBLAS_TRY(raft::linalg::detail::cublassetpointermode( handle_ptr->get_cublas_handle(), CUBLAS_POINTER_MODE_DEVICE, handle_ptr->get_stream())); RAFT_CUSPARSE_TRY(raft::sparse::detail::cusparsesetpointermode( From fd2248464ffee0afa429d6f36a7dc73dc5da1e29 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Fri, 31 Oct 2025 13:15:37 +0000 Subject: [PATCH 76/87] tentative fix --- cpp/src/mip/utilities/cpu_worker_thread.cuh | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/cpp/src/mip/utilities/cpu_worker_thread.cuh b/cpp/src/mip/utilities/cpu_worker_thread.cuh index a6f133a7b..5d766f859 100644 --- a/cpp/src/mip/utilities/cpu_worker_thread.cuh +++ b/cpp/src/mip/utilities/cpu_worker_thread.cuh @@ -89,24 +89,29 @@ void cpu_worker_thread_base_t::cpu_worker_thread() template void cpu_worker_thread_base_t::request_termination() { + bool should_terminate = false; { std::lock_guard lock(cpu_mutex); - if (cpu_thread_terminate) return; + if (cpu_thread_terminate) return; // Already terminated cpu_thread_terminate = true; + should_terminate = true; static_cast(this)->on_terminate(); + } // Release lock before notify + + if (should_terminate) { + cpu_cv.notify_one(); + join_worker(); } - cpu_cv.notify_one(); - join_worker(); } template void cpu_worker_thread_base_t::join_worker() { - if (!cpu_thread_terminate) { + { std::lock_guard lock(cpu_mutex); - cpu_thread_terminate = true; - cpu_cv.notify_one(); + if (!cpu_thread_terminate) { cpu_thread_terminate = true; } } + cpu_cv.notify_one(); if (cpu_worker.joinable()) { cpu_worker.join(); } } From a683ad4bcdb72012a3f005979f7b6843443aa75f Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Fri, 31 Oct 2025 13:16:14 +0000 Subject: [PATCH 77/87] bump --- cpp/src/mip/utilities/cpu_worker_thread.cuh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/src/mip/utilities/cpu_worker_thread.cuh b/cpp/src/mip/utilities/cpu_worker_thread.cuh index 5d766f859..0f1671c94 100644 --- a/cpp/src/mip/utilities/cpu_worker_thread.cuh +++ b/cpp/src/mip/utilities/cpu_worker_thread.cuh @@ -92,11 +92,11 @@ void cpu_worker_thread_base_t::request_termination() bool should_terminate = false; { std::lock_guard lock(cpu_mutex); - if (cpu_thread_terminate) return; // Already terminated + if (cpu_thread_terminate) return; cpu_thread_terminate = true; should_terminate = true; static_cast(this)->on_terminate(); - } // Release lock before notify + } if (should_terminate) { cpu_cv.notify_one(); From f4862b86a32df93a94507cadcb166073ddce7991 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Tue, 4 Nov 2025 12:22:01 +0000 Subject: [PATCH 78/87] Fix crashed on var flags w/ free vars --- cpp/src/mip/problem/problem.cu | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/cpp/src/mip/problem/problem.cu b/cpp/src/mip/problem/problem.cu index 79b39912f..5aa60152a 100644 --- a/cpp/src/mip/problem/problem.cu +++ b/cpp/src/mip/problem/problem.cu @@ -1285,6 +1285,9 @@ void problem_t::remove_given_variables(problem_t& original_p variable_types.begin()); variable_types.resize(variable_map.size(), handle_ptr->get_stream()); // keep implied-integer and other flags consistent with new variable set + cuopt_assert(original_problem.presolve_data.var_flags.size() == original_problem.n_variables, + "size mismatch"); + cuopt_assert(presolve_data.var_flags.size() == n_variables, "size mismatch"); thrust::gather(handle_ptr->get_thrust_policy(), variable_map.begin(), variable_map.end(), @@ -1417,6 +1420,7 @@ void standardize_bounds(std::vector>>& variable_ auto h_var_bounds = cuopt::host_copy(pb.variable_bounds); auto h_objective_coefficients = cuopt::host_copy(pb.objective_coefficients); auto h_variable_types = cuopt::host_copy(pb.variable_types); + auto h_var_flags = cuopt::host_copy(pb.presolve_data.var_flags); handle_ptr->sync_stream(); const i_t n_vars_originally = (i_t)h_var_bounds.size(); @@ -1448,6 +1452,7 @@ void standardize_bounds(std::vector>>& variable_ pb.presolve_data.variable_offsets.push_back(0.); h_objective_coefficients.push_back(-h_objective_coefficients[i]); h_variable_types.push_back(h_variable_types[i]); + h_var_flags.push_back(0); pb.presolve_data.additional_var_used.push_back(false); pb.presolve_data.additional_var_id_per_var.push_back(-1); pb.n_variables++; @@ -1464,6 +1469,7 @@ void standardize_bounds(std::vector>>& variable_ pb.variable_bounds.resize(h_var_bounds.size(), handle_ptr->get_stream()); pb.objective_coefficients.resize(h_objective_coefficients.size(), handle_ptr->get_stream()); pb.variable_types.resize(h_variable_types.size(), handle_ptr->get_stream()); + pb.presolve_data.var_flags.resize(h_var_flags.size(), handle_ptr->get_stream()); } raft::copy( @@ -1476,6 +1482,10 @@ void standardize_bounds(std::vector>>& variable_ h_variable_types.data(), h_variable_types.size(), handle_ptr->get_stream()); + raft::copy(pb.presolve_data.var_flags.data(), + h_var_flags.data(), + h_var_flags.size(), + handle_ptr->get_stream()); handle_ptr->sync_stream(); } From 9c895593fe1d6b58f26d1f24c943267f9e2fdc6a Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Tue, 4 Nov 2025 12:23:37 +0000 Subject: [PATCH 79/87] bump --- cpp/src/mip/solver_context.cuh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/mip/solver_context.cuh b/cpp/src/mip/solver_context.cuh index f3a3244c1..6201b9e48 100644 --- a/cpp/src/mip/solver_context.cuh +++ b/cpp/src/mip/solver_context.cuh @@ -23,7 +23,7 @@ #pragma once -// forward declare +// Forward declare namespace cuopt::linear_programming::dual_simplex { template class branch_and_bound_t; From 97afc8bd6d24a388234b26bdbe57c4309acf76c6 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Tue, 4 Nov 2025 12:35:22 +0000 Subject: [PATCH 80/87] remove log_prefix from args --- cpp/src/dual_simplex/branch_and_bound.cpp | 13 ++++++------- cpp/src/dual_simplex/branch_and_bound.hpp | 2 +- cpp/src/mip/diversity/lns/rins.cu | 3 ++- cpp/src/mip/solver.cu | 3 +-- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/cpp/src/dual_simplex/branch_and_bound.cpp b/cpp/src/dual_simplex/branch_and_bound.cpp index 52698875c..53c4cd7a9 100644 --- a/cpp/src/dual_simplex/branch_and_bound.cpp +++ b/cpp/src/dual_simplex/branch_and_bound.cpp @@ -1041,15 +1041,14 @@ void branch_and_bound_t::diving_thread(lp_problem_t& leaf_pr } template -mip_status_t branch_and_bound_t::solve(mip_solution_t& solution, - std::string log_prefix) +mip_status_t branch_and_bound_t::solve(mip_solution_t& solution) { logger_t log; - log.log = false; - log.log_prefix = settings_.log.log_prefix = log_prefix; - status_ = mip_exploration_status_t::UNSET; - stats_.nodes_unexplored = 0; - stats_.nodes_explored = 0; + log.log = false; + log.log_prefix = settings_.log.log_prefix; + status_ = mip_exploration_status_t::UNSET; + stats_.nodes_unexplored = 0; + stats_.nodes_explored = 0; if (guess_.size() != 0) { std::vector crushed_guess; diff --git a/cpp/src/dual_simplex/branch_and_bound.hpp b/cpp/src/dual_simplex/branch_and_bound.hpp index c45ca540f..23fb9eb7f 100644 --- a/cpp/src/dual_simplex/branch_and_bound.hpp +++ b/cpp/src/dual_simplex/branch_and_bound.hpp @@ -142,7 +142,7 @@ class branch_and_bound_t { i_t get_heap_size(); // The main entry routine. Returns the solver status and populates solution with the incumbent. - mip_status_t solve(mip_solution_t& solution, std::string log_prefix = ""); + mip_status_t solve(mip_solution_t& solution); private: const user_problem_t& original_problem_; diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index d8c40e6fa..df149c38b 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -247,6 +247,7 @@ void rins_t::run_rins() branch_and_bound_settings.num_threads = 2; branch_and_bound_settings.num_bfs_threads = 1; branch_and_bound_settings.num_diving_threads = 1; + branch_and_bound_settings.log.log_prefix = "[RINS] "; branch_and_bound_settings.solution_callback = [this, &rins_solution_queue]( std::vector& solution, f_t objective) { rins_solution_queue.push_back(solution); @@ -254,7 +255,7 @@ void rins_t::run_rins() dual_simplex::branch_and_bound_t branch_and_bound(branch_and_bound_problem, branch_and_bound_settings); branch_and_bound.set_initial_guess(cuopt::host_copy(fixed_assignment, rins_handle.get_stream())); - branch_and_bound_status = branch_and_bound.solve(branch_and_bound_solution, "[RINS] "); + branch_and_bound_status = branch_and_bound.solve(branch_and_bound_solution); if (!std::isnan(branch_and_bound_solution.objective)) { CUOPT_LOG_DEBUG("RINS submip solution found. Objective %.16e. Status %d", diff --git a/cpp/src/mip/solver.cu b/cpp/src/mip/solver.cu index 8f4e8023a..63909c5f4 100644 --- a/cpp/src/mip/solver.cu +++ b/cpp/src/mip/solver.cu @@ -231,8 +231,7 @@ solution_t mip_solver_t::run_solver() branch_and_bound_status_future = std::async(std::launch::async, &dual_simplex::branch_and_bound_t::solve, branch_and_bound.get(), - std::ref(branch_and_bound_solution), - ""); + std::ref(branch_and_bound_solution)); } // Start the primal heuristics From 9ddca08a070fc8decf0f53077cecd6082df349ad Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Tue, 4 Nov 2025 12:51:06 +0000 Subject: [PATCH 81/87] fix context BnB pointer --- cpp/src/mip/solver.cu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/mip/solver.cu b/cpp/src/mip/solver.cu index 63909c5f4..fc22c552d 100644 --- a/cpp/src/mip/solver.cu +++ b/cpp/src/mip/solver.cu @@ -164,7 +164,6 @@ solution_t mip_solver_t::run_solver() std::unique_ptr> branch_and_bound; branch_and_bound_solution_helper_t solution_helper(&dm, branch_and_bound_settings); dual_simplex::mip_solution_t branch_and_bound_solution(1); - context.branch_and_bound_ptr = branch_and_bound.get(); if (!context.settings.heuristics_only) { // Convert the presolved problem to dual_simplex::user_problem_t @@ -217,6 +216,7 @@ solution_t mip_solver_t::run_solver() // Create the branch and bound object branch_and_bound = std::make_unique>( branch_and_bound_problem, branch_and_bound_settings); + context.branch_and_bound_ptr = branch_and_bound.get(); // Set the primal heuristics -> branch and bound callback context.problem_ptr->branch_and_bound_callback = From 285b4c71c0d1c935f472189925e38fb866e08397 Mon Sep 17 00:00:00 2001 From: Alice Boucher <160623740+aliceb-nv@users.noreply.github.com> Date: Tue, 4 Nov 2025 14:57:32 +0100 Subject: [PATCH 82/87] Update pyproject.toml --- python/libcuopt/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/libcuopt/pyproject.toml b/python/libcuopt/pyproject.toml index fc3f4341c..c4f0b0c47 100644 --- a/python/libcuopt/pyproject.toml +++ b/python/libcuopt/pyproject.toml @@ -66,7 +66,7 @@ libcuopt = "libcuopt" select = [ "distro-too-large-compressed", ] -max_allowed_size_compressed = '595M' +max_allowed_size_compressed = '605M' [project.scripts] cuopt_cli = "libcuopt._cli_wrapper:main" From 5c71aab37534926a9e9b49acd9f48e29461d0cdc Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Tue, 4 Nov 2025 18:35:10 +0000 Subject: [PATCH 83/87] fix crash --- cpp/src/mip/presolve/trivial_presolve.cuh | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/cpp/src/mip/presolve/trivial_presolve.cuh b/cpp/src/mip/presolve/trivial_presolve.cuh index d7fe14233..04b21cfb5 100644 --- a/cpp/src/mip/presolve/trivial_presolve.cuh +++ b/cpp/src/mip/presolve/trivial_presolve.cuh @@ -77,28 +77,35 @@ void cleanup_vectors(problem_t& pb, handle_ptr->get_stream()); handle_ptr->sync_stream(); - auto bnd_iter = thrust::remove_if(handle_ptr->get_thrust_policy(), + auto bnd_iter = thrust::remove_if(handle_ptr->get_thrust_policy(), pb.variable_bounds.begin(), pb.variable_bounds.end(), var_map.begin(), is_zero_t{}); - auto type_iter = thrust::remove_if(handle_ptr->get_thrust_policy(), + auto type_iter = thrust::remove_if(handle_ptr->get_thrust_policy(), pb.variable_types.begin(), pb.variable_types.end(), var_map.begin(), is_zero_t{}); - auto binary_iter = thrust::remove_if(handle_ptr->get_thrust_policy(), + auto binary_iter = thrust::remove_if(handle_ptr->get_thrust_policy(), pb.is_binary_variable.begin(), pb.is_binary_variable.end(), var_map.begin(), is_zero_t{}); - auto obj_iter = thrust::remove_if(handle_ptr->get_thrust_policy(), + auto obj_iter = thrust::remove_if(handle_ptr->get_thrust_policy(), pb.objective_coefficients.begin(), pb.objective_coefficients.end(), var_map.begin(), is_zero_t{}); + auto var_flags_iter = thrust::remove_if(handle_ptr->get_thrust_policy(), + pb.presolve_data.var_flags.begin(), + pb.presolve_data.var_flags.end(), + var_map.begin(), + is_zero_t{}); pb.variable_bounds.resize(bnd_iter - pb.variable_bounds.begin(), handle_ptr->get_stream()); pb.variable_types.resize(type_iter - pb.variable_types.begin(), handle_ptr->get_stream()); + pb.presolve_data.var_flags.resize(var_flags_iter - pb.presolve_data.var_flags.begin(), + handle_ptr->get_stream()); pb.is_binary_variable.resize(binary_iter - pb.is_binary_variable.begin(), handle_ptr->get_stream()); pb.objective_coefficients.resize(obj_iter - pb.objective_coefficients.begin(), From 6e05ca110a0ad5a8495045955de83d05b9b793e3 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Wed, 5 Nov 2025 11:02:09 +0000 Subject: [PATCH 84/87] fix crash due to solution_t allocation during graph capture, added more freauent external solution population update checks --- cpp/src/mip/diversity/diversity_manager.cu | 1 + cpp/src/mip/diversity/lns/rins.cu | 3 --- cpp/src/mip/diversity/population.cu | 10 ++++++++++ cpp/src/mip/diversity/population.cuh | 7 ++++++- cpp/src/mip/local_search/local_search.cu | 17 ++++++++++------- 5 files changed, 27 insertions(+), 11 deletions(-) diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index 50acef3b9..9c6cc0acd 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -255,6 +255,7 @@ bool diversity_manager_t::check_b_b_preemption() population.add_external_solutions_to_population(); return true; } + population.add_external_solutions_to_population(); return false; } diff --git a/cpp/src/mip/diversity/lns/rins.cu b/cpp/src/mip/diversity/lns/rins.cu index df149c38b..1efc971b2 100644 --- a/cpp/src/mip/diversity/lns/rins.cu +++ b/cpp/src/mip/diversity/lns/rins.cu @@ -90,9 +90,6 @@ void rins_t::run_rins() { if (total_calls == 0) RAFT_CUDA_TRY(cudaSetDevice(context.handle_ptr->get_device())); - auto external_solution_size = dm.population.get_external_solution_size(); - if (external_solution_size > 0) dm.population.add_external_solutions_to_population(); - if (!dm.population.is_feasible()) return; cuopt_assert(lp_optimal_solution.size() == problem_ptr->n_variables, "Assignment size mismatch"); diff --git a/cpp/src/mip/diversity/population.cu b/cpp/src/mip/diversity/population.cu index 5a841bc33..c84f5ba29 100644 --- a/cpp/src/mip/diversity/population.cu +++ b/cpp/src/mip/diversity/population.cu @@ -183,11 +183,15 @@ void population_t::add_external_solution(const std::vector& solut problem_ptr->get_user_obj_from_solver_obj(objective)); } if (external_solution_queue.size() >= 5) { early_exit_primal_generation = true; } + solutions_in_external_queue_ = true; } template void population_t::add_external_solutions_to_population() { + // early exit to avoid taking the population lock + if (!solutions_in_external_queue_.load()) { return; } + auto new_sol_vector = get_external_solutions(); add_solutions_from_vec(std::move(new_sol_vector)); } @@ -208,6 +212,7 @@ std::vector> population_t::get_external_solutions std::vector> return_vector; i_t counter = 0; f_t new_best_feasible_objective = best_feasible_objective; + f_t longest_wait_time = 0; for (auto& queue : {external_solution_queue, external_solution_queue_cpufj}) { for (auto& h_entry : queue) { // ignore CPUFJ solutions if they're not better than the best feasible. @@ -220,6 +225,7 @@ std::vector> population_t::get_external_solutions new_best_feasible_objective = h_entry.objective; } + longest_wait_time = max(longest_wait_time, h_entry.timer.elapsed_time()); solution_t sol(*problem_ptr); sol.copy_new_assignment(h_entry.solution); sol.compute_feasibility(); @@ -249,6 +255,10 @@ std::vector> population_t::get_external_solutions external_solution_queue.clear(); } external_solution_queue_cpufj.clear(); + solutions_in_external_queue_ = false; + if (return_vector.size() > 0) { + CUOPT_LOG_DEBUG("Longest wait time in external queue: %f seconds", longest_wait_time); + } return return_vector; } diff --git a/cpp/src/mip/diversity/population.cuh b/cpp/src/mip/diversity/population.cuh index 01332728a..eaa81447f 100644 --- a/cpp/src/mip/diversity/population.cuh +++ b/cpp/src/mip/diversity/population.cuh @@ -194,12 +194,16 @@ class population_t { struct external_solution_t { external_solution_t() = default; external_solution_t(const std::vector& solution, f_t objective, solution_origin_t origin) - : solution(solution), objective(objective), origin(origin) + : solution(solution), + objective(objective), + origin(origin), + timer(std::numeric_limits::infinity()) { } std::vector solution; f_t objective; solution_origin_t origin; + timer_t timer; // debug timer to track how long a solution has lingered in the queue }; std::vector external_solution_queue; @@ -210,6 +214,7 @@ class population_t { std::mutex solution_mutex; std::atomic early_exit_primal_generation = false; std::atomic preempt_heuristic_solver_ = false; + std::atomic solutions_in_external_queue_ = false; f_t best_feasible_objective = std::numeric_limits::max(); assignment_hash_map_t population_hash_map; cuopt::timer_t timer; diff --git a/cpp/src/mip/local_search/local_search.cu b/cpp/src/mip/local_search/local_search.cu index 66910db7b..f867f8e01 100644 --- a/cpp/src/mip/local_search/local_search.cu +++ b/cpp/src/mip/local_search/local_search.cu @@ -463,6 +463,7 @@ bool local_search_t::run_staged_fp(solution_t& solution, fp.reset(); fp.resize_vectors(*solution.problem_ptr, solution.handle_ptr); for (i_t i = 0; i < n_fp_iterations && !timer.check_time_limit(); ++i) { + population_ptr->add_external_solutions_to_population(); if (population_ptr->preempt_heuristic_solver_.load()) { CUOPT_LOG_DEBUG("Preempting heuristic solver!"); return false; @@ -472,6 +473,7 @@ bool local_search_t::run_staged_fp(solution_t& solution, timer_t binary_timer(timer.remaining_time() / 3); i_t binary_it_counter = 0; for (; binary_it_counter < 100; ++binary_it_counter) { + population_ptr->add_external_solutions_to_population(); if (population_ptr->preempt_heuristic_solver_.load()) { CUOPT_LOG_DEBUG("Preempting heuristic solver!"); return false; @@ -581,8 +583,7 @@ void local_search_t::reset_alpha_and_save_solution( solution_copy.problem_ptr = old_problem_ptr; solution_copy.resize_to_problem(); population_ptr->add_solution(std::move(solution_copy)); - auto new_sol_vector = population_ptr->get_external_solutions(); - population_ptr->add_solutions_from_vec(std::move(new_sol_vector)); + population_ptr->add_external_solutions_to_population(); if (!cutting_plane_added_for_active_run) { solution.problem_ptr = &problem_with_objective_cut; solution.resize_to_problem(); @@ -616,8 +617,7 @@ void local_search_t::reset_alpha_and_run_recombiners( raft::common::nvtx::range fun_scope("reset_alpha_and_run_recombiners"); constexpr i_t iterations_for_stagnation = 3; constexpr i_t max_iterations_without_improvement = 8; - auto new_sol_vector = population_ptr->get_external_solutions(); - population_ptr->add_solutions_from_vec(std::move(new_sol_vector)); + population_ptr->add_external_solutions_to_population(); if (population_ptr->current_size() > 1 && i - last_improved_iteration > iterations_for_stagnation) { fp.config.alpha = default_alpha; @@ -671,13 +671,13 @@ bool local_search_t::run_fp(solution_t& solution, break; } CUOPT_LOG_DEBUG("fp_loop it %d last_improved_iteration %d", i, last_improved_iteration); + population_ptr->add_external_solutions_to_population(); if (population_ptr->preempt_heuristic_solver_.load()) { CUOPT_LOG_DEBUG("Preempting heuristic solver!"); break; } - is_feasible = fp.run_single_fp_descent(solution); - auto new_sol_vector = population_ptr->get_external_solutions(); - population_ptr->add_solutions_from_vec(std::move(new_sol_vector)); + is_feasible = fp.run_single_fp_descent(solution); + population_ptr->add_external_solutions_to_population(); CUOPT_LOG_DEBUG("Population size at iteration %d: %d", i, population_ptr->current_size()); if (population_ptr->preempt_heuristic_solver_.load()) { CUOPT_LOG_DEBUG("Preempting heuristic solver!"); @@ -702,6 +702,7 @@ bool local_search_t::run_fp(solution_t& solution, break; } is_feasible = fp.restart_fp(solution); + population_ptr->add_external_solutions_to_population(); if (population_ptr->preempt_heuristic_solver_.load()) { CUOPT_LOG_DEBUG("Preempting heuristic solver!"); break; @@ -756,6 +757,7 @@ bool local_search_t::generate_solution(solution_t& solution, CUOPT_LOG_DEBUG("Solution generated with FJ on LP optimal: is_feasible %d", is_feasible); return true; } + population_ptr->add_external_solutions_to_population(); if (population_ptr->preempt_heuristic_solver_.load()) { CUOPT_LOG_DEBUG("Preempting heuristic solver!"); return is_feasible; @@ -776,6 +778,7 @@ bool local_search_t::generate_solution(solution_t& solution, solution.assignment.size(), solution.handle_ptr->get_stream()); } + population_ptr->add_external_solutions_to_population(); if (population_ptr->preempt_heuristic_solver_.load()) { CUOPT_LOG_DEBUG("Preempting heuristic solver!"); return is_feasible; From e60338b943c759353bd7be5594bbe0b1d08beb53 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Wed, 5 Nov 2025 11:02:57 +0000 Subject: [PATCH 85/87] bump --- cpp/src/mip/local_search/local_search.cu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/mip/local_search/local_search.cu b/cpp/src/mip/local_search/local_search.cu index f867f8e01..826805b80 100644 --- a/cpp/src/mip/local_search/local_search.cu +++ b/cpp/src/mip/local_search/local_search.cu @@ -680,7 +680,7 @@ bool local_search_t::run_fp(solution_t& solution, population_ptr->add_external_solutions_to_population(); CUOPT_LOG_DEBUG("Population size at iteration %d: %d", i, population_ptr->current_size()); if (population_ptr->preempt_heuristic_solver_.load()) { - CUOPT_LOG_DEBUG("Preempting heuristic solver!"); + CUOPT_LOG_DEBUG("Preempting heuristic solver!"); // 1 break; } if (is_feasible) { From 54136ae1936ff2fa5b882059ff0e041ee8c460af Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Wed, 5 Nov 2025 11:03:13 +0000 Subject: [PATCH 86/87] bump 2 --- cpp/src/mip/local_search/local_search.cu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/mip/local_search/local_search.cu b/cpp/src/mip/local_search/local_search.cu index 826805b80..f867f8e01 100644 --- a/cpp/src/mip/local_search/local_search.cu +++ b/cpp/src/mip/local_search/local_search.cu @@ -680,7 +680,7 @@ bool local_search_t::run_fp(solution_t& solution, population_ptr->add_external_solutions_to_population(); CUOPT_LOG_DEBUG("Population size at iteration %d: %d", i, population_ptr->current_size()); if (population_ptr->preempt_heuristic_solver_.load()) { - CUOPT_LOG_DEBUG("Preempting heuristic solver!"); // 1 + CUOPT_LOG_DEBUG("Preempting heuristic solver!"); break; } if (is_feasible) { From 0c76574569c60eabcdb2c50281381cdb393f8d5e Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Wed, 5 Nov 2025 12:43:06 +0000 Subject: [PATCH 87/87] fix crash on early external solution add --- cpp/src/mip/diversity/diversity_manager.cu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index 9c6cc0acd..8239a09c3 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -320,10 +320,10 @@ solution_t diversity_manager_t::run_solver() ls.constraint_prop.bounds_update.calculate_infeasible_redundant_constraints(*problem_ptr), "The problem must not be ii"); population.initialize_population(); + population.allocate_solutions(); if (check_b_b_preemption()) { return population.best_feasible(); } add_user_given_solutions(initial_sol_vector); // Run CPUFJ early to find quick initial solutions - population.allocate_solutions(); ls_cpufj_raii_guard_t ls_cpufj_raii_guard(ls); // RAII to stop cpufj threads on solve stop ls.start_cpufj_scratch_threads(population);