diff --git a/benchmarks/linear_programming/cuopt/run_mip.cpp b/benchmarks/linear_programming/cuopt/run_mip.cpp index f3124136f3..b20ae26ea6 100644 --- a/benchmarks/linear_programming/cuopt/run_mip.cpp +++ b/benchmarks/linear_programming/cuopt/run_mip.cpp @@ -205,6 +205,8 @@ int run_single_file(std::string file_path, settings.heuristics_only = heuristics_only; settings.num_cpu_threads = num_cpu_threads; settings.log_to_console = log_to_console; + // settings.tolerances.relative_tolerance = 1e-10; + // settings.tolerances.absolute_tolerance = 1e-6; cuopt::linear_programming::benchmark_info_t benchmark_info; settings.benchmark_info_ptr = &benchmark_info; auto start_run_solver = std::chrono::high_resolution_clock::now(); diff --git a/cpp/src/dual_simplex/branch_and_bound.cpp b/cpp/src/dual_simplex/branch_and_bound.cpp index f1075ed557..e141a71bf7 100644 --- a/cpp/src/dual_simplex/branch_and_bound.cpp +++ b/cpp/src/dual_simplex/branch_and_bound.cpp @@ -452,6 +452,12 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut const i_t num_fractional = fractional_variables(settings, root_relax_soln.x, var_types, fractional); const f_t root_objective = compute_objective(original_lp, root_relax_soln.x); + if (settings.solution_callback != nullptr) { + std::vector original_x; + uncrush_primal_solution(original_problem, original_lp, root_relax_soln.x, original_x); + settings.set_simplex_solution_callback(original_x, + compute_user_objective(original_lp, root_objective)); + } global_variables::mutex_lower.lock(); f_t lower_bound = global_variables::lower_bound = root_objective; global_variables::mutex_lower.unlock(); diff --git a/cpp/src/dual_simplex/simplex_solver_settings.hpp b/cpp/src/dual_simplex/simplex_solver_settings.hpp index 8328f23813..df5e4e1d0c 100644 --- a/cpp/src/dual_simplex/simplex_solver_settings.hpp +++ b/cpp/src/dual_simplex/simplex_solver_settings.hpp @@ -105,6 +105,7 @@ struct simplex_solver_settings_t { 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 heuristic_preemption_callback; + std::function&, f_t)> set_simplex_solution_callback; mutable logger_t log; std::atomic* concurrent_halt; // if nullptr ignored, if !nullptr, 0 if solver should // continue, 1 if solver should halt diff --git a/cpp/src/mip/CMakeLists.txt b/cpp/src/mip/CMakeLists.txt index d3bdf3cf89..25c9a18bf3 100644 --- a/cpp/src/mip/CMakeLists.txt +++ b/cpp/src/mip/CMakeLists.txt @@ -22,7 +22,9 @@ list(PREPEND ${CMAKE_CURRENT_SOURCE_DIR}/solver.cu ${CMAKE_CURRENT_SOURCE_DIR}/solver_settings.cu ${CMAKE_CURRENT_SOURCE_DIR}/solver_solution.cu + ${CMAKE_CURRENT_SOURCE_DIR}/diversity/assignment_hash_map.cu ${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}/relaxed_lp/relaxed_lp.cu ${CMAKE_CURRENT_SOURCE_DIR}/local_search/local_search.cu diff --git a/cpp/src/mip/diversity/assignment_hash_map.cu b/cpp/src/mip/diversity/assignment_hash_map.cu new file mode 100644 index 0000000000..cdef55ea58 --- /dev/null +++ b/cpp/src/mip/diversity/assignment_hash_map.cu @@ -0,0 +1,172 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2024-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 "assignment_hash_map.cuh" + +#include +#include + +#include +#include +#include + +namespace cuopt { +namespace linear_programming { +namespace detail { + +struct combine_hash { + DI size_t operator()(size_t hash_1, size_t hash_2) + { + const std::size_t magic_constant = 0x9e3779b97f4a7c15; + hash_1 ^= hash_2 + magic_constant + (hash_1 << 12) + (hash_1 >> 4); + return hash_1; + } +}; + +template +__global__ void hash_solution_kernel(raft::device_span assignment, + raft::device_span reduction_buffer) +{ + typedef cub::BlockReduce BlockReduce; + __shared__ typename BlockReduce::TempStorage temp_storage; + + size_t th_hash = assignment.size(); +#pragma unroll + for (i_t idx = blockIdx.x * blockDim.x + threadIdx.x; idx < assignment.size(); + idx += blockDim.x * gridDim.x) { + th_hash = combine_hash()(th_hash, assignment[idx]); + } + size_t hash_sum = BlockReduce(temp_storage).Reduce(th_hash, combine_hash(), TPB); + if (threadIdx.x == 0) { reduction_buffer[blockIdx.x] = hash_sum; } +} + +template +__global__ void reduce_hash_kernel(raft::device_span reduction_buffer, + size_t* global_hash_sum) +{ + typedef cub::BlockReduce BlockReduce; + __shared__ typename BlockReduce::TempStorage temp_storage; + size_t th_hash = reduction_buffer[threadIdx.x]; + size_t hash_sum = BlockReduce(temp_storage).Reduce(th_hash, combine_hash(), TPB); + if (threadIdx.x == 0) { *global_hash_sum = hash_sum; } +} + +template +assignment_hash_map_t::assignment_hash_map_t(const problem_t& problem) + : reduction_buffer(problem.n_variables, problem.handle_ptr->get_stream()), + integer_assignment(problem.n_integer_vars, problem.handle_ptr->get_stream()), + hash_sum(problem.handle_ptr->get_stream()), + temp_storage(0, problem.handle_ptr->get_stream()) +{ +} + +// we might move this to be a solution member if it is needed +// currently having an ordered integer array is only needed here +template +void assignment_hash_map_t::fill_integer_assignment(solution_t& solution) +{ + static_assert(sizeof(f_t) == sizeof(size_t), "f_t must be double precision"); + thrust::gather(solution.handle_ptr->get_thrust_policy(), + solution.problem_ptr->integer_indices.begin(), + solution.problem_ptr->integer_indices.end(), + solution.assignment.begin(), + reinterpret_cast(integer_assignment.data())); +} + +template +size_t assignment_hash_map_t::hash_solution(solution_t& solution) +{ + const int TPB = 1024; + fill_integer_assignment(solution); + thrust::fill( + solution.handle_ptr->get_thrust_policy(), reduction_buffer.begin(), reduction_buffer.end(), 0); + hash_solution_kernel + <<<(integer_assignment.size() + TPB - 1) / TPB, TPB, 0, solution.handle_ptr->get_stream()>>>( + cuopt::make_span(integer_assignment), cuopt::make_span(reduction_buffer)); + RAFT_CHECK_CUDA(handle_ptr->get_stream()); + // Get the number of blocks used in the hash_solution_kernel + int num_blocks = (integer_assignment.size() + TPB - 1) / TPB; + + // If we have more than one block, perform a device-wide reduction using CUB + if (num_blocks > 1) { + // Determine temporary device storage requirements + void* d_temp_storage = nullptr; + size_t temp_storage_bytes = 0; + cub::DeviceReduce::Reduce(d_temp_storage, + temp_storage_bytes, + reduction_buffer.data(), + hash_sum.data(), + num_blocks, + combine_hash(), + 0, + solution.handle_ptr->get_stream()); + + // Allocate temporary storage + temp_storage.resize(temp_storage_bytes, solution.handle_ptr->get_stream()); + d_temp_storage = temp_storage.data(); + + // Run reduction + cub::DeviceReduce::Reduce(d_temp_storage, + temp_storage_bytes, + reduction_buffer.data(), + hash_sum.data(), + num_blocks, + combine_hash(), + 0, + solution.handle_ptr->get_stream()); + + // Return early since we've already computed the hash sum + return hash_sum.value(solution.handle_ptr->get_stream()); + } else { + return reduction_buffer.element(0, solution.handle_ptr->get_stream()); + } +} + +template +void assignment_hash_map_t::insert(solution_t& solution) +{ + size_t sol_hash = hash_solution(solution); + solution_hash_count[sol_hash]++; +} + +template +bool assignment_hash_map_t::check_skip_solution(solution_t& solution, + i_t max_occurance) +{ + size_t hash = hash_solution(solution); + if (solution_hash_count[hash] > max_occurance) { + CUOPT_LOG_DEBUG("Skipping solution which is encountered %d times", solution_hash_count[hash]); + return true; + } + return false; +} + +#if !MIP_INSTANTIATE_FLOAT && !MIP_INSTANTIATE_DOUBLE +static_assert(false, "MIP_INSTANTIATE_FLOAT or MIP_INSTANTIATE_DOUBLE must be defined"); +#endif + +#if MIP_INSTANTIATE_FLOAT +template class assignment_hash_map_t; +#endif + +#if MIP_INSTANTIATE_DOUBLE +template class assignment_hash_map_t; +#endif + +} // namespace detail +} // namespace linear_programming +} // namespace cuopt diff --git a/cpp/src/mip/diversity/assignment_hash_map.cuh b/cpp/src/mip/diversity/assignment_hash_map.cuh new file mode 100644 index 0000000000..8806db9d66 --- /dev/null +++ b/cpp/src/mip/diversity/assignment_hash_map.cuh @@ -0,0 +1,45 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2024-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 + +namespace cuopt { +namespace linear_programming { +namespace detail { + +template +class assignment_hash_map_t { + public: + assignment_hash_map_t(const problem_t& problem); + void fill_integer_assignment(solution_t& solution); + size_t hash_solution(solution_t& solution); + void insert(solution_t& solution); + bool check_skip_solution(solution_t& solution, i_t max_occurance); + + // keep the hash to encounter count of solution hash + std::unordered_map solution_hash_count; + rmm::device_uvector reduction_buffer; + rmm::device_uvector integer_assignment; + rmm::device_scalar hash_sum; + rmm::device_buffer temp_storage; +}; + +} // namespace detail +} // namespace linear_programming +} // namespace cuopt diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index 874230a298..a05a9063b6 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -71,8 +71,10 @@ diversity_manager_t::diversity_manager_t(mip_solver_context_thandle_ptr), rng(cuopt::seed_generator::get_seed()), stats(context.stats), - mab_arm_stats_(recombiner_enum_t::SIZE), - mab_rng_(cuopt::seed_generator::get_seed()) + mab_recombiner( + recombiner_enum_t::SIZE, cuopt::seed_generator::get_seed(), recombiner_alpha, "recombiner"), + mab_ls(mab_ls_config_t::n_of_arms, cuopt::seed_generator::get_seed(), ls_alpha, "ls"), + assignment_hash_map(*context.problem_ptr) { // Read configuration ID from environment variable int max_config = -1; @@ -103,6 +105,22 @@ diversity_manager_t::diversity_manager_t(mip_solver_context_t +bool diversity_manager_t::run_local_search(solution_t& solution, + const weight_t& weights, + timer_t& timer, + ls_config_t& ls_config) +{ + // i_t ls_mab_option = mab_ls.select_mab_option(); + // mab_ls_config_t::get_local_search_and_lm_from_config(ls_mab_option, ls_config); + assignment_hash_map.insert(solution); + constexpr i_t skip_solutions_threshold = 3; + if (assignment_hash_map.check_skip_solution(solution, skip_solutions_threshold)) { return false; } + ls.run_local_search(solution, weights, timer, ls_config); + return true; +} + // There should be at least 3 solutions in the population template bool diversity_manager_t::regenerate_solutions() @@ -143,7 +161,8 @@ std::vector> diversity_manager_t::generate_more_s solutions.emplace_back(solution_t(sol)); if (total_time_to_generate.check_time_limit()) { return solutions; } timer_t timer(std::min(ls_limit, timer.remaining_time())); - ls.run_local_search(sol, population.weights, timer); + ls_config_t ls_config; + run_local_search(sol, population.weights, timer, ls_config); population.run_solution_callbacks(sol); solutions.emplace_back(std::move(sol)); if (total_time_to_generate.check_time_limit()) { return solutions; } @@ -253,7 +272,8 @@ void diversity_manager_t::generate_initial_solutions() population.run_solution_callbacks(initial_sol_vector.back()); // run ls on the generated solutions solution_t searched_sol(initial_sol_vector.back()); - ls.run_local_search(searched_sol, population.weights, gen_timer); + ls_config_t ls_config; + run_local_search(searched_sol, population.weights, gen_timer, ls_config); population.run_solution_callbacks(searched_sol); initial_sol_vector.emplace_back(std::move(searched_sol)); average_fj_weights(i); @@ -318,7 +338,8 @@ void diversity_manager_t::generate_quick_feasible_solution() initial_sol_vector.emplace_back(std::move(solution)); problem_ptr->handle_ptr->sync_stream(); solution_t searched_sol(initial_sol_vector.back()); - ls.run_local_search(searched_sol, population.weights, sol_timer); + ls_config_t ls_config; + run_local_search(searched_sol, population.weights, sol_timer, ls_config); population.run_solution_callbacks(searched_sol); initial_sol_vector.emplace_back(std::move(searched_sol)); auto& feas_sol = initial_sol_vector.back().get_feasible() @@ -353,7 +374,6 @@ void diversity_manager_t::run_fj_alone(solution_t& solution) ls.fj.settings.n_of_minimums_for_exit = 20000 * 1000; ls.fj.settings.update_weights = true; ls.fj.settings.feasibility_run = false; - ls.fj.settings.termination = fj_termination_flags_t::FJ_TERMINATION_TIME_LIMIT; ls.fj.settings.time_limit = timer.remaining_time(); ls.fj.solve(solution); CUOPT_LOG_INFO("FJ alone finished!"); @@ -407,14 +427,34 @@ solution_t diversity_manager_t::run_solver() lp_state_t& lp_state = problem_ptr->lp_state; // resize because some constructor might be called before the presolve lp_state.resize(*problem_ptr, problem_ptr->handle_ptr->get_stream()); - relaxed_lp_settings_t lp_settings; - lp_settings.time_limit = lp_time_limit; - lp_settings.tolerance = context.settings.tolerances.absolute_tolerance; - lp_settings.return_first_feasible = false; - lp_settings.save_state = true; - if (!fj_only_run) { + bool bb_thread_solution_exists = false; + { + std::lock_guard guard(relaxed_solution_mutex); + bb_thread_solution_exists = simplex_solution_exists; + } // Mutex is unlocked here + if (bb_thread_solution_exists) { + ls.lp_optimal_exists = true; + } else if (!fj_only_run) { + relaxed_lp_settings_t lp_settings; + lp_settings.time_limit = lp_time_limit; + lp_settings.tolerance = context.settings.tolerances.absolute_tolerance; + lp_settings.return_first_feasible = false; + lp_settings.save_state = true; + lp_settings.concurrent_halt = &global_concurrent_halt; + rmm::device_uvector lp_optimal_solution_copy(lp_optimal_solution.size(), + problem_ptr->handle_ptr->get_stream()); auto lp_result = - get_relaxed_lp_solution(*problem_ptr, lp_optimal_solution, lp_state, lp_settings); + get_relaxed_lp_solution(*problem_ptr, lp_optimal_solution_copy, lp_state, lp_settings); + { + std::lock_guard guard(relaxed_solution_mutex); + if (!simplex_solution_exists) { + raft::copy(lp_optimal_solution.data(), + lp_optimal_solution_copy.data(), + lp_optimal_solution.size(), + problem_ptr->handle_ptr->get_stream()); + } + } + problem_ptr->handle_ptr->sync_stream(); ls.lp_optimal_exists = true; if (lp_result.get_termination_status() == pdlp_termination_status_t::Optimal) { set_new_user_bound(lp_result.get_objective_value()); @@ -530,7 +570,8 @@ void diversity_manager_t::recombine_and_ls_with_all( for (auto& sol : solutions) { if (timer.check_time_limit()) { return; } solution_t ls_solution(sol); - ls.run_local_search(ls_solution, population.weights, timer); + ls_config_t ls_config; + run_local_search(ls_solution, population.weights, timer, ls_config); if (timer.check_time_limit()) { return; } // TODO try if running LP with integers fixed makes it feasible if (ls_solution.get_feasible()) { @@ -628,17 +669,17 @@ diversity_manager_t::recombine_and_local_search(solution_t& sol1.get_feasible(), sol2.get_quality(population.weights), sol2.get_feasible()); - double best_of_parents = std::min(sol1.get_objective(), sol2.get_objective()); + double best_objective_of_parents = std::min(sol1.get_objective(), sol2.get_objective()); bool at_least_one_parent_feasible = sol1.get_feasible() || sol2.get_feasible(); // randomly choose among 3 recombiners auto [offspring, success] = recombine(sol1, sol2); if (!success) { // add the attempt - add_mab_reward(recombine_stats.get_last_attempt(), - std::numeric_limits::lowest(), - std::numeric_limits::lowest(), - std::numeric_limits::max(), - 0.0); + mab_recombiner.add_mab_reward(recombine_stats.get_last_attempt(), + std::numeric_limits::lowest(), + std::numeric_limits::lowest(), + std::numeric_limits::max(), + recombiner_work_normalized_reward_t(0.0)); return std::make_pair(solution_t(sol1), solution_t(sol2)); } cuopt_assert(population.test_invariant(), ""); @@ -648,15 +689,25 @@ diversity_manager_t::recombine_and_local_search(solution_t& offspring.get_feasible()); cuopt_assert(offspring.test_number_all_integer(), "All must be integers before LS"); bool feasibility_before = offspring.get_feasible(); - ls.run_local_search( - offspring, population.weights, timer, best_of_parents, at_least_one_parent_feasible); + 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); + if (!success) { + // add the attempt + mab_recombiner.add_mab_reward(recombine_stats.get_last_attempt(), + std::numeric_limits::lowest(), + std::numeric_limits::lowest(), + std::numeric_limits::max(), + recombiner_work_normalized_reward_t(0.0)); + return std::make_pair(solution_t(sol1), solution_t(sol2)); + } cuopt_assert(offspring.test_number_all_integer(), "All must be integers after LS"); cuopt_assert(population.test_invariant(), ""); - + offspring.compute_feasibility(); CUOPT_LOG_DEBUG("After LS offspring sol cost:feas %f : %d", offspring.get_quality(population.weights), offspring.get_feasible()); - offspring.compute_feasibility(); cuopt_assert(population.test_invariant(), ""); // run LP with the vars solution_t lp_offspring(offspring); @@ -685,12 +736,19 @@ diversity_manager_t::recombine_and_local_search(solution_t& f_t offspring_qual = std::min(offspring.get_quality(population.weights), lp_qual); recombine_stats.update_improve_stats( offspring_qual, sol1.get_quality(population.weights), sol2.get_quality(population.weights)); - add_mab_reward( + f_t best_quality_of_parents = + std::min(sol1.get_quality(population.weights), sol2.get_quality(population.weights)); + mab_recombiner.add_mab_reward( recombine_stats.get_last_attempt(), - std::min(sol1.get_quality(population.weights), sol2.get_quality(population.weights)), - population.best_feasible().get_quality(population.weights), + best_quality_of_parents, + population.best().get_quality(population.weights), offspring_qual, - recombine_stats.get_last_recombiner_time()); + recombiner_work_normalized_reward_t(recombine_stats.get_last_recombiner_time())); + mab_ls.add_mab_reward(mab_ls_config_t::last_ls_mab_option, + best_quality_of_parents, + population.best_feasible().get_quality(population.weights), + offspring_qual, + ls_work_normalized_reward_t(mab_ls_config_t::last_lm_config)); if (context.settings.benchmark_info_ptr != nullptr) { check_better_than_both(offspring, sol1, sol2); check_better_than_both(lp_offspring, sol1, sol2); @@ -710,7 +768,7 @@ std::pair, bool> diversity_manager_t::recombine( } else if (run_only_fp_recombiner) { recombiner = recombiner_enum_t::FP; } else { - recombiner = select_mab_recombiner(); + recombiner = mab_recombiner.select_mab_option(); } recombine_stats.add_attempt((recombiner_enum_t)recombiner); recombine_stats.start_recombiner_time(); @@ -733,163 +791,25 @@ std::pair, bool> diversity_manager_t::recombine( } template -recombiner_enum_t diversity_manager_t::select_mab_recombiner() -{ - if (recombiner_enum_t::SIZE == 0) { - CUOPT_LOG_ERROR("select_mab_recombiner called with no recombiners defined."); - cuopt_expects(false, error_type_t::RuntimeError, "No recombiners available to select in MAB."); - } - - mab_total_steps_++; - - // Phase 1: Initial exploration - ensure each arm is tried at least once - for (int i = 0; i < static_cast(recombiner_enum_t::SIZE); ++i) { - if (mab_arm_stats_[i].num_pulls == 0) { - CUOPT_LOG_DEBUG("MAB Initial Pull: Arm " + std::to_string(i)); - return static_cast(i); - } - } - - if (use_ucb_) { - // Phase 2: UCB Action Selection - return select_ucb_arm(); - } else { - // Fallback to epsilon-greedy if desired - return select_epsilon_greedy_arm(); - } -} - -// UCB arm selection with confidence bounds -template -recombiner_enum_t diversity_manager_t::select_ucb_arm() +void diversity_manager_t::set_simplex_solution(const std::vector& solution, + f_t objective) { - double max_ucb_value = -std::numeric_limits::infinity(); - std::vector best_arms; - - for (int i = 0; i < static_cast(recombiner_enum_t::SIZE); ++i) { - // Calculate UCB value: Q(a) + sqrt(2*ln(t)/N(a)) - double confidence_bound = std::sqrt((2.0 * std::log(mab_total_steps_)) / - static_cast(mab_arm_stats_[i].num_pulls)); - double ucb_value = mab_arm_stats_[i].q_value + confidence_bound; - - CUOPT_LOG_DEBUG( - "MAB UCB: Arm " + std::to_string(i) + ", Q=" + std::to_string(mab_arm_stats_[i].q_value) + - ", CB=" + std::to_string(confidence_bound) + ", UCB=" + std::to_string(ucb_value)); - - constexpr double tolerance = 1e-9; - if (ucb_value > max_ucb_value + tolerance) { - max_ucb_value = ucb_value; - best_arms.clear(); - best_arms.push_back(static_cast(i)); - } else if (std::abs(ucb_value - max_ucb_value) < tolerance) { - best_arms.push_back(static_cast(i)); - } - } - - if (!best_arms.empty()) { - std::uniform_int_distribution dist_tie(0, best_arms.size() - 1); - recombiner_enum_t chosen_arm = best_arms[dist_tie(mab_rng_)]; - CUOPT_LOG_DEBUG("MAB UCB Selected: Arm " + std::to_string(static_cast(chosen_arm)) + - " (UCB Value: " + std::to_string(max_ucb_value) + ")"); - return chosen_arm; - } else { - CUOPT_LOG_ERROR("MAB UCB: No best arm found, falling back to random."); - std::uniform_int_distribution dist_arm(0, recombiner_enum_t::SIZE - 1); - return static_cast(dist_arm(mab_rng_)); - } -} - -// Fallback epsilon-greedy method (preserved for compatibility) -template -recombiner_enum_t diversity_manager_t::select_epsilon_greedy_arm() -{ - std::uniform_real_distribution dist_epsilon(0.0, 1.0); - if (dist_epsilon(mab_rng_) < mab_epsilon_) { - // Explore: Choose a random arm - std::uniform_int_distribution dist_arm(0, recombiner_enum_t::SIZE - 1); - recombiner_enum_t random_arm = static_cast(dist_arm(mab_rng_)); - CUOPT_LOG_DEBUG("MAB Explore: Arm " + std::to_string(static_cast(random_arm))); - return random_arm; - } else { - // Exploit: Choose the arm with the highest Q value - double max_q_value = -std::numeric_limits::infinity(); - std::vector best_arms; - - for (int i = 0; i < static_cast(recombiner_enum_t::SIZE); ++i) { - constexpr double tolerance = 1e-9; - if (mab_arm_stats_[i].q_value > max_q_value + tolerance) { - max_q_value = mab_arm_stats_[i].q_value; - best_arms.clear(); - best_arms.push_back(static_cast(i)); - } else if (std::abs(mab_arm_stats_[i].q_value - max_q_value) < tolerance) { - best_arms.push_back(static_cast(i)); - } - } - - if (!best_arms.empty()) { - std::uniform_int_distribution dist_tie(0, best_arms.size() - 1); - recombiner_enum_t chosen_arm = best_arms[dist_tie(mab_rng_)]; - CUOPT_LOG_DEBUG("MAB Exploit: Arm " + std::to_string(static_cast(chosen_arm)) + - " (Q Value: " + std::to_string(max_q_value) + ")"); - return chosen_arm; - } - } - - // Fallback - std::uniform_int_distribution dist_arm(0, recombiner_enum_t::SIZE - 1); - return static_cast(dist_arm(mab_rng_)); -} - -template -void diversity_manager_t::add_mab_reward(recombiner_enum_t recombiner_id, - double best_of_parents_quality, - double best_feasible_quality, - double offspring_quality, - double recombination_time_in_miliseconds) -{ - int id_val = static_cast(recombiner_id); - double epsilon = std::max(1e-6, 1e-4 * std::abs(best_feasible_quality)); - bool is_better_than_best_feasible = offspring_quality + epsilon < best_feasible_quality; - bool is_better_than_best_of_parents = offspring_quality + epsilon < best_of_parents_quality; - if (id_val >= 0 && id_val < static_cast(mab_arm_stats_.size())) { - // Calculate reward based on your existing logic - double reward = 0.0; - if (is_better_than_best_feasible) { - reward = 8.0; - } else if (is_better_than_best_of_parents) { - double factor; - if (std::abs(offspring_quality - best_feasible_quality) / - (std::abs(best_feasible_quality) + 1.0) > - 1.0) { - factor = 0.; - } else if (std::abs(offspring_quality - best_feasible_quality) / - (std::abs(best_feasible_quality) + 1.0) > - 0.2) { - factor = 0.; - } else { - factor = 1.; - } - reward = factor * (std::max(0.1, 4.0 - (recombination_time_in_miliseconds / 2000))); - } - - // Update statistics - mab_arm_stats_[id_val].num_pulls++; - mab_arm_stats_[id_val].last_reward = reward; - - // Exponential recency-weighted average update: Q_new = Q_old + α(R - Q_old) - double prediction_error = reward - mab_arm_stats_[id_val].q_value; - mab_arm_stats_[id_val].q_value += mab_alpha_ * prediction_error; - - CUOPT_LOG_DEBUG( - "MAB Reward Update: Arm " + std::to_string(id_val) + ", Reward: " + std::to_string(reward) + - ", is_better_than_best_of_parents: " + (is_better_than_best_of_parents ? "Yes" : "No") + - ", Better than best: " + (is_better_than_best_feasible ? "Yes" : "No") + - ", Pulls: " + std::to_string(mab_arm_stats_[id_val].num_pulls) + - ", Q Value: " + std::to_string(mab_arm_stats_[id_val].q_value)); - } else { - CUOPT_LOG_ERROR("MAB: Attempted to add reward for invalid recombiner_id: " + - std::to_string(id_val)); - } + CUOPT_LOG_DEBUG("Setting simplex solution with objective %f", objective); + using sol_t = solution_t; + cuopt_func_call(sol_t new_sol(*problem_ptr)); + cuopt_func_call(new_sol.copy_new_assignment(solution)); + cuopt_func_call(new_sol.compute_feasibility()); + cuopt_assert(integer_equal(new_sol.get_user_objective(), objective, 1e-3), "Objective mismatch"); + std::lock_guard lock(relaxed_solution_mutex); + RAFT_CUDA_TRY(cudaSetDevice(context.handle_ptr->get_device())); + simplex_solution_exists = true; + global_concurrent_halt.store(1, std::memory_order_release); + // it is safe to use lp_optimal_solution while executing the copy operation + // the operations are ordered as long as they are on the same stream + raft::copy( + lp_optimal_solution.data(), solution.data(), solution.size(), context.handle_ptr->get_stream()); + set_new_user_bound(objective); + context.handle_ptr->sync_stream(); } #if MIP_INSTANTIATE_FLOAT diff --git a/cpp/src/mip/diversity/diversity_manager.cuh b/cpp/src/mip/diversity/diversity_manager.cuh index c45b831075..712ad3f4a2 100644 --- a/cpp/src/mip/diversity/diversity_manager.cuh +++ b/cpp/src/mip/diversity/diversity_manager.cuh @@ -17,6 +17,8 @@ #pragma once +#include "assignment_hash_map.cuh" +#include "multi_armed_bandit.cuh" #include "population.cuh" #include "recombiners/bound_prop_recombiner.cuh" @@ -67,11 +69,18 @@ class diversity_manager_t { void check_better_than_both(solution_t& offspring, solution_t& sol1, solution_t& sol2); + bool run_local_search(solution_t& solution, + const weight_t& weights, + timer_t& timer, + ls_config_t& ls_config); + + void set_simplex_solution(const std::vector& solution, f_t objective); mip_solver_context_t& context; problem_t* problem_ptr; population_t population; rmm::device_uvector lp_optimal_solution; + bool simplex_solution_exists{false}; local_search_t ls; cuopt::timer_t timer; bound_prop_recombiner_t bound_prop_recombiner; @@ -82,29 +91,14 @@ class diversity_manager_t { i_t current_step{0}; solver_stats_t& stats; std::vector> initial_sol_vector; + mab_t mab_recombiner; + mab_t mab_ls; + assignment_hash_map_t assignment_hash_map; + // mutex for the simplex solution update + std::mutex relaxed_solution_mutex; + // atomic for signalling pdlp to stop + std::atomic global_concurrent_halt{0}; - // Enhanced statistics structure for UCB with exponential recency weighting - struct mab_arm_stats_t { - int num_pulls = 0; // Number of times this arm was selected - double q_value = 0.5; // Exponential recency-weighted average estimate - double last_reward = 0.0; // Last reward received (for debugging) - }; - std::vector mab_arm_stats_; - double mab_epsilon_ = 0.15; // Probability of exploration in Epsilon-Greedy. - std::mt19937 mab_rng_; // RNG dedicated to MAB decisions. - double mab_alpha_ = 0.05; // Step size for exponential recency weighting - int mab_total_steps_ = 0; // Total number of action selections (for UCB) - bool use_ucb_ = true; // Flag to enable UCB vs epsilon-greedy - - // --- MAB Helper Methods --- - recombiner_enum_t select_mab_recombiner(); - void add_mab_reward(recombiner_enum_t recombiner_id, - double best_of_parents_quality, - double best_feasible_quality, - double offspring_quality, - double recombination_time_in_miliseconds); - recombiner_enum_t select_ucb_arm(); - recombiner_enum_t select_epsilon_greedy_arm(); 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/multi_armed_bandit.cu b/cpp/src/mip/diversity/multi_armed_bandit.cu new file mode 100644 index 0000000000..921cead995 --- /dev/null +++ b/cpp/src/mip/diversity/multi_armed_bandit.cu @@ -0,0 +1,199 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2024-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 +#include +#include "multi_armed_bandit.cuh" + +#include + +namespace cuopt::linear_programming::detail { + +mab_t::mab_t(int n_arms, int seed, double alpha, std::string bandit_name) + : mab_arm_stats_(n_arms), mab_rng_(seed), bandit_name(bandit_name), mab_alpha_(alpha) +{ +} + +int mab_t::select_mab_option() +{ + if (mab_arm_stats_.size() == 0) { + CUOPT_LOG_ERROR("select_mab_recombiner called with no recombiners defined."); + cuopt_expects(false, error_type_t::RuntimeError, "No recombiners available to select in MAB."); + } + + mab_total_steps_++; + + // Phase 1: Initial exploration - ensure each arm is tried at least once + for (int i = 0; i < static_cast(mab_arm_stats_.size()); ++i) { + if (mab_arm_stats_[i].num_pulls == 0) { + CUOPT_LOG_DEBUG("MAB " + bandit_name + ": Initial Pull: Arm " + std::to_string(i)); + return i; + } + } + + if (use_ucb_) { + // Phase 2: UCB Action Selection + return select_ucb_arm(); + } else { + // Fallback to epsilon-greedy if desired + return select_epsilon_greedy_arm(); + } +} + +// UCB arm selection with confidence bounds +int mab_t::select_ucb_arm() +{ + double max_ucb_value = -std::numeric_limits::infinity(); + std::vector best_arms; + + for (int i = 0; i < static_cast(mab_arm_stats_.size()); ++i) { + // Calculate UCB value: Q(a) + 2*sqrt(ln(t)/N(a)) + double confidence_bound = std::sqrt((2.0 * std::log(mab_total_steps_)) / + static_cast(mab_arm_stats_[i].num_pulls)); + double ucb_value = mab_arm_stats_[i].q_value + confidence_bound; + + CUOPT_LOG_DEBUG("MAB " + bandit_name + ": UCB: Arm " + std::to_string(i) + + ", Q=" + std::to_string(mab_arm_stats_[i].q_value) + ", CB=" + + std::to_string(confidence_bound) + ", UCB=" + std::to_string(ucb_value)); + + constexpr double tolerance = 1e-9; + if (ucb_value > max_ucb_value + tolerance) { + max_ucb_value = ucb_value; + best_arms.clear(); + best_arms.push_back(i); + } else if (std::abs(ucb_value - max_ucb_value) < tolerance) { + best_arms.push_back(i); + } + } + + if (!best_arms.empty()) { + std::uniform_int_distribution dist_tie(0, best_arms.size() - 1); + int chosen_arm = best_arms[dist_tie(mab_rng_)]; + CUOPT_LOG_DEBUG("MAB " + bandit_name + ": UCB Selected: Arm " + std::to_string(chosen_arm) + + " (UCB Value: " + std::to_string(max_ucb_value) + ")"); + return chosen_arm; + } else { + CUOPT_LOG_ERROR("MAB " + bandit_name + ": UCB: No best arm found, falling back to random."); + std::uniform_int_distribution dist_arm(0, mab_arm_stats_.size() - 1); + return dist_arm(mab_rng_); + } +} + +// Fallback epsilon-greedy method (preserved for compatibility) +int mab_t::select_epsilon_greedy_arm() +{ + std::uniform_real_distribution dist_epsilon(0.0, 1.0); + if (dist_epsilon(mab_rng_) < mab_epsilon_) { + // Explore: Choose a random arm + std::uniform_int_distribution dist_arm(0, mab_arm_stats_.size() - 1); + int random_arm = dist_arm(mab_rng_); + CUOPT_LOG_DEBUG("MAB " + bandit_name + ": Explore: Arm " + + std::to_string(static_cast(random_arm))); + return random_arm; + } else { + // Exploit: Choose the arm with the highest Q value + double max_q_value = -std::numeric_limits::infinity(); + std::vector best_arms; + + for (int i = 0; i < static_cast(mab_arm_stats_.size()); ++i) { + constexpr double tolerance = 1e-9; + if (mab_arm_stats_[i].q_value > max_q_value + tolerance) { + max_q_value = mab_arm_stats_[i].q_value; + best_arms.clear(); + best_arms.push_back(i); + } else if (std::abs(mab_arm_stats_[i].q_value - max_q_value) < tolerance) { + best_arms.push_back(i); + } + } + + if (!best_arms.empty()) { + std::uniform_int_distribution dist_tie(0, best_arms.size() - 1); + int chosen_arm = best_arms[dist_tie(mab_rng_)]; + CUOPT_LOG_DEBUG("MAB " + bandit_name + ": Exploit: Arm " + + std::to_string(static_cast(chosen_arm)) + + " (Q Value: " + std::to_string(max_q_value) + ")"); + return chosen_arm; + } + } + + // Fallback + std::uniform_int_distribution dist_arm(0, mab_arm_stats_.size() - 1); + return dist_arm(mab_rng_); +} + +template +void mab_t::add_mab_reward(int option_id, + double best_of_parents_quality, + double best_feasible_quality, + double offspring_quality, + Func work_normalized_reward) +{ + double epsilon = max(1e-6, 1e-4 * fabs(best_feasible_quality)); + bool is_better_than_best_feasible = offspring_quality + epsilon < best_feasible_quality; + bool is_better_than_best_of_parents = offspring_quality + epsilon < best_of_parents_quality; + if (option_id >= 0 && option_id < static_cast(mab_arm_stats_.size())) { + // Calculate reward based on your existing logic + double reward = 0.0; + if (is_better_than_best_feasible) { + reward = 8.0; + } else if (is_better_than_best_of_parents) { + double factor = 0.; + if (fabs(offspring_quality - best_feasible_quality) / (fabs(best_feasible_quality) + 1.0) < + 0.2) { + factor = 1.; + } + reward = work_normalized_reward(factor); + } + + // Update statistics + mab_arm_stats_[option_id].num_pulls++; + mab_arm_stats_[option_id].last_reward = reward; + + // Exponential recency-weighted average update: Q_new = Q_old + α(R - Q_old) + double prediction_error = reward - mab_arm_stats_[option_id].q_value; + mab_arm_stats_[option_id].q_value += mab_alpha_ * prediction_error; + + CUOPT_LOG_DEBUG("MAB " + bandit_name + ": Reward Update: Arm " + std::to_string(option_id) + + ", Reward: " + std::to_string(reward) + ", is_better_than_best_of_parents: " + + (is_better_than_best_of_parents ? "Yes" : "No") + + ", Better than best: " + (is_better_than_best_feasible ? "Yes" : "No") + + ", Pulls: " + std::to_string(mab_arm_stats_[option_id].num_pulls) + + ", Q Value: " + std::to_string(mab_arm_stats_[option_id].q_value)); + } else { + CUOPT_LOG_ERROR("MAB " + bandit_name + ": Attempted to add reward for invalid option_id: " + + std::to_string(option_id)); + } +} + +#if MIP_INSTANTIATE_FLOAT +template struct mab_ls_config_t; +template void mab_t::add_mab_reward( + int, double, double, double, ls_work_normalized_reward_t); +template void mab_t::add_mab_reward( + int, double, double, double, recombiner_work_normalized_reward_t); +#endif + +#if MIP_INSTANTIATE_DOUBLE +template struct mab_ls_config_t; +template void mab_t::add_mab_reward( + int, double, double, double, ls_work_normalized_reward_t); +template void mab_t::add_mab_reward( + int, double, double, double, recombiner_work_normalized_reward_t); +#endif + +} // namespace cuopt::linear_programming::detail \ No newline at end of file diff --git a/cpp/src/mip/diversity/multi_armed_bandit.cuh b/cpp/src/mip/diversity/multi_armed_bandit.cuh new file mode 100644 index 0000000000..f48601c65d --- /dev/null +++ b/cpp/src/mip/diversity/multi_armed_bandit.cuh @@ -0,0 +1,113 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2024-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 { + +constexpr double recombiner_alpha = 0.05; +constexpr double ls_alpha = 0.03; + +template +struct mab_ls_config_t { + static constexpr i_t n_of_ls = 2; + static constexpr i_t n_of_configs = 4; + static constexpr i_t n_of_arms = n_of_ls * n_of_configs; + static constexpr i_t ls_local_mins[n_of_configs] = {50, 100, 200, 500}; + static constexpr i_t ls_line_segment_local_mins[n_of_configs] = {10, 20, 40, 100}; + + static void get_local_search_and_lm_from_config(i_t config_id, ls_config_t& ls_config) + { + ls_method_t local_search = ls_method_t(config_id % n_of_ls); + i_t lm_id = config_id / n_of_ls; + if (local_search == ls_method_t::FJ_LINE_SEGMENT) { + ls_config.ls_method = ls_method_t::FJ_LINE_SEGMENT; + ls_config.n_local_mins_for_line_segment = ls_line_segment_local_mins[lm_id]; + } else { + ls_config.ls_method = ls_method_t::FJ_ANNEALING; + ls_config.n_local_mins = ls_local_mins[lm_id]; + } + mab_ls_config_t::last_lm_config = lm_id; + mab_ls_config_t::last_ls_mab_option = config_id; + } + static i_t last_lm_config; + static i_t last_ls_mab_option; +}; + +template +i_t mab_ls_config_t::last_lm_config = 0; +template +i_t mab_ls_config_t::last_ls_mab_option = 0; + +struct ls_work_normalized_reward_t { + int option_id; + static constexpr double reward_per_option[mab_ls_config_t::n_of_configs] = { + 2, 1, 0.5, 0.25}; + ls_work_normalized_reward_t(int option_id) : option_id(option_id) {} + + double operator()(double factor) const { return factor * reward_per_option[option_id]; } +}; + +struct recombiner_work_normalized_reward_t { + double time_in_miliseconds; + recombiner_work_normalized_reward_t(double time_in_miliseconds) + : time_in_miliseconds(time_in_miliseconds) + { + } + + double operator()(double factor) const + { + // normal recombiners take 2000 ms + return factor * (std::max(0.1, 4.0 - (time_in_miliseconds / 2000))); + } +}; + +struct mab_t { + mab_t(int n_arms, int seed, double alpha, std::string bandit_name); + // Enhanced statistics structure for UCB with exponential recency weighting + struct mab_arm_stats_t { + int num_pulls = 0; // Number of times this arm was selected + double q_value = 0.5; // Exponential recency-weighted average estimate + double last_reward = 0.0; // Last reward received (for debugging) + }; + std::vector mab_arm_stats_; + double mab_epsilon_ = 0.15; // Probability of exploration in Epsilon-Greedy. + std::mt19937 mab_rng_; // RNG dedicated to MAB decisions. + double mab_alpha_ = 0.05; // Step size for exponential recency weighting + int mab_total_steps_ = 0; // Total number of action selections (for UCB) + bool use_ucb_ = true; // Flag to enable UCB vs epsilon-greedy + std::string bandit_name; + + // --- MAB Helper Methods --- + int select_mab_option(); + template + void add_mab_reward(int option_id, + double best_of_parents_quality, + double best_feasible_quality, + double offspring_quality, + Func work_normalized_reward); + int select_ucb_arm(); + int select_epsilon_greedy_arm(); +}; + +} // namespace cuopt::linear_programming::detail \ No newline at end of file diff --git a/cpp/src/mip/diversity/population.cu b/cpp/src/mip/diversity/population.cu index 5ed21f9c4d..6a42cb39a1 100644 --- a/cpp/src/mip/diversity/population.cu +++ b/cpp/src/mip/diversity/population.cu @@ -280,7 +280,7 @@ void population_t::adjust_weights_according_to_best_feasible() CUOPT_LOG_DEBUG("Best solution is infeasible, adjusting weights"); // if not the case, adjust the weights such that the best is feasible f_t weighted_violation_of_best = best().get_quality(weights) - best().get_objective(); - CUOPT_LOG_DEBUG("weighted_violation_of_best %f quality %f objective %f\n", + CUOPT_LOG_DEBUG("weighted_violation_of_best %f quality %f objective %f", weighted_violation_of_best, best().get_quality(weights), best().get_objective()); diff --git a/cpp/src/mip/diversity/recombiners/line_segment_recombiner.cuh b/cpp/src/mip/diversity/recombiners/line_segment_recombiner.cuh index cfde6d03c4..d3640f5b34 100644 --- a/cpp/src/mip/diversity/recombiners/line_segment_recombiner.cuh +++ b/cpp/src/mip/diversity/recombiners/line_segment_recombiner.cuh @@ -100,11 +100,10 @@ class line_segment_recombiner_t : public recombiner_t { std::min(guiding_solution.get_quality(weights), other_solution.get_quality(weights)); line_segment_search.settings.parents_infeasible = !guiding_solution.get_feasible() && !other_solution.get_feasible(); - // TODO fix common part and run FJ on remaining + line_segment_search.settings.n_points_to_search = ls_recombiner_config_t::n_points_to_search; line_segment_search.search_line_segment(offspring, guiding_solution.assignment, other_solution.assignment, - n_points_to_search, delta_vector, is_feasibility_run, line_segment_timer); diff --git a/cpp/src/mip/diversity/recombiners/recombiner.cuh b/cpp/src/mip/diversity/recombiners/recombiner.cuh index e4a86b4914..94ca34ea18 100644 --- a/cpp/src/mip/diversity/recombiners/recombiner.cuh +++ b/cpp/src/mip/diversity/recombiners/recombiner.cuh @@ -119,6 +119,8 @@ class recombiner_t { for (auto var : vec_remaining_indices) { if (vec_objective_coeffs[var] != 0) { objective_indices_in_subproblem.push_back(var); } } + // needed for set_difference + std::sort(objective_indices_in_subproblem.begin(), objective_indices_in_subproblem.end()); CUOPT_LOG_DEBUG("n_objective_vars in different vars %d n_objective_vars %d", objective_indices_in_subproblem.size(), objective_indices.size()); diff --git a/cpp/src/mip/diversity/recombiners/recombiner_stats.hpp b/cpp/src/mip/diversity/recombiners/recombiner_stats.hpp index 1c34b0b5c4..e87cdd57a2 100644 --- a/cpp/src/mip/diversity/recombiners/recombiner_stats.hpp +++ b/cpp/src/mip/diversity/recombiners/recombiner_stats.hpp @@ -17,6 +17,8 @@ #pragma once +#include + namespace cuopt::linear_programming::detail { enum recombiner_enum_t : int { BOUND_PROP = 0, FP, LINE_SEGMENT, SIZE }; diff --git a/cpp/src/mip/feasibility_jump/feasibility_jump.cu b/cpp/src/mip/feasibility_jump/feasibility_jump.cu index 5043d77801..ba92a99a87 100644 --- a/cpp/src/mip/feasibility_jump/feasibility_jump.cu +++ b/cpp/src/mip/feasibility_jump/feasibility_jump.cu @@ -842,9 +842,6 @@ i_t fj_t::host_loop(solution_t& solution, i_t climber_idx) f_t obj = -std::numeric_limits::infinity(); data.incumbent_quality.set_value_async(obj, handle_ptr->get_stream()); - cuopt_assert((settings.termination & fj_termination_flags_t::FJ_TERMINATION_TIME_LIMIT) || - (settings.termination & fj_termination_flags_t::FJ_TERMINATION_ITERATION_LIMIT), - "invalid termination criteria"); data.incumbent_quality.set_value_async(obj, handle_ptr->get_stream()); timer_t timer(settings.time_limit); @@ -853,14 +850,7 @@ i_t fj_t::host_loop(solution_t& solution, i_t climber_idx) for (steps = 0; steps < std::numeric_limits::max(); steps += iterations_per_graph) { // to actualize time limit handle_ptr->sync_stream(); - if ((settings.termination & fj_termination_flags_t::FJ_TERMINATION_TIME_LIMIT) && - timer.check_time_limit()) { - limit_reached = true; - } - if ((settings.termination & fj_termination_flags_t::FJ_TERMINATION_ITERATION_LIMIT) && - steps >= settings.iteration_limit) { - limit_reached = true; - } + if (timer.check_time_limit() || steps >= settings.iteration_limit) { limit_reached = true; } #if !FJ_SINGLE_STEP if (steps % 500 == 0) diff --git a/cpp/src/mip/feasibility_jump/feasibility_jump.cuh b/cpp/src/mip/feasibility_jump/feasibility_jump.cuh index a55f115f19..028b1c8baf 100644 --- a/cpp/src/mip/feasibility_jump/feasibility_jump.cuh +++ b/cpp/src/mip/feasibility_jump/feasibility_jump.cuh @@ -82,13 +82,6 @@ struct fj_hyper_parameters_t { int load_balancing_codepath_min_varcount = 3200; }; -// instead of putting in a type dependent class, put it in a name space -// easy to access and prevents variable shadowing -enum fj_termination_flags_t { - FJ_TERMINATION_TIME_LIMIT = (1 << 0), - FJ_TERMINATION_ITERATION_LIMIT = (1 << 1) -}; - enum fj_move_type_t { FJ_MOVE_BEGIN = 0, FJ_MOVE_LIFT = FJ_MOVE_BEGIN, @@ -112,7 +105,6 @@ struct fj_settings_t { int seed{0}; fj_mode_t mode{fj_mode_t::FIRST_FEASIBLE}; fj_candidate_selection_t candidate_selection{fj_candidate_selection_t::WEIGHTED_SCORE}; - int termination{fj_termination_flags_t::FJ_TERMINATION_TIME_LIMIT}; double time_limit{60.0}; int iteration_limit{std::numeric_limits::max()}; fj_hyper_parameters_t parameters{}; diff --git a/cpp/src/mip/local_search/feasibility_pump/feasibility_pump.cu b/cpp/src/mip/local_search/feasibility_pump/feasibility_pump.cu index c8aa874334..ee04155d6c 100644 --- a/cpp/src/mip/local_search/feasibility_pump/feasibility_pump.cu +++ b/cpp/src/mip/local_search/feasibility_pump/feasibility_pump.cu @@ -291,7 +291,6 @@ bool feasibility_pump_t::run_fj_cycle_escape(solution_t& sol fj.settings.feasibility_run = true; fj.settings.n_of_minimums_for_exit = 5000; fj.settings.time_limit = std::min(3., timer.remaining_time()); - fj.settings.termination = fj_termination_flags_t::FJ_TERMINATION_TIME_LIMIT; is_feasible = fj.solve(solution); // if FJ didn't change the solution, take last incumbent solution if (!is_feasible && cycle_queue.check_cycle(solution)) { @@ -314,7 +313,6 @@ bool feasibility_pump_t::test_fj_feasible(solution_t& soluti fj.settings.feasibility_run = true; fj.settings.n_of_minimums_for_exit = 5000; fj.settings.time_limit = std::min(time_limit, timer.remaining_time()); - fj.settings.termination = fj_termination_flags_t::FJ_TERMINATION_TIME_LIMIT; cuopt_func_call(solution.test_variable_bounds(true)); is_feasible = fj.solve(solution); cuopt_func_call(solution.test_variable_bounds(true)); diff --git a/cpp/src/mip/local_search/line_segment_search/line_segment_search.cu b/cpp/src/mip/local_search/line_segment_search/line_segment_search.cu index 154723b3fa..6d0be3c669 100644 --- a/cpp/src/mip/local_search/line_segment_search/line_segment_search.cu +++ b/cpp/src/mip/local_search/line_segment_search/line_segment_search.cu @@ -136,7 +136,6 @@ bool line_segment_search_t::search_line_segment( solution_t& solution, const rmm::device_uvector& point_1, const rmm::device_uvector& point_2, - i_t n_points_to_search, const rmm::device_uvector& delta_vector, bool is_feasibility_run, cuopt::timer_t& timer) @@ -157,7 +156,7 @@ bool line_segment_search_t::search_line_segment( } f_t best_feasible_cost = std::numeric_limits::max(); bool initial_is_feasible = solution.get_feasible(); - middle_first_iterator_t it(n_points_to_search); + middle_first_iterator_t it(settings.n_points_to_search); int i; while (it.next(i)) { // make it one indexed @@ -213,14 +212,12 @@ bool line_segment_search_t::search_line_segment( cuopt_func_call(solution.test_variable_bounds(false)); // do the search here fj.settings.mode = fj_mode_t::EXIT_NON_IMPROVING; - fj.settings.termination = fj_termination_flags_t::FJ_TERMINATION_ITERATION_LIMIT; - fj.settings.n_of_minimums_for_exit = 50; - fj.settings.iteration_limit = - std::max(20 * fj.settings.n_of_minimums_for_exit, solution.problem_ptr->n_constraints / 50); - fj.settings.update_weights = false; - fj.settings.feasibility_run = is_feasibility_run; - fj.settings.time_limit = std::min(1., timer.remaining_time()); - is_feasible = fj.solve(solution); + fj.settings.n_of_minimums_for_exit = settings.n_local_min; + fj.settings.iteration_limit = settings.iteration_limit; + fj.settings.update_weights = false; + fj.settings.feasibility_run = is_feasibility_run; + fj.settings.time_limit = std::min(1., timer.remaining_time()); + is_feasible = fj.solve(solution); if (is_feasibility_run) { if (is_feasible) { CUOPT_LOG_DEBUG("Line segment found feasible"); @@ -275,7 +272,6 @@ template bool line_segment_search_t::search_line_segment(solution_t& solution, const rmm::device_uvector& point_1, const rmm::device_uvector& point_2, - i_t n_points_to_search, bool is_feasibility_run, cuopt::timer_t& timer) { @@ -294,14 +290,14 @@ bool line_segment_search_t::search_line_segment(solution_t& delta_vector.begin(), [] __device__(const f_t a, const f_t b) { return a - b; }); - thrust::transform( - solution.handle_ptr->get_thrust_policy(), - delta_vector.begin(), - delta_vector.end(), - delta_vector.begin(), - [n_points_to_search] __device__(const f_t x) { return x / (n_points_to_search + 1); }); - return search_line_segment( - solution, point_1, point_2, n_points_to_search, delta_vector, is_feasibility_run, timer); + thrust::transform(solution.handle_ptr->get_thrust_policy(), + delta_vector.begin(), + delta_vector.end(), + delta_vector.begin(), + [n_points = settings.n_points_to_search] __device__(const f_t x) { + return x / (n_points + 1); + }); + return search_line_segment(solution, point_1, point_2, delta_vector, is_feasibility_run, timer); } #if MIP_INSTANTIATE_FLOAT diff --git a/cpp/src/mip/local_search/line_segment_search/line_segment_search.cuh b/cpp/src/mip/local_search/line_segment_search/line_segment_search.cuh index a7ee6ef155..583cc9fb83 100644 --- a/cpp/src/mip/local_search/line_segment_search/line_segment_search.cuh +++ b/cpp/src/mip/local_search/line_segment_search/line_segment_search.cuh @@ -27,6 +27,9 @@ struct line_segment_settings_t { bool recombiner_mode = false; double best_of_parents_cost = std::numeric_limits::max(); bool parents_infeasible = false; + int n_local_min = 50; + int iteration_limit = 20 * n_local_min; + int n_points_to_search = 5; }; template @@ -37,14 +40,12 @@ class line_segment_search_t { bool search_line_segment(solution_t& solution, const rmm::device_uvector& point_1, const rmm::device_uvector& point_2, - i_t n_points_to_search, bool is_feasibility_run, cuopt::timer_t& timer); bool search_line_segment(solution_t& solution, const rmm::device_uvector& point_1, const rmm::device_uvector& point_2, - i_t n_points_to_search, const rmm::device_uvector& delta_vector, bool is_feasibility_run, 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 655b07fa9d..711612550b 100644 --- a/cpp/src/mip/local_search/local_search.cu +++ b/cpp/src/mip/local_search/local_search.cu @@ -65,7 +65,6 @@ void local_search_t::generate_fast_solution(solution_t& solu fj.settings.n_of_minimums_for_exit = 500; fj.settings.update_weights = true; fj.settings.feasibility_run = true; - fj.settings.termination = fj_termination_flags_t::FJ_TERMINATION_TIME_LIMIT; fj.settings.time_limit = std::min(30., timer.remaining_time()); while (!timer.check_time_limit()) { timer_t constr_prop_timer = timer_t(std::min(timer.remaining_time(), 2.)); @@ -86,15 +85,14 @@ template bool local_search_t::run_local_search(solution_t& solution, const weight_t& weights, timer_t timer, - f_t baseline_objective, - bool at_least_one_parent_feasible) + const ls_config_t& ls_config) { 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 (at_least_one_parent_feasible) { + if (ls_config.at_least_one_parent_feasible) { fj_settings.time_limit = 1.; timer = timer_t(1.); } else { @@ -102,7 +100,7 @@ bool local_search_t::run_local_search(solution_t& solution, timer = timer_t(0.5); } } else { - fj_settings.time_limit = timer.remaining_time(); + fj_settings.time_limit = std::min(10., timer.remaining_time()); } fj_settings.update_weights = false; fj_settings.feasibility_run = false; @@ -110,10 +108,15 @@ bool local_search_t::run_local_search(solution_t& solution, fj.copy_weights(weights, solution.handle_ptr); bool is_feas; i_t rd = std::uniform_int_distribution(0, 1)(rng); - if (rd == 0 && lp_optimal_exists) { - is_feas = run_fj_line_segment(solution, timer); + if (ls_config.ls_method == ls_method_t::FJ_LINE_SEGMENT) { + rd = ls_method_t::FJ_LINE_SEGMENT; + } else if (ls_config.ls_method == ls_method_t::FJ_ANNEALING) { + rd = ls_method_t::FJ_ANNEALING; + } + if (rd == ls_method_t::FJ_LINE_SEGMENT && lp_optimal_exists) { + is_feas = run_fj_line_segment(solution, timer, ls_config); } else { - is_feas = run_fj_annealing(solution, timer, baseline_objective); + is_feas = run_fj_annealing(solution, timer, ls_config); } return is_feas; } @@ -141,21 +144,19 @@ bool local_search_t::run_fj_until_timer(solution_t& solution template bool local_search_t::run_fj_annealing(solution_t& solution, timer_t timer, - f_t baseline_objective) + const ls_config_t& ls_config) { auto prev_settings = fj.settings; // run in FEASIBLE_FIRST to priorize feasibility-improving moves - fj.settings.n_of_minimums_for_exit = 250; - fj.settings.mode = fj_mode_t::EXIT_NON_IMPROVING; - fj.settings.candidate_selection = fj_candidate_selection_t::FEASIBLE_FIRST; - fj.settings.termination = fj_termination_flags_t::FJ_TERMINATION_ITERATION_LIMIT; - fj.settings.iteration_limit = - max(20 * fj.settings.n_of_minimums_for_exit, solution.problem_ptr->n_constraints / 50); + fj.settings.n_of_minimums_for_exit = ls_config.n_local_mins; + fj.settings.mode = fj_mode_t::EXIT_NON_IMPROVING; + fj.settings.candidate_selection = fj_candidate_selection_t::FEASIBLE_FIRST; + fj.settings.iteration_limit = ls_config.iteration_limit; fj.settings.time_limit = std::min(10., timer.remaining_time()); fj.settings.parameters.allow_infeasibility_iterations = 100; fj.settings.update_weights = 1; - fj.settings.baseline_objective_for_longer_run = baseline_objective; + fj.settings.baseline_objective_for_longer_run = ls_config.best_objective_of_parents; fj.solve(solution); bool is_feasible = solution.compute_feasibility(); @@ -164,13 +165,21 @@ bool local_search_t::run_fj_annealing(solution_t& solution, } template -bool local_search_t::run_fj_line_segment(solution_t& solution, timer_t timer) +bool local_search_t::run_fj_line_segment(solution_t& solution, + timer_t timer, + const ls_config_t& ls_config) { rmm::device_uvector starting_point(solution.assignment, solution.handle_ptr->get_stream()); + line_segment_search.settings.best_of_parents_cost = ls_config.best_objective_of_parents; + line_segment_search.settings.parents_infeasible = !ls_config.at_least_one_parent_feasible; + line_segment_search.settings.recombiner_mode = false; + line_segment_search.settings.n_local_min = ls_config.n_local_mins_for_line_segment; + line_segment_search.settings.n_points_to_search = ls_config.n_points_to_search_for_line_segment; + line_segment_search.settings.iteration_limit = ls_config.iteration_limit_for_line_segment; + bool feas = line_segment_search.search_line_segment(solution, starting_point, lp_optimal_solution, - /*n_points_to_search=*/5, /*feasibility_run=*/false, timer); return feas; @@ -210,7 +219,6 @@ bool local_search_t::check_fj_on_lp_optimal(solution_t& solu fj.settings.n_of_minimums_for_exit = 20000; fj.settings.update_weights = true; fj.settings.feasibility_run = true; - fj.settings.termination = fj_termination_flags_t::FJ_TERMINATION_TIME_LIMIT; fj.settings.time_limit = std::min(30., timer.remaining_time()); fj.solve(solution); return solution.get_feasible(); @@ -228,7 +236,6 @@ bool local_search_t::run_fj_on_zero(solution_t& solution, ti fj.settings.n_of_minimums_for_exit = 20000; fj.settings.update_weights = true; fj.settings.feasibility_run = true; - fj.settings.termination = fj_termination_flags_t::FJ_TERMINATION_TIME_LIMIT; fj.settings.time_limit = std::min(30., timer.remaining_time()); bool is_feasible = fj.solve(solution); return is_feasible; diff --git a/cpp/src/mip/local_search/local_search.cuh b/cpp/src/mip/local_search/local_search.cuh index 4334f10637..fdb3ff5803 100644 --- a/cpp/src/mip/local_search/local_search.cuh +++ b/cpp/src/mip/local_search/local_search.cuh @@ -25,6 +25,20 @@ namespace cuopt::linear_programming::detail { +enum ls_method_t { FJ_ANNEALING = 0, FJ_LINE_SEGMENT, RANDOM }; + +template +struct ls_config_t { + bool at_least_one_parent_feasible{true}; + f_t best_objective_of_parents{std::numeric_limits::lowest()}; + i_t n_local_mins_for_line_segment = 50; + i_t n_points_to_search_for_line_segment = 5; + i_t n_local_mins = 250; + i_t iteration_limit_for_line_segment = 20 * n_local_mins_for_line_segment; + i_t iteration_limit = 20 * n_local_mins; + ls_method_t ls_method = ls_method_t::RANDOM; +}; + template class local_search_t { public: @@ -42,12 +56,13 @@ class local_search_t { bool run_local_search(solution_t& solution, const weight_t& weights, timer_t timer, - f_t baseline_objective = std::numeric_limits::lowest(), - bool at_least_one_parent_feasible = true); + const ls_config_t& ls_config); bool run_fj_annealing(solution_t& solution, timer_t timer, - f_t baseline_objective = std::numeric_limits::lowest()); - bool run_fj_line_segment(solution_t& solution, timer_t timer); + const ls_config_t& ls_config); + bool run_fj_line_segment(solution_t& solution, + timer_t timer, + const ls_config_t& ls_config); bool run_fj_on_zero(solution_t& solution, timer_t timer); bool check_fj_on_lp_optimal(solution_t& solution, bool perturb, timer_t timer); bool run_staged_fp(solution_t& solution, timer_t timer, bool& early_exit); diff --git a/cpp/src/mip/local_search/rounding/constraint_prop.cu b/cpp/src/mip/local_search/rounding/constraint_prop.cu index 33a631d298..e557d3ba81 100644 --- a/cpp/src/mip/local_search/rounding/constraint_prop.cu +++ b/cpp/src/mip/local_search/rounding/constraint_prop.cu @@ -661,6 +661,16 @@ constraint_prop_t::generate_bulk_rounding_vector( cuda::std::tie(first_probe, second_probe) = generate_double_probing_pair(sol, orig_sol, unset_var_idx, probing_config, false); } + cuopt_assert(orig_sol.problem_ptr->variable_lower_bounds.element( + unset_var_idx, sol.handle_ptr->get_stream()) <= first_probe + int_tol && + first_probe - int_tol <= orig_sol.problem_ptr->variable_upper_bounds.element( + unset_var_idx, sol.handle_ptr->get_stream()), + "Variable out of original bounds!"); + cuopt_assert(orig_sol.problem_ptr->variable_lower_bounds.element( + unset_var_idx, sol.handle_ptr->get_stream()) <= second_probe + int_tol && + second_probe - int_tol <= orig_sol.problem_ptr->variable_upper_bounds.element( + unset_var_idx, sol.handle_ptr->get_stream()), + "Variable out of original bounds!"); cuopt_assert(orig_sol.problem_ptr->is_integer(first_probe), "Probing value must be an integer"); cuopt_assert(orig_sol.problem_ptr->is_integer(second_probe), "Probing value must be an integer"); diff --git a/cpp/src/mip/problem/problem.cu b/cpp/src/mip/problem/problem.cu index ea162cdfba..888388ef18 100644 --- a/cpp/src/mip/problem/problem.cu +++ b/cpp/src/mip/problem/problem.cu @@ -47,6 +47,8 @@ #include +#include + namespace cuopt::linear_programming::detail { template diff --git a/cpp/src/mip/relaxed_lp/relaxed_lp.cu b/cpp/src/mip/relaxed_lp/relaxed_lp.cu index 604f759df2..d7a337dd28 100644 --- a/cpp/src/mip/relaxed_lp/relaxed_lp.cu +++ b/cpp/src/mip/relaxed_lp/relaxed_lp.cu @@ -55,6 +55,7 @@ optimization_problem_solution_t get_relaxed_lp_solution( pdlp_settings.tolerances.relative_primal_tolerance = settings.tolerance / 100.; pdlp_settings.tolerances.relative_dual_tolerance = settings.tolerance / 100.; pdlp_settings.time_limit = settings.time_limit; + pdlp_settings.concurrent_halt = settings.concurrent_halt; if (settings.return_first_feasible) { pdlp_settings.per_constraint_residual = true; } pdlp_settings.first_primal_feasible = settings.return_first_feasible; pdlp_solver_t lp_solver(op_problem, pdlp_settings); diff --git a/cpp/src/mip/relaxed_lp/relaxed_lp.cuh b/cpp/src/mip/relaxed_lp/relaxed_lp.cuh index def1b7fa2c..a5fe23adb8 100644 --- a/cpp/src/mip/relaxed_lp/relaxed_lp.cuh +++ b/cpp/src/mip/relaxed_lp/relaxed_lp.cuh @@ -27,12 +27,13 @@ namespace cuopt::linear_programming::detail { struct relaxed_lp_settings_t { - double tolerance = 1e-4; - double time_limit = 1.0; - bool check_infeasibility = true; - bool return_first_feasible = false; - bool save_state = true; - bool per_constraint_residual = false; + double tolerance = 1e-4; + double time_limit = 1.0; + bool check_infeasibility = true; + bool return_first_feasible = false; + bool save_state = true; + bool per_constraint_residual = false; + std::atomic* concurrent_halt = nullptr; }; template diff --git a/cpp/src/mip/solver.cu b/cpp/src/mip/solver.cu index 9db407c65a..a60aa77b5a 100644 --- a/cpp/src/mip/solver.cu +++ b/cpp/src/mip/solver.cu @@ -64,17 +64,22 @@ mip_solver_t::mip_solver_t(const problem_t& op_problem, template struct branch_and_bound_solution_helper_t { - branch_and_bound_solution_helper_t(population_t* population, + branch_and_bound_solution_helper_t(diversity_manager_t* dm, dual_simplex::simplex_solver_settings_t& settings) - : population_ptr(population), settings_(settings) {}; + : dm(dm), settings_(settings) {}; void solution_callback(std::vector& solution, f_t objective) { - population_ptr->add_external_solution(solution, objective); + dm->population.add_external_solution(solution, objective); } - void preempt_heuristic_solver() { population_ptr->preempt_heuristic_solver(); } - population_t* population_ptr; + void set_simplex_solution(std::vector& solution, f_t objective) + { + dm->set_simplex_solution(solution, objective); + } + + void preempt_heuristic_solver() { dm->population.preempt_heuristic_solver(); } + diversity_manager_t* dm; dual_simplex::simplex_solver_settings_t& settings_; }; @@ -124,8 +129,7 @@ solution_t mip_solver_t::run_solver() dual_simplex::user_problem_t branch_and_bound_problem; dual_simplex::simplex_solver_settings_t branch_and_bound_settings; std::unique_ptr> branch_and_bound; - branch_and_bound_solution_helper_t solution_helper(dm.get_population_pointer(), - branch_and_bound_settings); + branch_and_bound_solution_helper_t solution_helper(&dm, branch_and_bound_settings); dual_simplex::mip_solution_t branch_and_bound_solution(1); if (!context.settings.heuristics_only) { @@ -153,6 +157,13 @@ solution_t mip_solver_t::run_solver() std::placeholders::_2); branch_and_bound_settings.heuristic_preemption_callback = std::bind( &branch_and_bound_solution_helper_t::preempt_heuristic_solver, &solution_helper); + + branch_and_bound_settings.set_simplex_solution_callback = + std::bind(&branch_and_bound_solution_helper_t::set_simplex_solution, + &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); diff --git a/cpp/tests/mip/feasibility_jump_tests.cu b/cpp/tests/mip/feasibility_jump_tests.cu index b6eda2853e..740f18ad61 100644 --- a/cpp/tests/mip/feasibility_jump_tests.cu +++ b/cpp/tests/mip/feasibility_jump_tests.cu @@ -139,8 +139,7 @@ static bool run_fj_check_no_obj_runoff(std::string test_instance) fj_settings.n_of_minimums_for_exit = 20000 * 1000; fj_settings.update_weights = true; fj_settings.feasibility_run = false; - fj_settings.termination = detail::fj_termination_flags_t::FJ_TERMINATION_ITERATION_LIMIT; - fj_settings.iteration_limit = 20000; + fj_settings.iteration_limit = 20000; auto state = run_fj(test_instance, fj_settings); @@ -162,8 +161,7 @@ static bool run_fj_check_objective(std::string test_instance, int iter_limit, do fj_settings.n_of_minimums_for_exit = 20000 * 1000; fj_settings.update_weights = true; fj_settings.feasibility_run = obj_target == +std::numeric_limits::infinity(); - fj_settings.termination = detail::fj_termination_flags_t::FJ_TERMINATION_ITERATION_LIMIT; - fj_settings.iteration_limit = iter_limit; + fj_settings.iteration_limit = iter_limit; auto state = run_fj(test_instance, fj_settings); auto& solution = state.solution; @@ -190,8 +188,7 @@ static bool run_fj_check_feasible(std::string test_instance) fj_settings.n_of_minimums_for_exit = 20000 * 1000; fj_settings.update_weights = true; fj_settings.feasibility_run = false; - fj_settings.termination = detail::fj_termination_flags_t::FJ_TERMINATION_ITERATION_LIMIT; - fj_settings.iteration_limit = 25000; + fj_settings.iteration_limit = 25000; auto state = run_fj(test_instance, fj_settings); auto& solution = state.solution; diff --git a/python/cuopt/cuopt/tests/linear_programming/test_lp_solver.py b/python/cuopt/cuopt/tests/linear_programming/test_lp_solver.py index ed0d04ec19..f0944acdc2 100644 --- a/python/cuopt/cuopt/tests/linear_programming/test_lp_solver.py +++ b/python/cuopt/cuopt/tests/linear_programming/test_lp_solver.py @@ -475,6 +475,9 @@ def test_parse_var_names(): ) +@pytest.mark.skip( + reason="Intermittent failure, new version is being worked on" +) def test_parser_and_batch_solver(): data_model_list = [] @@ -564,6 +567,9 @@ def test_warm_start_other_problem(): solver.Solve(data_model_obj2, settings) +@pytest.mark.skip( + reason="Intermittent failure, new version is being worked on" +) def test_batch_solver_warm_start(): data_model_list = []