diff --git a/cpp/src/branch_and_bound/branch_and_bound.cpp b/cpp/src/branch_and_bound/branch_and_bound.cpp index e69ff7b9a5..c2d16dac23 100644 --- a/cpp/src/branch_and_bound/branch_and_bound.cpp +++ b/cpp/src/branch_and_bound/branch_and_bound.cpp @@ -1436,8 +1436,9 @@ void branch_and_bound_t::plunge_with(branch_and_bound_worker_t*> stack; stack.push_front(worker->start_node); - worker->recompute_basis = true; - worker->recompute_bounds = true; + bool requeue_pending_nodes = false; + worker->recompute_basis = true; + worker->recompute_bounds = true; f_t lower_bound = get_lower_bound(); f_t upper_bound = upper_bound_; @@ -1483,8 +1484,11 @@ void branch_and_bound_t::plunge_with(branch_and_bound_worker_t::plunge_with(branch_and_bound_worker_t 0 && - (rel_gap <= settings_.relative_mip_gap_tol || abs_gap <= settings_.absolute_mip_gap_tol)) { - // If the solver converged according to the gap rules, but we still have nodes to explore - // in the stack, then we should add all the pending nodes back to the heap so the lower - // bound of the solver is set to the correct value. + if (stack.size() > 0 && (requeue_pending_nodes || rel_gap <= settings_.relative_mip_gap_tol || + abs_gap <= settings_.absolute_mip_gap_tol)) { + // If the solver exits early without consuming the local stack, or converged according to + // the gap rules while nodes are still pending, put those nodes back into the global queue + // before returning. while (!stack.empty()) { auto node = stack.front(); stack.pop_front(); diff --git a/cpp/src/mip_heuristics/diversity/lns/rins.cu b/cpp/src/mip_heuristics/diversity/lns/rins.cu index c4331343de..7b1db8452c 100644 --- a/cpp/src/mip_heuristics/diversity/lns/rins.cu +++ b/cpp/src/mip_heuristics/diversity/lns/rins.cu @@ -26,6 +26,12 @@ #include namespace cuopt::linear_programming::detail { +template +rins_t::~rins_t() +{ + stop_rins(); +} + template rins_t::rins_t(mip_solver_context_t& context_, diversity_manager_t& dm_, @@ -58,31 +64,35 @@ 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; + if (!enabled.load()) return; node_count++; 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); - bool population_ready = false; - if (rins_thread->cpu_thread_done) { - std::lock_guard pop_lock(dm.population.write_mutex); - population_ready = dm.population.current_size() > 0 && dm.population.is_feasible(); - } - if (population_ready) { - lp_optimal_solution = solution; - rins_thread->start_cpu_solver(); - } + if (node_count - node_count_at_last_rins <= settings.node_freq) { return; } + + std::lock_guard lock(rins_mutex); + if (!enabled.load() || !rins_thread) { return; } + if (!rins_thread->cpu_thread_done.load()) { return; } + + { + std::lock_guard pop_lock(dm.population.write_mutex); + if (dm.population.current_size() == 0 || !dm.population.is_feasible()) { return; } + + auto& best_feasible_ref = dm.population.best_feasible(); + if (!best_feasible_ref.get_feasible()) { return; } + + incumbent_solution_snapshot = best_feasible_ref.get_host_assignment(); + lp_optimal_solution = solution; } + rins_thread->start_cpu_solver(); } template void rins_t::enable() { + std::lock_guard lock(rins_mutex); rins_thread = std::make_unique>(); rins_thread->rins_ptr = this; seed = cuopt::seed_generator::get_seed(); @@ -94,9 +104,13 @@ void rins_t::enable() template void rins_t::stop_rins() { - enabled = false; - if (rins_thread) rins_thread->request_termination(); - rins_thread.reset(); + std::unique_ptr> local_thread; + { + std::lock_guard lock(rins_mutex); + enabled = false; + local_thread = std::move(rins_thread); + } + if (local_thread) { local_thread->request_termination(); } } template @@ -104,7 +118,6 @@ void rins_t::run_rins() { if (total_calls == 0) RAFT_CUDA_TRY(cudaSetDevice(context.handle_ptr->get_device())); - cuopt_assert(lp_optimal_solution.size() == problem_copy->n_variables, "Assignment size mismatch"); cuopt_assert(problem_copy->handle_ptr == &rins_handle, "Handle mismatch"); // Do not make assertions based on problem_ptr. The original problem may have been modified within // the FP loop relaxing integers cuopt_assert(problem_copy->n_variables == @@ -117,20 +130,11 @@ void rins_t::run_rins() solution_t best_sol(*problem_copy); rins_handle.sync_stream(); - // copy the best from the population into a solution_t in the RINS stream - { - std::lock_guard lock(dm.population.write_mutex); - if (!dm.population.is_feasible()) return; - cuopt_assert(dm.population.current_size() > 0, "No solutions in population"); - auto& best_feasible_ref = dm.population.best_feasible(); - cuopt_assert(best_feasible_ref.assignment.size() == best_sol.assignment.size(), - "Assignment size mismatch"); - 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(); - } + // Use the launch-time snapshot to keep the incumbent and problem model consistent. + best_sol.copy_new_assignment(incumbent_solution_snapshot); + 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"); diff --git a/cpp/src/mip_heuristics/diversity/lns/rins.cuh b/cpp/src/mip_heuristics/diversity/lns/rins.cuh index 0a9133f848..3071b82ac2 100644 --- a/cpp/src/mip_heuristics/diversity/lns/rins.cuh +++ b/cpp/src/mip_heuristics/diversity/lns/rins.cuh @@ -67,6 +67,7 @@ struct rins_thread_t : public cpu_worker_thread_base_t> template class rins_t { public: + ~rins_t(); rins_t(mip_solver_context_t& context, diversity_manager_t& dm, rins_settings_t settings = rins_settings_t()); @@ -89,6 +90,7 @@ class rins_t { raft::handle_t rins_handle; std::vector lp_optimal_solution; + std::vector incumbent_solution_snapshot; f_t fixrate{0.5}; i_t total_calls{0};