diff --git a/benchmarks/linear_programming/cuopt/run_mip.cpp b/benchmarks/linear_programming/cuopt/run_mip.cpp index 213e38e5e..5e98388c0 100644 --- a/benchmarks/linear_programming/cuopt/run_mip.cpp +++ b/benchmarks/linear_programming/cuopt/run_mip.cpp @@ -383,8 +383,6 @@ int main(int argc, char* argv[]) double memory_limit = program.get("--memory-limit"); bool track_allocations = program.get("--track-allocations")[0] == 't'; - if (num_cpu_threads < 0) { num_cpu_threads = omp_get_max_threads() / n_gpus; } - if (program.is_used("--out-dir")) { out_dir = program.get("--out-dir"); result_file = out_dir + "/final_result.csv"; @@ -421,6 +419,7 @@ int main(int argc, char* argv[]) paths.push_back(entry.path()); } } + if (num_cpu_threads < 0) { num_cpu_threads = omp_get_max_threads() / n_gpus; } // if batch_num is given, trim the paths to only concerned batch if (batch_num != -1) { if (n_batches <= 0) { @@ -487,6 +486,7 @@ int main(int argc, char* argv[]) } merge_result_files(out_dir, result_file, n_gpus, batch_num); } else { + if (num_cpu_threads < 0) { num_cpu_threads = omp_get_max_threads(); } auto memory_resource = make_async(); if (memory_limit > 0) { auto limiting_adaptor = diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index fac00c66a..b1f9d6940 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -75,6 +75,7 @@ if(CMAKE_COMPILER_IS_GNUCXX) list(APPEND CUOPT_CXX_FLAGS -Werror -Wno-error=deprecated-declarations) endif(CMAKE_COMPILER_IS_GNUCXX) + # To use sanitizer with cuda runtime, one must follow a few steps: # 1. Run the binary with env var set: LD_PRELOAD="$(gcc -print-file-name=libasan.so)" ASAN_OPTIONS='protect_shadow_gap=0:replace_intrin=0' # 2. (Optional) To run with a debugger (gdb or cuda-gdb) use the additional ASAN option alloc_dealloc_mismatch=0 @@ -178,7 +179,6 @@ if(DEFINE_ASSERT) add_definitions(-UNDEBUG) endif() - # ################################################################################################## # - find CPM based dependencies ------------------------------------------------------------------ rapids_cpm_init() diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index cfe9876de..46d28e9d8 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -495,8 +495,7 @@ void diversity_manager_t::diversity_step(i_t max_iterations_without_im improved = false; while (k-- > 0) { if (check_b_b_preemption()) { return; } - auto new_sol_vector = population.get_external_solutions(); - recombine_and_ls_with_all(new_sol_vector); + population.add_external_solutions_to_population(); population.adjust_weights_according_to_best_feasible(); cuopt_assert(population.test_invariant(), ""); if (population.current_size() < 2) { @@ -649,7 +648,15 @@ diversity_manager_t::recombine_and_local_search(solution_t& ls_config_t ls_config; ls_config.best_objective_of_parents = best_objective_of_parents; ls_config.at_least_one_parent_feasible = at_least_one_parent_feasible; - success = this->run_local_search(offspring, population.weights, timer, ls_config); + offspring.swap_problem_pointers(); + population.weights_with_cuts.cstr_weights.resize(offspring.problem_ptr->n_constraints, + offspring.handle_ptr->get_stream()); + raft::copy(population.weights_with_cuts.cstr_weights.data(), + population.weights.cstr_weights.data(), + population.weights.cstr_weights.size(), + offspring.handle_ptr->get_stream()); + success = this->run_local_search(offspring, population.weights_with_cuts, timer, ls_config); + offspring.swap_problem_pointers(); if (!success) { // add the attempt mab_recombiner.add_mab_reward(mab_recombiner.last_chosen_option, diff --git a/cpp/src/mip/diversity/population.cu b/cpp/src/mip/diversity/population.cu index 766ed09cb..c49f06638 100644 --- a/cpp/src/mip/diversity/population.cu +++ b/cpp/src/mip/diversity/population.cu @@ -1,6 +1,6 @@ /* clang-format off */ /* - * SPDX-FileCopyrightText: Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ /* clang-format on */ @@ -24,7 +24,6 @@ constexpr double weight_decrease_ratio = 0.9; constexpr double max_infeasibility_weight = 1e12; constexpr double min_infeasibility_weight = 1.; constexpr double infeasibility_balance_ratio = 1.1; -constexpr double halving_skip_ratio = 0.75; template population_t::population_t(std::string const& name_, @@ -41,8 +40,8 @@ population_t::population_t(std::string const& name_, max_solutions(max_solutions_), infeasibility_importance(infeasibility_weight_), weights(0, context.problem_ptr->handle_ptr), + weights_with_cuts(0, context.problem_ptr->handle_ptr), rng(cuopt::seed_generator::get_seed()), - early_exit_primal_generation(false), population_hash_map(*problem_ptr), timer(0) { @@ -64,6 +63,14 @@ i_t get_max_var_threshold(i_t n_vars) return n_vars - 10; } +template +void population_t::apply_problem_ptr_to_all_solutions() +{ + for (size_t i = 0; i < indices.size(); i++) { + solutions[indices[i].first].second.problem_with_cuts_ptr = problem_ptr_with_cuts; + } +} + template void population_t::allocate_solutions() { @@ -73,6 +80,20 @@ void population_t::allocate_solutions() } } +template +void population_t::set_problem_ptr_with_cuts(problem_t* problem_ptr_with_cuts) +{ + constexpr f_t ten = 10.; + this->problem_ptr_with_cuts = problem_ptr_with_cuts; + weights_with_cuts.cstr_weights.resize(problem_ptr_with_cuts->n_constraints, + problem_ptr_with_cuts->handle_ptr->get_stream()); + // fill last element with default + thrust::uninitialized_fill(problem_ptr_with_cuts->handle_ptr->get_thrust_policy(), + weights_with_cuts.cstr_weights.begin() + problem_ptr->n_constraints, + weights_with_cuts.cstr_weights.end(), + ten); +} + template void population_t::initialize_population() { @@ -87,6 +108,12 @@ void population_t::initialize_population() weights.cstr_weights.begin(), weights.cstr_weights.end(), ten); + weights_with_cuts.cstr_weights.resize(problem_ptr->n_constraints, + problem_ptr->handle_ptr->get_stream()); + thrust::uninitialized_fill(problem_ptr->handle_ptr->get_thrust_policy(), + weights_with_cuts.cstr_weights.begin(), + weights_with_cuts.cstr_weights.end(), + ten); } template @@ -109,12 +136,11 @@ std::pair, solution_t> population_t::ge auto second_solution = solutions[indices[j].first].second; // if best feasible and best are the same, take the second index instead of best if (i == 0 && j == 1) { - bool same = - check_integer_equal_on_indices(first_solution.problem_ptr->integer_indices, - first_solution.assignment, - second_solution.assignment, - first_solution.problem_ptr->tolerances.integrality_tolerance, - first_solution.handle_ptr); + bool same = check_integer_equal_on_indices(problem_ptr->integer_indices, + first_solution.assignment, + second_solution.assignment, + problem_ptr->tolerances.integrality_tolerance, + first_solution.handle_ptr); if (same) { auto new_sol = solutions[indices[2].first].second; second_solution = std::move(new_sol); @@ -172,7 +198,6 @@ void population_t::add_external_solution(const std::vector& solut CUOPT_LOG_DEBUG("Found new best solution %g in external queue", 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; } @@ -184,6 +209,7 @@ void population_t::add_external_solutions_to_population() auto new_sol_vector = get_external_solutions(); add_solutions_from_vec(std::move(new_sol_vector)); + apply_problem_ptr_to_all_solutions(); } // normally we would need a lock here but these are boolean types and race conditions are not @@ -192,7 +218,6 @@ template void population_t::preempt_heuristic_solver() { context.preempt_heuristic_solver_ = true; - early_exit_primal_generation = true; } template @@ -668,42 +693,6 @@ std::vector> population_t::population_to_vector() return sol_vec; } -template -void population_t::halve_the_population() -{ - raft::common::nvtx::range fun_scope("halve_the_population"); - // try 3/4 here - if (current_size() <= (max_solutions * halving_skip_ratio)) { return; } - CUOPT_LOG_DEBUG("Halving the population, current size: %lu", current_size()); - // put population into a vector - auto sol_vec = population_to_vector(); - 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); - for (auto& sol : sol_vec) { - add_solution(solution_t(sol)); - } - if (counter++ > max_adjustments) break; - } - counter = 0; - // if we removed too many decrease the diversity a little - while (current_size() < max_solutions / 4) { - clear_except_best_feasible(); - var_threshold = std::min( - max_var_threshold, - std::min((size_t)(var_threshold * 1.02), (size_t)(0.995 * problem_ptr->n_integer_vars))); - for (auto& sol : sol_vec) { - add_solution(solution_t(sol)); - } - if (counter++ > max_adjustments) break; - } -} - template size_t population_t::find_free_solution_index() { diff --git a/cpp/src/mip/diversity/population.cuh b/cpp/src/mip/diversity/population.cuh index 05f22b623..4a51d82a5 100644 --- a/cpp/src/mip/diversity/population.cuh +++ b/cpp/src/mip/diversity/population.cuh @@ -1,6 +1,6 @@ /* clang-format off */ /* - * SPDX-FileCopyrightText: Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ /* clang-format on */ @@ -151,7 +151,6 @@ class population_t { void find_diversity(std::vector>& initial_sol_vector, bool avg); std::vector> population_to_vector(); - void halve_the_population(); void run_solution_callbacks(solution_t& sol); @@ -161,6 +160,9 @@ class population_t { void diversity_step(i_t max_iterations_without_improvement); + void set_problem_ptr_with_cuts(problem_t* problem_ptr_with_cuts); + void apply_problem_ptr_to_all_solutions(); + // does some consistency tests bool test_invariant(); @@ -169,6 +171,7 @@ class population_t { std::string name; mip_solver_context_t& context; problem_t* problem_ptr; + problem_t* problem_ptr_with_cuts; diversity_manager_t& dm; i_t var_threshold; i_t initial_threshold; @@ -178,6 +181,7 @@ class population_t { f_t infeasibility_importance = 100.; size_t max_solutions; weight_t weights; + weight_t weights_with_cuts; std::vector> indices; std::vector>> solutions; @@ -202,7 +206,7 @@ class population_t { i_t update_iter = 0; std::recursive_mutex write_mutex; 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; diff --git a/cpp/src/mip/diversity/recombiners/bound_prop_recombiner.cuh b/cpp/src/mip/diversity/recombiners/bound_prop_recombiner.cuh index 94cc66399..b380f3415 100644 --- a/cpp/src/mip/diversity/recombiners/bound_prop_recombiner.cuh +++ b/cpp/src/mip/diversity/recombiners/bound_prop_recombiner.cuh @@ -140,6 +140,7 @@ class bound_prop_recombiner_t : public recombiner_t { auto& other_solution = a.get_feasible() ? b : a; // copy the solution from guiding solution_t offspring(guiding_solution); + offspring.swap_problem_pointers(); // find same values and populate it to offspring i_t n_different_vars = this->assign_same_integer_values(a, b, offspring); CUOPT_LOG_DEBUG("BP rec: Number of different variables %d MAX_VARS %d", @@ -181,8 +182,9 @@ class bound_prop_recombiner_t : public recombiner_t { rmm::device_uvector old_assignment(offspring.assignment, offspring.handle_ptr->get_stream()); offspring.handle_ptr->sync_stream(); - offspring.assignment = std::move(fixed_assignment); - offspring.problem_ptr = &fixed_problem; + offspring.assignment = std::move(fixed_assignment); + problem_t* orig_problem_ptr = offspring.problem_ptr; + offspring.problem_ptr = &fixed_problem; cuopt_func_call(offspring.test_variable_bounds(false)); get_probing_values_for_feasible(guiding_solution, other_solution, @@ -199,7 +201,7 @@ class bound_prop_recombiner_t : public recombiner_t { constraint_prop.single_rounding_only = false; cuopt_func_call(bool feasible_after_bounds_prop = offspring.get_feasible()); offspring.handle_ptr->sync_stream(); - offspring.problem_ptr = a.problem_ptr; + offspring.problem_ptr = orig_problem_ptr; fixed_assignment = std::move(offspring.assignment); offspring.assignment = std::move(old_assignment); offspring.handle_ptr->sync_stream(); @@ -219,6 +221,7 @@ class bound_prop_recombiner_t : public recombiner_t { } constraint_prop.max_n_failed_repair_iterations = 1; cuopt_func_call(offspring.test_number_all_integer()); + offspring.swap_problem_pointers(); bool better_cost_than_parents = offspring.get_quality(weights) < std::min(other_solution.get_quality(weights), guiding_solution.get_quality(weights)); diff --git a/cpp/src/mip/diversity/recombiners/fp_recombiner.cuh b/cpp/src/mip/diversity/recombiners/fp_recombiner.cuh index 1daaf3e51..07e00499b 100644 --- a/cpp/src/mip/diversity/recombiners/fp_recombiner.cuh +++ b/cpp/src/mip/diversity/recombiners/fp_recombiner.cuh @@ -1,6 +1,6 @@ /* clang-format off */ /* - * SPDX-FileCopyrightText: Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ /* clang-format on */ @@ -44,6 +44,7 @@ class fp_recombiner_t : public recombiner_t { auto& other_solution = a.get_feasible() ? b : a; // copy the solution from A solution_t offspring(guiding_solution); + offspring.swap_problem_pointers(); // find same values and populate it to offspring i_t n_different_vars = this->assign_same_integer_values(guiding_solution, other_solution, offspring); @@ -126,6 +127,7 @@ class fp_recombiner_t : public recombiner_t { fp_recombiner_config_t::decrease_max_n_of_vars_from_other(); } } + offspring.swap_problem_pointers(); bool better_cost_than_parents = offspring.get_quality(weights) < std::min(other_solution.get_quality(weights), guiding_solution.get_quality(weights)); diff --git a/cpp/src/mip/diversity/recombiners/line_segment_recombiner.cuh b/cpp/src/mip/diversity/recombiners/line_segment_recombiner.cuh index b39ee8542..2f004d1f2 100644 --- a/cpp/src/mip/diversity/recombiners/line_segment_recombiner.cuh +++ b/cpp/src/mip/diversity/recombiners/line_segment_recombiner.cuh @@ -1,6 +1,6 @@ /* clang-format off */ /* - * SPDX-FileCopyrightText: Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ /* clang-format on */ @@ -75,6 +75,7 @@ class line_segment_recombiner_t : public recombiner_t { auto& other_solution = a.get_feasible() ? b : a; // copy the solution from A solution_t offspring(guiding_solution); + offspring.swap_problem_pointers(); timer_t line_segment_timer{ls_recombiner_config_t::time_limit}; // TODO after we have the conic combination, detect the lambda change // (i.e. the integral variables flip on line segment) @@ -99,6 +100,7 @@ class line_segment_recombiner_t : public recombiner_t { is_feasibility_run, line_segment_timer); line_segment_search.settings = {}; + offspring.swap_problem_pointers(); bool better_cost_than_parents = offspring.get_quality(weights) < std::min(other_solution.get_quality(weights), guiding_solution.get_quality(weights)); diff --git a/cpp/src/mip/diversity/recombiners/sub_mip.cuh b/cpp/src/mip/diversity/recombiners/sub_mip.cuh index 5be807372..5452191b8 100644 --- a/cpp/src/mip/diversity/recombiners/sub_mip.cuh +++ b/cpp/src/mip/diversity/recombiners/sub_mip.cuh @@ -1,6 +1,6 @@ /* clang-format off */ /* - * SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ /* clang-format on */ @@ -46,6 +46,7 @@ class sub_mip_recombiner_t : public recombiner_t { auto& other_solution = a.get_feasible() ? b : a; // copy the solution from A solution_t offspring(guiding_solution); + offspring.swap_problem_pointers(); // find same values and populate it to offspring i_t n_different_vars = this->assign_same_integer_values(guiding_solution, other_solution, offspring); @@ -148,7 +149,6 @@ class sub_mip_recombiner_t : public recombiner_t { } cuopt_func_call(offspring.test_variable_bounds()); cuopt_assert(offspring.test_number_all_integer(), "All must be integers after offspring"); - offspring.compute_feasibility(); // bool same_as_parents = this->check_if_offspring_is_same_as_parents(offspring, a, b); // adjust the max_n_of_vars_from_other if (n_different_vars > (i_t)sub_mip_recombiner_config_t::max_n_of_vars_from_other) { @@ -175,9 +175,15 @@ class sub_mip_recombiner_t : public recombiner_t { sol.unfix_variables(fixed_assignment, variable_map); sol.clamp_within_bounds(); // Scaling might bring some very slight variable bound violations sol.compute_feasibility(); + // the current problem is the proble with objective cut + // to add to the population, swap problem to original + cuopt_assert(sol.compute_feasibility(), "Solution must be feasible"); + sol.swap_problem_pointers(); + cuopt_assert(sol.get_feasible(), "Solution must be feasible"); cuopt_func_call(sol.test_variable_bounds()); population.add_solution(std::move(sol)); } + offspring.swap_problem_pointers(); bool better_cost_than_parents = offspring.get_quality(weights) < std::min(other_solution.get_quality(weights), guiding_solution.get_quality(weights)); diff --git a/cpp/src/mip/local_search/local_search.cu b/cpp/src/mip/local_search/local_search.cu index ecd277065..24a7107ff 100644 --- a/cpp/src/mip/local_search/local_search.cu +++ b/cpp/src/mip/local_search/local_search.cu @@ -32,15 +32,9 @@ local_search_t::local_search_t(mip_solver_context_t& context fj_sol_on_lp_opt(context.problem_ptr->n_variables, context.problem_ptr->handle_ptr->get_stream()), fj(context), - // fj_tree(fj), constraint_prop(context), line_segment_search(fj, constraint_prop), - fp(context, - fj, - // fj_tree, - constraint_prop, - line_segment_search, - lp_optimal_solution_), + fp(context, fj, constraint_prop, line_segment_search, lp_optimal_solution_), rng(cuopt::seed_generator::get_seed()), problem_with_objective_cut(*context.problem_ptr, context.problem_ptr->handle_ptr) { @@ -151,7 +145,6 @@ bool local_search_t::do_fj_solve(solution_t& solution, if (time_limit == 0.) return solution.get_feasible(); timer_t timer(time_limit); - 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) { @@ -164,8 +157,6 @@ bool local_search_t::do_fj_solve(solution_t& solution, true); } - auto solution_copy = solution; - // Start CPU solver in background thread for (auto& cpu_fj : ls_cpu_fj) { cpu_fj.start_cpu_solver(); @@ -184,7 +175,7 @@ bool local_search_t::do_fj_solve(solution_t& solution, auto gpu_fj_end = std::chrono::high_resolution_clock::now(); double gpu_fj_duration = std::chrono::duration(gpu_fj_end - gpu_fj_start).count(); - solution_t solution_cpu(*solution.problem_ptr); + solution_t solution_cpu(solution); f_t best_cpu_obj = std::numeric_limits::max(); // // Wait for CPU solver to finish @@ -269,18 +260,8 @@ bool local_search_t::run_local_search(solution_t& solution, raft::common::nvtx::range fun_scope("local search"); fj_settings_t fj_settings; if (timer.check_time_limit()) return false; - // adjust these time limits - if (!solution.get_feasible()) { - if (ls_config.at_least_one_parent_feasible) { - fj_settings.time_limit = 0.5; - timer = timer_t(fj_settings.time_limit); - } else { - fj_settings.time_limit = 0.25; - timer = timer_t(fj_settings.time_limit); - } - } else { - fj_settings.time_limit = std::min(1., timer.remaining_time()); - } + fj_settings.time_limit = std::min(1., timer.remaining_time()); + timer = timer_t(fj_settings.time_limit); fj_settings.update_weights = false; fj_settings.feasibility_run = false; fj.set_fj_settings(fj_settings); @@ -528,15 +509,15 @@ void local_search_t::resize_vectors(problem_t& problem, template void local_search_t::save_solution_and_add_cutting_plane( - solution_t& solution, rmm::device_uvector& best_solution, f_t& best_objective) + solution_t& best_in_population, solution_t& solution, f_t& best_objective) { raft::common::nvtx::range fun_scope("save_solution_and_add_cutting_plane"); - if (solution.get_objective() < best_objective) { - raft::copy(best_solution.data(), - solution.assignment.data(), + if (best_in_population.get_objective() < best_objective) { + raft::copy(solution.assignment.data(), + best_in_population.assignment.data(), solution.assignment.size(), solution.handle_ptr->get_stream()); - best_objective = solution.get_objective(); + best_objective = best_in_population.get_objective(); f_t objective_cut = best_objective - std::max(std::abs(0.001 * best_objective), OBJECTIVE_EPSILON); problem_with_objective_cut.add_cutting_plane_at_objective(objective_cut); @@ -547,9 +528,6 @@ template void local_search_t::resize_to_new_problem() { resize_vectors(problem_with_objective_cut, problem_with_objective_cut.handle_ptr); - // hint for next PR in case load balanced is reintroduced - // lb_constraint_prop.temp_problem.setup(problem_with_objective_cut); - // lb_constraint_prop.bounds_update.setup(lb_constraint_prop.temp_problem); constraint_prop.bounds_update.resize(problem_with_objective_cut); } @@ -557,75 +535,88 @@ template void local_search_t::resize_to_old_problem(problem_t* old_problem_ptr) { resize_vectors(*old_problem_ptr, old_problem_ptr->handle_ptr); - // hint for next PR in case load balanced is reintroduced - // lb_constraint_prop.temp_problem.setup(*old_problem_ptr); - // lb_constraint_prop.bounds_update.setup(lb_constraint_prop.temp_problem); constraint_prop.bounds_update.resize(*old_problem_ptr); } template -void local_search_t::reset_alpha_and_save_solution( - solution_t& solution, - problem_t* old_problem_ptr, - population_t* population_ptr, - i_t i, - i_t last_improved_iteration, - rmm::device_uvector& best_solution, - f_t& best_objective) +void local_search_t::handle_cutting_plane_and_weights( + solution_t& solution, population_t* population_ptr, f_t& best_objective) { - raft::common::nvtx::range fun_scope("reset_alpha_and_save_solution"); - fp.config.alpha = default_alpha; - solution_t solution_copy(solution); - solution_copy.problem_ptr = old_problem_ptr; - solution_copy.resize_to_problem(); - population_ptr->add_solution(std::move(solution_copy)); - 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(); + population_ptr->set_problem_ptr_with_cuts(&problem_with_objective_cut); + constexpr bool is_cuts_problem = true; + solution.set_problem_ptr(&problem_with_objective_cut, is_cuts_problem); resize_to_new_problem(); cutting_plane_added_for_active_run = true; raft::copy(population_ptr->weights.cstr_weights.data(), fj.cstr_weights.data(), population_ptr->weights.cstr_weights.size(), solution.handle_ptr->get_stream()); + raft::copy(population_ptr->weights_with_cuts.cstr_weights.data(), + fj.cstr_weights.data(), + fj.cstr_weights.size(), + solution.handle_ptr->get_stream()); } population_ptr->update_weights(); - save_solution_and_add_cutting_plane( - population_ptr->best_feasible(), best_solution, best_objective); - raft::copy(solution.assignment.data(), - best_solution.data(), - solution.assignment.size(), - solution.handle_ptr->get_stream()); + save_solution_and_add_cutting_plane(population_ptr->best_feasible(), solution, best_objective); + population_ptr->apply_problem_ptr_to_all_solutions(); +} + +template +void local_search_t::reset_alpha_and_save_solution(solution_t& solution, + problem_t* old_problem_ptr, + population_t* population_ptr, + i_t i, + i_t last_improved_iteration, + f_t& best_objective) +{ + raft::common::nvtx::range fun_scope("reset_alpha_and_save_solution"); + fp.reset(); + solution_t solution_copy(solution); + solution_copy.set_problem_ptr(old_problem_ptr); + population_ptr->add_solution(std::move(solution_copy)); + population_ptr->add_external_solutions_to_population(); + handle_cutting_plane_and_weights(solution, population_ptr, best_objective); population_ptr->print(); } template -void local_search_t::reset_alpha_and_run_recombiners( - solution_t& solution, - problem_t* old_problem_ptr, - population_t* population_ptr, - i_t i, - i_t last_improved_iteration, - rmm::device_uvector& best_solution, - f_t& best_objective) +void local_search_t::run_recombiners(solution_t& solution, + problem_t* old_problem_ptr, + population_t* population_ptr, + i_t i, + i_t last_improved_iteration, + f_t& best_objective) { 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; population_ptr->add_external_solutions_to_population(); + if (population_ptr->is_feasible()) { + if (!cutting_plane_added_for_active_run) { + solution.set_problem_ptr(&problem_with_objective_cut); + population_ptr->set_problem_ptr_with_cuts(&problem_with_objective_cut); + resize_to_new_problem(); + cutting_plane_added_for_active_run = true; + raft::copy(population_ptr->weights.cstr_weights.data(), + fj.cstr_weights.data(), + population_ptr->weights.cstr_weights.size(), + solution.handle_ptr->get_stream()); + raft::copy(population_ptr->weights_with_cuts.cstr_weights.data(), + fj.cstr_weights.data(), + fj.cstr_weights.size(), + solution.handle_ptr->get_stream()); + } + population_ptr->update_weights(); + save_solution_and_add_cutting_plane(population_ptr->best_feasible(), solution, best_objective); + } if (population_ptr->current_size() > 1 && i - last_improved_iteration > iterations_for_stagnation) { - fp.config.alpha = default_alpha; + population_ptr->apply_problem_ptr_to_all_solutions(); population_ptr->diversity_step(max_iterations_without_improvement); population_ptr->print(); population_ptr->update_weights(); - save_solution_and_add_cutting_plane( - population_ptr->best_feasible(), best_solution, best_objective); - raft::copy(solution.assignment.data(), - best_solution.data(), - solution.assignment.size(), - solution.handle_ptr->get_stream()); + save_solution_and_add_cutting_plane(population_ptr->best_feasible(), solution, best_objective); } } @@ -641,7 +632,6 @@ bool local_search_t::run_fp(solution_t& solution, cutting_plane_added_for_active_run = is_feasible; double best_objective = is_feasible ? solution.get_objective() : std::numeric_limits::max(); - rmm::device_uvector best_solution(solution.assignment, solution.handle_ptr->get_stream()); problem_t* old_problem_ptr = solution.problem_ptr; fp.timer = timer_t(timer.remaining_time()); // if it has not been initialized yet, create a new problem and move it to the cut problem @@ -656,8 +646,9 @@ bool local_search_t::run_fp(solution_t& solution, // Do the copy here for proper handling of the added constraints weight fj.copy_weights( population_ptr->weights, solution.handle_ptr, problem_with_objective_cut.n_constraints); - solution.problem_ptr = &problem_with_objective_cut; - solution.resize_to_problem(); + solution.set_problem_ptr(&problem_with_objective_cut); + population_ptr->set_problem_ptr_with_cuts(&problem_with_objective_cut); + population_ptr->apply_problem_ptr_to_all_solutions(); resize_to_new_problem(); } i_t last_improved_iteration = 0; @@ -682,13 +673,8 @@ bool local_search_t::run_fp(solution_t& solution, if (is_feasible) { CUOPT_LOG_DEBUG("Found feasible in FP with obj %f. Continue with FJ!", solution.get_objective()); - reset_alpha_and_save_solution(solution, - old_problem_ptr, - population_ptr, - i, - last_improved_iteration, - best_solution, - best_objective); + reset_alpha_and_save_solution( + solution, old_problem_ptr, population_ptr, i, last_improved_iteration, best_objective); last_improved_iteration = i; } // if not feasible, it means it is a cycle @@ -706,31 +692,16 @@ bool local_search_t::run_fp(solution_t& solution, if (is_feasible) { CUOPT_LOG_DEBUG("Found feasible during restart with obj %f. Continue with FJ!", solution.get_objective()); - reset_alpha_and_save_solution(solution, - old_problem_ptr, - population_ptr, - i, - last_improved_iteration, - best_solution, - best_objective); + reset_alpha_and_save_solution( + solution, old_problem_ptr, population_ptr, i, last_improved_iteration, best_objective); last_improved_iteration = i; } else { - reset_alpha_and_run_recombiners(solution, - old_problem_ptr, - population_ptr, - i, - last_improved_iteration, - best_solution, - best_objective); + run_recombiners( + solution, old_problem_ptr, population_ptr, i, last_improved_iteration, best_objective); } } } - raft::copy(solution.assignment.data(), - best_solution.data(), - solution.assignment.size(), - solution.handle_ptr->get_stream()); - solution.problem_ptr = old_problem_ptr; - solution.resize_to_problem(); + solution.set_problem_ptr(old_problem_ptr); resize_to_old_problem(old_problem_ptr); solution.handle_ptr->sync_stream(); return is_feasible; diff --git a/cpp/src/mip/local_search/local_search.cuh b/cpp/src/mip/local_search/local_search.cuh index 6fdf4ac72..0224fa0b1 100644 --- a/cpp/src/mip/local_search/local_search.cuh +++ b/cpp/src/mip/local_search/local_search.cuh @@ -1,6 +1,6 @@ /* clang-format off */ /* - * SPDX-FileCopyrightText: Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ /* clang-format on */ @@ -87,26 +87,28 @@ class local_search_t { const std::string& source); i_t ls_threads() const { return ls_cpu_fj.size() + scratch_cpu_fj.size(); } - void save_solution_and_add_cutting_plane(solution_t& solution, - rmm::device_uvector& best_solution, + void save_solution_and_add_cutting_plane(solution_t& best_in_population, + solution_t& solution, f_t& best_objective); void resize_to_new_problem(); void resize_to_old_problem(problem_t* old_problem_ptr); - void reset_alpha_and_run_recombiners(solution_t& solution, - problem_t* old_problem_ptr, - population_t* population_ptr, - i_t i, - i_t last_unimproved_iteration, - rmm::device_uvector& best_solution, - f_t& best_objective); + void run_recombiners(solution_t& solution, + problem_t* old_problem_ptr, + population_t* population_ptr, + i_t i, + i_t last_unimproved_iteration, + f_t& best_objective); void reset_alpha_and_save_solution(solution_t& solution, problem_t* old_problem_ptr, population_t* population_ptr, i_t i, i_t last_unimproved_iteration, - rmm::device_uvector& best_solution, f_t& best_objective); + void handle_cutting_plane_and_weights(solution_t& solution, + population_t* population_ptr, + f_t& best_objective); + mip_solver_context_t& context; rmm::device_uvector& lp_optimal_solution; bool lp_optimal_exists{false}; diff --git a/cpp/src/mip/solution/solution.cu b/cpp/src/mip/solution/solution.cu index 9e9a2d75f..537eb47d5 100644 --- a/cpp/src/mip/solution/solution.cu +++ b/cpp/src/mip/solution/solution.cu @@ -42,6 +42,8 @@ rmm::device_uvector get_lower_bounds( template solution_t::solution_t(problem_t& problem_) : problem_ptr(&problem_), + problem_with_cuts_ptr(&problem_), + current_problem_is_cut(false), handle_ptr(problem_.handle_ptr), assignment(std::move(get_lower_bounds(problem_.variable_bounds, handle_ptr))), lower_excess(problem_.n_constraints, handle_ptr->get_stream()), @@ -59,6 +61,8 @@ solution_t::solution_t(problem_t& problem_) template solution_t::solution_t(const solution_t& other) : problem_ptr(other.problem_ptr), + problem_with_cuts_ptr(other.problem_with_cuts_ptr), + current_problem_is_cut(other.current_problem_is_cut), handle_ptr(other.handle_ptr), assignment(other.assignment, handle_ptr->get_stream()), lower_excess(other.lower_excess, handle_ptr->get_stream()), @@ -85,11 +89,13 @@ template void solution_t::copy_from(const solution_t& other_sol) { // TODO handle resize - problem_ptr = other_sol.problem_ptr; - handle_ptr = other_sol.handle_ptr; - h_obj = other_sol.h_obj; - h_user_obj = other_sol.h_user_obj; - h_infeasibility_cost = other_sol.h_infeasibility_cost; + problem_ptr = other_sol.problem_ptr; + problem_with_cuts_ptr = other_sol.problem_with_cuts_ptr; + current_problem_is_cut = other_sol.current_problem_is_cut; + handle_ptr = other_sol.handle_ptr; + h_obj = other_sol.h_obj; + h_user_obj = other_sol.h_user_obj; + h_infeasibility_cost = other_sol.h_infeasibility_cost; expand_device_copy(assignment, other_sol.assignment, handle_ptr->get_stream()); expand_device_copy(lower_excess, other_sol.lower_excess, handle_ptr->get_stream()); expand_device_copy(upper_excess, other_sol.upper_excess, handle_ptr->get_stream()); @@ -123,6 +129,39 @@ void solution_t::resize_to_problem() lp_state.prev_dual.resize(problem_ptr->n_constraints, handle_ptr->get_stream()); } +template +void solution_t::swap_problem_pointers() +{ + current_problem_is_cut = !current_problem_is_cut; + cuopt_assert(problem_with_cuts_ptr != nullptr, "Problem with cuts pointer must be set"); + cuopt_assert(problem_with_cuts_ptr != problem_ptr, "Problem pointers must be different"); + std::swap(problem_ptr, problem_with_cuts_ptr); + resize_to_problem(); + compute_feasibility(); +} + +template +void solution_t::set_problem_ptr(problem_t* _problem_ptr, bool is_cuts_problem) +{ + cuopt_assert(_problem_ptr != nullptr, "Problem pointer must be set"); + // FIXME: when we create ping_pong problems with constant cuts, adjust this logic + if (is_cuts_problem) { + cuopt_assert(!current_problem_is_cut, "Current problem must not be with cuts"); + // set original problem to be swap pointer + problem_with_cuts_ptr = problem_ptr; + // set current problem to new cut problem + problem_ptr = _problem_ptr; + current_problem_is_cut = true; + } else { + problem_ptr = _problem_ptr; + // if we are setting non-cut problem, the cut version should be invalidated + problem_with_cuts_ptr = nullptr; + current_problem_is_cut = false; + } + resize_to_problem(); + compute_feasibility(); +} + template void solution_t::resize_to_original_problem() { diff --git a/cpp/src/mip/solution/solution.cuh b/cpp/src/mip/solution/solution.cuh index 342827b1a..7483f2d12 100644 --- a/cpp/src/mip/solution/solution.cuh +++ b/cpp/src/mip/solution/solution.cuh @@ -1,6 +1,6 @@ /* clang-format off */ /* - * SPDX-FileCopyrightText: Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ /* clang-format on */ @@ -99,6 +99,8 @@ class solution_t { f_t compute_max_constraint_violation(); f_t compute_max_int_violation(); f_t compute_max_variable_violation(); + void swap_problem_pointers(); + void set_problem_ptr(problem_t* _problem_ptr, bool is_cuts_problem = false); struct view_t { // let's not bloat the class for every simple getter and setters @@ -124,6 +126,8 @@ class solution_t { // we might need to change it later as we tighten the bounds // and run lp on fixed parts problem_t* problem_ptr; + problem_t* problem_with_cuts_ptr; + bool current_problem_is_cut = false; const raft::handle_t* handle_ptr; rmm::device_uvector assignment; rmm::device_uvector lower_excess;