diff --git a/cpp/include/cuopt/logger.hpp b/cpp/include/cuopt/logger.hpp index ed0877d4e..0d1cc353c 100644 --- a/cpp/include/cuopt/logger.hpp +++ b/cpp/include/cuopt/logger.hpp @@ -74,6 +74,7 @@ inline rapids_logger::logger& default_logger() rapids_logger::logger logger_{"CUOPT", {default_sink()}}; logger_.set_pattern(default_pattern()); logger_.set_level(default_level()); + logger_.flush_on(rapids_logger::level_enum::info); return logger_; }(); @@ -91,6 +92,7 @@ inline void reset_default_logger() default_logger().sinks().push_back(default_sink()); default_logger().set_pattern(default_pattern()); default_logger().set_level(default_level()); + default_logger().flush_on(rapids_logger::level_enum::info); } } // namespace cuopt diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index 72f6c2f21..153175fc0 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -147,16 +147,12 @@ void diversity_manager_t::add_user_given_solution( if (context.settings.has_initial_solution()) { solution_t sol(*problem_ptr); auto& init_sol = context.settings.get_initial_solution(); - if (sol.assignment.size() <= init_sol.size()) { - thrust::for_each( - sol.handle_ptr->get_thrust_policy(), - thrust::make_counting_iterator(0), - thrust::make_counting_iterator((int)sol.assignment.size()), - [assgn = make_span(sol.assignment), - init_sol = make_span(init_sol), - var_map = make_span(problem_ptr->presolve_data.variable_mapping)] __device__(i_t i) { - assgn[i] = init_sol[var_map[i]]; - }); + rmm::device_uvector init_sol_assignment(init_sol, sol.handle_ptr->get_stream()); + if (problem_ptr->pre_process_assignment(init_sol_assignment)) { + raft::copy(sol.assignment.data(), + init_sol_assignment.data(), + init_sol_assignment.size(), + sol.handle_ptr->get_stream()); bool is_feasible = sol.compute_feasibility(); cuopt_func_call(sol.test_variable_bounds(true)); CUOPT_LOG_INFO("Adding initial solution success! feas %d objective %f excess %f", @@ -168,8 +164,8 @@ void diversity_manager_t::add_user_given_solution( } else { CUOPT_LOG_ERROR( "Error cannot add the provided initial solution! \ - Assignment size %lu \ - initial solution size %lu", + Assignment size %lu \ + initial solution size %lu", sol.assignment.size(), init_sol.size()); } @@ -250,14 +246,16 @@ bool diversity_manager_t::run_presolve(f_t time_limit) if (termination_criterion_t::NO_UPDATE != term_crit) { ls.constraint_prop.bounds_update.set_updated_bounds(*problem_ptr); trivial_presolve(*problem_ptr); + if (!problem_ptr->empty) { check_bounds_sanity(*problem_ptr); } + } + if (!problem_ptr->empty) { + // do the resizing no-matter what, bounds presolve might not change the bounds but initial + // trivial presolve might have + ls.constraint_prop.bounds_update.resize(*problem_ptr); + ls.constraint_prop.conditional_bounds_update.update_constraint_bounds( + *problem_ptr, ls.constraint_prop.bounds_update); check_bounds_sanity(*problem_ptr); } - // do the resizing no-matter what, bounds presolve might not change the bounds but initial trivial - // presolve might have - ls.constraint_prop.bounds_update.resize(*problem_ptr); - ls.constraint_prop.conditional_bounds_update.update_constraint_bounds( - *problem_ptr, ls.constraint_prop.bounds_update); - check_bounds_sanity(*problem_ptr); stats.presolve_time = presolve_timer.elapsed_time(); return true; } diff --git a/cpp/src/mip/diversity/population.cu b/cpp/src/mip/diversity/population.cu index 23b1f745e..0780e77ce 100644 --- a/cpp/src/mip/diversity/population.cu +++ b/cpp/src/mip/diversity/population.cu @@ -163,7 +163,6 @@ void population_t::run_solution_callbacks(solution_t& sol) ? sol.get_user_objective() > best_feasible_objective : sol.get_user_objective() < best_feasible_objective; auto user_callbacks = context.settings.get_mip_callbacks(); - if (better_solution_found && sol.get_feasible()) { CUOPT_LOG_DEBUG("Population: Found new best solution %g", sol.get_user_objective()); best_feasible_objective = sol.get_user_objective(); @@ -174,19 +173,29 @@ void population_t::run_solution_callbacks(solution_t& sol) for (auto callback : user_callbacks) { if (callback->get_type() == internals::base_solution_callback_type::GET_SOLUTION) { auto get_sol_callback = static_cast(callback); - rmm::device_uvector incumbent_assignment(sol.assignment, sol.handle_ptr->get_stream()); - problem_ptr->post_process_assignment(incumbent_assignment); - rmm::device_uvector dummy(0, sol.handle_ptr->get_stream()); + solution_t temp_sol(sol); + problem_ptr->post_process_assignment(temp_sol.assignment); + rmm::device_uvector dummy(0, temp_sol.handle_ptr->get_stream()); if (context.settings.mip_scaling) { - context.scaling.unscale_solutions(incumbent_assignment, dummy); + context.scaling.unscale_solutions(temp_sol.assignment, dummy); + // Need to get unscaled problem as well + problem_t n_problem(*sol.problem_ptr->original_problem_ptr); + temp_sol.problem_ptr = &n_problem; + temp_sol.resize_to_original_problem(); + temp_sol.compute_feasibility(); + if (!temp_sol.get_feasible()) { + CUOPT_LOG_DEBUG("Discard infeasible after unscaling"); + return; + } } - rmm::device_uvector user_objective_vec(1, sol.handle_ptr->get_stream()); + rmm::device_uvector user_objective_vec(1, temp_sol.handle_ptr->get_stream()); - f_t user_objective = sol.problem_ptr->get_user_obj_from_solver_obj(sol.get_objective()); - user_objective_vec.set_element_async(0, user_objective, sol.handle_ptr->get_stream()); + f_t user_objective = + temp_sol.problem_ptr->get_user_obj_from_solver_obj(temp_sol.get_objective()); + user_objective_vec.set_element_async(0, user_objective, temp_sol.handle_ptr->get_stream()); CUOPT_LOG_DEBUG("Returning incumbent solution with objective %g", user_objective); - get_sol_callback->get_solution(incumbent_assignment.data(), user_objective_vec.data()); + get_sol_callback->get_solution(temp_sol.assignment.data(), user_objective_vec.data()); } } } @@ -199,10 +208,15 @@ void population_t::run_solution_callbacks(solution_t& sol) rmm::device_uvector dummy(0, sol.handle_ptr->get_stream()); solution_t outside_sol(sol); rmm::device_scalar d_outside_sol_objective(sol.handle_ptr->get_stream()); + auto inf = std::numeric_limits::infinity(); + d_outside_sol_objective.set_value_async(inf, sol.handle_ptr->get_stream()); + sol.handle_ptr->sync_stream(); set_sol_callback->set_solution(incumbent_assignment.data(), d_outside_sol_objective.data()); f_t outside_sol_objective = d_outside_sol_objective.value(sol.handle_ptr->get_stream()); - + // The callback might be called without setting any valid solution or objective which triggers + // asserts + if (outside_sol_objective == inf) { return; } CUOPT_LOG_DEBUG("Injecting external solution with objective %g", outside_sol_objective); if (context.settings.mip_scaling) { @@ -210,21 +224,19 @@ void population_t::run_solution_callbacks(solution_t& sol) } bool is_valid = problem_ptr->pre_process_assignment(incumbent_assignment); if (!is_valid) { return; } - cuopt_assert(outside_sol.assignment.size() == incumbent_assignment.size(), "Incumbent assignment size mismatch"); raft::copy(outside_sol.assignment.data(), incumbent_assignment.data(), incumbent_assignment.size(), sol.handle_ptr->get_stream()); - outside_sol.compute_feasibility(); - CUOPT_LOG_DEBUG("Injected solution feasibility = %d", outside_sol.get_feasible()); + CUOPT_LOG_DEBUG("Injected solution feasibility = %d objective = %g", + outside_sol.get_feasible(), + outside_sol.get_user_objective()); - cuopt_assert(std::abs(outside_sol.problem_ptr->get_user_obj_from_solver_obj( - outside_sol.get_objective()) - - outside_sol_objective) <= 1e-6, + cuopt_assert(std::abs(outside_sol.get_user_objective() - outside_sol_objective) <= 1e-6, "External solution objective mismatch"); auto h_outside_sol = outside_sol.get_host_assignment(); add_external_solution(h_outside_sol, outside_sol.get_objective()); diff --git a/cpp/src/mip/presolve/trivial_presolve.cuh b/cpp/src/mip/presolve/trivial_presolve.cuh index 495a191fa..d7cfa6dc1 100644 --- a/cpp/src/mip/presolve/trivial_presolve.cuh +++ b/cpp/src/mip/presolve/trivial_presolve.cuh @@ -277,8 +277,9 @@ void update_from_csr(problem_t& pb) pb.n_constraints = updated_n_cnst; pb.n_variables = updated_n_vars; - CUOPT_LOG_INFO( - "After trivial presolve updated %d constraints %d variables", updated_n_cnst, updated_n_vars); + CUOPT_LOG_INFO("After trivial presolve updated number of %d constraints %d variables", + updated_n_cnst, + updated_n_vars); // check successive cnst in coo increases by atmost 1 // update csr offset pb.offsets.resize(pb.n_constraints + 1, handle_ptr->get_stream()); diff --git a/cpp/src/mip/problem/problem.cu b/cpp/src/mip/problem/problem.cu index 174d6af58..5826fee48 100644 --- a/cpp/src/mip/problem/problem.cu +++ b/cpp/src/mip/problem/problem.cu @@ -593,42 +593,42 @@ bool problem_t::pre_process_assignment(rmm::device_uvector& assig return false; } - // Map assignment to internal solution using variable mapping - rmm::device_uvector internal_assignment(presolve_data.variable_mapping.size(), - handle_ptr->get_stream()); - thrust::gather(handle_ptr->get_thrust_policy(), - presolve_data.variable_mapping.begin(), - presolve_data.variable_mapping.end(), - assignment.begin(), - internal_assignment.begin()); - + // create a temp assignment with the var size after bounds standardization (free vars added) + rmm::device_uvector temp_assignment(presolve_data.additional_var_used.size(), + handle_ptr->get_stream()); + // copy the assignment to the first part(the original variable count) of the temp_assignment + raft::copy( + temp_assignment.data(), assignment.data(), assignment.size(), handle_ptr->get_stream()); auto d_additional_var_used = cuopt::device_copy(presolve_data.additional_var_used, handle_ptr->get_stream()); auto d_additional_var_id_per_var = cuopt::device_copy(presolve_data.additional_var_id_per_var, handle_ptr->get_stream()); - thrust::for_each( - handle_ptr->get_thrust_policy(), - thrust::make_counting_iterator(0), - thrust::make_counting_iterator(presolve_data.variable_mapping.size()), - [additional_var_used = d_additional_var_used.data(), - additional_var_id_per_var = d_additional_var_id_per_var.data(), - assgn = internal_assignment.data()] __device__(auto idx) { - if (additional_var_used[idx]) { - cuopt_assert(additional_var_id_per_var[idx] != -1, "additional_var_id_per_var is not set"); - // We have two non-negative variables y and z that simulate a free variable x. If the value - // of x is negative, we can set z to be something higher than y. If the value of x is - // positive we can set y greater than z - assgn[additional_var_id_per_var[idx]] = assgn[idx] < 0 ? -assgn[idx] : assgn[idx]; - assgn[idx] += assgn[additional_var_id_per_var[idx]]; - } - }); - assignment.resize(internal_assignment.size(), handle_ptr->get_stream()); + // handle free var logic by substituting the free vars and their corresponding vars + thrust::for_each(handle_ptr->get_thrust_policy(), + thrust::make_counting_iterator(0), + thrust::make_counting_iterator(original_problem_ptr->get_n_variables()), + [additional_var_used = d_additional_var_used.data(), + additional_var_id_per_var = d_additional_var_id_per_var.data(), + assgn = temp_assignment.data()] __device__(auto idx) { + if (additional_var_used[idx]) { + cuopt_assert(additional_var_id_per_var[idx] != -1, + "additional_var_id_per_var is not set"); + // We have two non-negative variables y and z that simulate a free variable + // x. If the value of x is negative, we can set z to be something higher than + // y. If the value of x is positive we can set y greater than z + assgn[additional_var_id_per_var[idx]] = (assgn[idx] < 0 ? -assgn[idx] : 0.); + assgn[idx] += assgn[additional_var_id_per_var[idx]]; + } + }); + assignment.resize(n_variables, handle_ptr->get_stream()); assignment.shrink_to_fit(handle_ptr->get_stream()); - raft::copy(assignment.data(), - internal_assignment.data(), - internal_assignment.size(), - handle_ptr->get_stream()); + cuopt_assert(presolve_data.variable_mapping.size() == n_variables, "size mismatch"); + thrust::gather(handle_ptr->get_thrust_policy(), + presolve_data.variable_mapping.begin(), + presolve_data.variable_mapping.end(), + temp_assignment.begin(), + assignment.begin()); handle_ptr->sync_stream(); return true; } @@ -643,11 +643,14 @@ void problem_t::post_process_assignment(rmm::device_uvector& curr auto assgn = make_span(current_assignment); auto fixed_assgn = make_span(presolve_data.fixed_var_assignment); auto var_map = make_span(presolve_data.variable_mapping); - thrust::for_each( - handle_ptr->get_thrust_policy(), - thrust::make_counting_iterator(0), - thrust::make_counting_iterator(current_assignment.size()), - [fixed_assgn, var_map, assgn] __device__(auto idx) { fixed_assgn[var_map[idx]] = assgn[idx]; }); + if (current_assignment.size() > 0) { + thrust::for_each(handle_ptr->get_thrust_policy(), + thrust::make_counting_iterator(0), + thrust::make_counting_iterator(current_assignment.size()), + [fixed_assgn, var_map, assgn] __device__(auto idx) { + fixed_assgn[var_map[idx]] = assgn[idx]; + }); + } expand_device_copy( current_assignment, presolve_data.fixed_var_assignment, handle_ptr->get_stream()); auto h_assignment = cuopt::host_copy(current_assignment, handle_ptr->get_stream()); diff --git a/cpp/src/mip/solver.cu b/cpp/src/mip/solver.cu index 4aded0a08..24cdc7d1d 100644 --- a/cpp/src/mip/solver.cu +++ b/cpp/src/mip/solver.cu @@ -96,7 +96,7 @@ solution_t mip_solver_t::run_solver() "preprocess_problem should be called before running the solver"); if (context.problem_ptr->empty) { - CUOPT_LOG_INFO("Problem fully reduced at presolve"); + CUOPT_LOG_INFO("Problem fully reduced at trivial presolve"); solution_t sol(*context.problem_ptr); context.problem_ptr->post_process_solution(sol); return sol; @@ -112,6 +112,12 @@ solution_t mip_solver_t::run_solver() context.problem_ptr->post_process_solution(sol); return sol; } + if (context.problem_ptr->empty) { + CUOPT_LOG_INFO("Problem fully reduced at presolve"); + solution_t sol(*context.problem_ptr); + context.problem_ptr->post_process_solution(sol); + return sol; + } namespace dual_simplex = cuopt::linear_programming::dual_simplex; std::future branch_and_bound_status_future; diff --git a/cpp/tests/mip/incumbent_callback_test.cu b/cpp/tests/mip/incumbent_callback_test.cu index 35481256b..f45efca72 100644 --- a/cpp/tests/mip/incumbent_callback_test.cu +++ b/cpp/tests/mip/incumbent_callback_test.cu @@ -52,14 +52,16 @@ class test_set_solution_callback_t : public cuopt::internals::set_solution_callb // This will check that the we are able to recompute our own solution void set_solution(void* data, void* cost) override { + n_calls++; rmm::cuda_stream_view stream{}; - auto assignment = static_cast(data); - auto cost_ptr = static_cast(cost); + auto assignment = static_cast(data); + auto cost_ptr = static_cast(cost); + if (solutions.empty()) { return; } + auto const& [last_assignment, last_cost] = solutions.back(); raft::copy(assignment, last_assignment.data(), last_assignment.size(), stream); raft::copy(cost_ptr, &last_cost, 1, stream); stream.synchronize(); - n_calls++; } std::vector, double>>& solutions; int n_calls; @@ -74,6 +76,7 @@ class test_get_solution_callback_t : public cuopt::internals::get_solution_callb } void get_solution(void* data, void* cost) override { + n_calls++; rmm::cuda_stream_view stream{}; rmm::device_uvector assignment(n_variables, stream); raft::copy(assignment.data(), static_cast(data), n_variables, stream); @@ -81,7 +84,6 @@ class test_get_solution_callback_t : public cuopt::internals::get_solution_callb raft::copy(&h_cost, static_cast(cost), 1, stream); stream.synchronize(); solutions.push_back(std::make_pair(std::move(assignment), h_cost)); - n_calls++; } std::vector, double>>& solutions; int n_calls; @@ -131,7 +133,8 @@ void test_incumbent_callback(std::string test_instance) TEST(mip_solve, incumbent_callback_test) { - std::vector test_instances = {"mip/50v-10.mps", "mip/neos5.mps", "mip/swath1.mps"}; + std::vector test_instances = { + "mip/50v-10.mps", "mip/neos5-free-bound.mps", "mip/swath1.mps"}; for (const auto& test_instance : test_instances) { test_incumbent_callback(test_instance); } diff --git a/cpp/tests/mip/server_test.cu b/cpp/tests/mip/server_test.cu index ce3499e3d..cf8268b9f 100644 --- a/cpp/tests/mip/server_test.cu +++ b/cpp/tests/mip/server_test.cu @@ -15,7 +15,7 @@ * limitations under the License. */ -#include "../utilities/pdlp_test_utilities.cuh" +#include "../linear_programming/utilities/pdlp_test_utilities.cuh" #include "mip_utils.cuh" #include