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..c2483310a 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -178,7 +178,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..07ef9bed6 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) { diff --git a/cpp/src/mip/diversity/population.cu b/cpp/src/mip/diversity/population.cu index 766ed09cb..b640fd5a4 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_, @@ -42,7 +41,6 @@ population_t::population_t(std::string const& name_, infeasibility_importance(infeasibility_weight_), weights(0, context.problem_ptr->handle_ptr), rng(cuopt::seed_generator::get_seed()), - early_exit_primal_generation(false), population_hash_map(*problem_ptr), timer(0) { @@ -109,12 +107,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 +169,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; } @@ -192,7 +188,6 @@ template void population_t::preempt_heuristic_solver() { context.preempt_heuristic_solver_ = true; - early_exit_primal_generation = true; } template @@ -668,42 +663,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..b47b2ef46 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); @@ -202,7 +201,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/sub_mip.cuh b/cpp/src/mip/diversity/recombiners/sub_mip.cuh index 5be807372..48cb84af2 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 */ @@ -148,7 +148,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,6 +174,10 @@ 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"); + cuopt_assert(sol.get_feasible(), "Solution must be feasible"); cuopt_func_call(sol.test_variable_bounds()); population.add_solution(std::move(sol)); } diff --git a/cpp/src/mip/local_search/local_search.cu b/cpp/src/mip/local_search/local_search.cu index ecd277065..8697f8eb2 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 @@ -528,15 +519,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 +538,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,32 +545,15 @@ 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(); + solution.set_problem_ptr(&problem_with_objective_cut); resize_to_new_problem(); cutting_plane_added_for_active_run = true; raft::copy(population_ptr->weights.cstr_weights.data(), @@ -591,41 +562,58 @@ void local_search_t::reset_alpha_and_save_solution( 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); +} + +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); + 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()); + } + 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->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 +629,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 +643,7 @@ 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); resize_to_new_problem(); } i_t last_improved_iteration = 0; @@ -682,13 +668,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 +687,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..fdc35fbf3 100644 --- a/cpp/src/mip/solution/solution.cu +++ b/cpp/src/mip/solution/solution.cu @@ -123,6 +123,15 @@ void solution_t::resize_to_problem() lp_state.prev_dual.resize(problem_ptr->n_constraints, handle_ptr->get_stream()); } +template +void solution_t::set_problem_ptr(problem_t* _problem_ptr) +{ + cuopt_assert(_problem_ptr != nullptr, "Problem pointer must be set"); + problem_ptr = _problem_ptr; + 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..5b8bca30f 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); struct view_t { // let's not bloat the class for every simple getter and setters