From f271edc102ad46ee036afc90cc306d1b21faba07 Mon Sep 17 00:00:00 2001 From: akifcorduk Date: Mon, 19 May 2025 13:02:58 -0700 Subject: [PATCH 1/7] wip --- cpp/include/cuopt/logger.hpp | 2 + cpp/include/cuopt/semantic_version.hpp | 22 +++ cpp/src/mip/diversity/population.cu | 56 ++++++- cpp/tests/linear_programming/CMakeLists.txt | 33 ---- .../mip_unit_tests/incumbent_callback_test.cu | 102 ------------- .../bounds_standardization_test.cu | 2 +- .../doc_example_test.cu | 2 +- .../elim_var_remap_test.cu | 2 +- .../empty_fixed_problems_test.cu | 2 +- .../feasibility_jump_tests.cu | 2 +- cpp/tests/mip/incumbent_callback_test.cu | 143 ++++++++++++++++++ .../mip_unit_tests => mip}/mip_utils.cuh | 0 .../mip_unit_tests => mip}/miplib_test.cu | 2 +- .../multi_probe_test.cu | 2 +- .../mip_unit_tests => mip}/problem_test.cu | 2 +- .../mip_unit_tests => mip}/server_test.cu | 2 +- cpp/tests/mip/termination_test.cu | 103 +++++++++++++ .../mip_unit_tests => mip}/unit_test.cu | 2 +- 18 files changed, 330 insertions(+), 151 deletions(-) create mode 100644 cpp/include/cuopt/semantic_version.hpp delete mode 100644 cpp/tests/linear_programming/mip_unit_tests/incumbent_callback_test.cu rename cpp/tests/{linear_programming/mip_unit_tests => mip}/bounds_standardization_test.cu (98%) rename cpp/tests/{linear_programming/mip_unit_tests => mip}/doc_example_test.cu (98%) rename cpp/tests/{linear_programming/mip_unit_tests => mip}/elim_var_remap_test.cu (99%) rename cpp/tests/{linear_programming/mip_unit_tests => mip}/empty_fixed_problems_test.cu (97%) rename cpp/tests/{linear_programming/mip_unit_tests => mip}/feasibility_jump_tests.cu (99%) create mode 100644 cpp/tests/mip/incumbent_callback_test.cu rename cpp/tests/{linear_programming/mip_unit_tests => mip}/mip_utils.cuh (100%) rename cpp/tests/{linear_programming/mip_unit_tests => mip}/miplib_test.cu (97%) rename cpp/tests/{linear_programming/mip_unit_tests => mip}/multi_probe_test.cu (99%) rename cpp/tests/{linear_programming/mip_unit_tests => mip}/problem_test.cu (99%) rename cpp/tests/{linear_programming/mip_unit_tests => mip}/server_test.cu (98%) create mode 100644 cpp/tests/mip/termination_test.cu rename cpp/tests/{linear_programming/mip_unit_tests => mip}/unit_test.cu (98%) 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/include/cuopt/semantic_version.hpp b/cpp/include/cuopt/semantic_version.hpp new file mode 100644 index 000000000..7d3cac169 --- /dev/null +++ b/cpp/include/cuopt/semantic_version.hpp @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 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 + +#define CUOPT_SEMANTIC_VERSION_MAJOR 0 +#define CUOPT_SEMANTIC_VERSION_MINOR 0 +#define CUOPT_SEMANTIC_VERSION_PATCH 0 diff --git a/cpp/src/mip/diversity/population.cu b/cpp/src/mip/diversity/population.cu index e16bf9a68..58c64a302 100644 --- a/cpp/src/mip/diversity/population.cu +++ b/cpp/src/mip/diversity/population.cu @@ -161,6 +161,7 @@ void population_t::run_solution_callbacks(solution_t& sol) bool better_solution_found = problem_ptr->maximize ? 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(); @@ -168,13 +169,56 @@ void population_t::run_solution_callbacks(solution_t& sol) problem_ptr->branch_and_bound_callback(sol.get_host_assignment()); } - auto incumbent_sol_callback = context.settings.get_incumbent_solution_callback(); - if (incumbent_sol_callback != nullptr) { - rmm::device_uvector incumbent_assignment(sol.assignment, sol.handle_ptr->get_stream()); - problem_ptr->post_process_assignment(incumbent_assignment); + for (auto callback : user_callbacks) { + if (callback->get_type() == internals::base_solution_callback_type::GET_SOLUTION) { + auto get_sol_callback = static_cast(callback); + 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(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, temp_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(temp_sol.assignment.data(), user_objective_vec.data()); + } + } + } + + for (auto callback : user_callbacks) { + if (callback->get_type() == internals::base_solution_callback_type::SET_SOLUTION) { + auto set_sol_callback = static_cast(callback); + rmm::device_uvector incumbent_assignment( + problem_ptr->original_problem_ptr->get_n_variables(), sol.handle_ptr->get_stream()); rmm::device_uvector dummy(0, sol.handle_ptr->get_stream()); - if (context.settings.get_mip_scaling()) { - context.scaling.unscale_solutions(incumbent_assignment, dummy); + 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()); + 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) { + context.scaling.scale_solutions(incumbent_assignment, dummy); } f_t user_objective = sol.problem_ptr->get_user_obj_from_solver_obj(sol.get_objective()); CUOPT_LOG_DEBUG("Returning incumbent solution with objective %g", user_objective); diff --git a/cpp/tests/linear_programming/CMakeLists.txt b/cpp/tests/linear_programming/CMakeLists.txt index 00f445281..66bed4037 100644 --- a/cpp/tests/linear_programming/CMakeLists.txt +++ b/cpp/tests/linear_programming/CMakeLists.txt @@ -21,39 +21,6 @@ ConfigureTest(LP_UNIT_TEST ConfigureTest(PDLP_TEST ${CMAKE_CURRENT_SOURCE_DIR}/pdlp_test.cu ) -# ################################################################################################## -# - MIP tests ---------------------------------------------------------------------- -ConfigureTest(MIP_TEST - ${CMAKE_CURRENT_SOURCE_DIR}/mip_unit_tests/miplib_test.cu -) -ConfigureTest(PROBLEM_TEST - ${CMAKE_CURRENT_SOURCE_DIR}/mip_unit_tests/problem_test.cu -) -ConfigureTest(ELIM_VAR_REMAP_TEST - ${CMAKE_CURRENT_SOURCE_DIR}/mip_unit_tests/elim_var_remap_test.cu -) -ConfigureTest(STANDARDIZATION_TEST - ${CMAKE_CURRENT_SOURCE_DIR}/mip_unit_tests/bounds_standardization_test.cu -) -ConfigureTest(MULTI_PROBE_TEST - ${CMAKE_CURRENT_SOURCE_DIR}/mip_unit_tests/multi_probe_test.cu -) -ConfigureTest(INCUMBENT_CALLBACK_TEST - ${CMAKE_CURRENT_SOURCE_DIR}/mip_unit_tests/incumbent_callback_test.cu -) -ConfigureTest(FEASIBILITY_JUMP_TEST - ${CMAKE_CURRENT_SOURCE_DIR}/mip_unit_tests/feasibility_jump_tests.cu -) -ConfigureTest(DOC_EXAMPLE_TEST - ${CMAKE_CURRENT_SOURCE_DIR}/mip_unit_tests/doc_example_test.cu -) -ConfigureTest(UNIT_TEST - ${CMAKE_CURRENT_SOURCE_DIR}/mip_unit_tests/unit_test.cu -) - -ConfigureTest(EMPTY_FIXED_PROBLEMS_TEST - ${CMAKE_CURRENT_SOURCE_DIR}/mip_unit_tests/empty_fixed_problems_test.cu -) # ################################################################################################## # - C API Tests---------------------------------------------------------------------- diff --git a/cpp/tests/linear_programming/mip_unit_tests/incumbent_callback_test.cu b/cpp/tests/linear_programming/mip_unit_tests/incumbent_callback_test.cu deleted file mode 100644 index 87703386c..000000000 --- a/cpp/tests/linear_programming/mip_unit_tests/incumbent_callback_test.cu +++ /dev/null @@ -1,102 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 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 "../utilities/pdlp_test_utilities.cuh" -#include "mip_utils.cuh" - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include - -#include -#include -#include - -#include -#include -#include -#include -#include - -namespace cuopt::linear_programming::test { - -class test_incumbent_callback_t : public cuopt::internals::lp_incumbent_sol_callback_t { - public: - void set_solution(void* data, size_t size, double cost) override - { - rmm::cuda_stream_view stream{}; - rmm::device_uvector assignment(size, stream); - raft::copy(assignment.data(), static_cast(data), size, stream); - stream.synchronize(); - solutions.push_back(std::make_pair(cost, std::move(assignment))); - } - std::vector>> solutions; -}; - -void check_solutions(const test_incumbent_callback_t& incumbent_callback, - const cuopt::mps_parser::mps_data_model_t& op_problem, - const cuopt::linear_programming::mip_solver_settings_t& settings) -{ - for (const auto& solution : incumbent_callback.solutions) { - EXPECT_EQ(solution.second.size(), op_problem.get_variable_lower_bounds().size()); - test_variable_bounds(op_problem, solution.second, settings); - const double unscaled_acceptable_tol = 0.1; - test_constraint_sanity_per_row( - op_problem, - solution.second, - // because of scaling the values are not as accurate, so add more relative tolerance - unscaled_acceptable_tol, - settings.get_relative_tolerance()); - test_objective_sanity(op_problem, solution.second, solution.first, 1e-4); - } -} - -void test_incumbent_callback(std::string test_instance) -{ - const raft::handle_t handle_{}; - std::cout << "Running: " << test_instance << std::endl; - auto path = make_path_absolute(test_instance); - cuopt::mps_parser::mps_data_model_t mps_problem = - cuopt::mps_parser::parse_mps(path, false); - handle_.sync_stream(); - auto op_problem = mps_data_model_to_optimization_problem(&handle_, mps_problem); - - auto settings = mip_solver_settings_t{}; - settings.set_time_limit(30.); - test_incumbent_callback_t incumbent_callback; - settings.set_incumbent_solution_callback(&incumbent_callback); - auto solution = solve_mip(op_problem, settings); - check_solutions(incumbent_callback, mps_problem, settings); -} - -TEST(mip_solve, incumbent_callback_test) -{ - std::vector test_instances = {"mip/50v-10.mps", "mip/neos5.mps", "mip/swath1.mps"}; - for (const auto& test_instance : test_instances) { - test_incumbent_callback(test_instance); - } -} - -} // namespace cuopt::linear_programming::test diff --git a/cpp/tests/linear_programming/mip_unit_tests/bounds_standardization_test.cu b/cpp/tests/mip/bounds_standardization_test.cu similarity index 98% rename from cpp/tests/linear_programming/mip_unit_tests/bounds_standardization_test.cu rename to cpp/tests/mip/bounds_standardization_test.cu index f8390a02b..b4d23176a 100644 --- a/cpp/tests/linear_programming/mip_unit_tests/bounds_standardization_test.cu +++ b/cpp/tests/mip/bounds_standardization_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 diff --git a/cpp/tests/linear_programming/mip_unit_tests/doc_example_test.cu b/cpp/tests/mip/doc_example_test.cu similarity index 98% rename from cpp/tests/linear_programming/mip_unit_tests/doc_example_test.cu rename to cpp/tests/mip/doc_example_test.cu index 7151ea86b..f771017e6 100644 --- a/cpp/tests/linear_programming/mip_unit_tests/doc_example_test.cu +++ b/cpp/tests/mip/doc_example_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 diff --git a/cpp/tests/linear_programming/mip_unit_tests/elim_var_remap_test.cu b/cpp/tests/mip/elim_var_remap_test.cu similarity index 99% rename from cpp/tests/linear_programming/mip_unit_tests/elim_var_remap_test.cu rename to cpp/tests/mip/elim_var_remap_test.cu index a718c986c..163970eef 100644 --- a/cpp/tests/linear_programming/mip_unit_tests/elim_var_remap_test.cu +++ b/cpp/tests/mip/elim_var_remap_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 diff --git a/cpp/tests/linear_programming/mip_unit_tests/empty_fixed_problems_test.cu b/cpp/tests/mip/empty_fixed_problems_test.cu similarity index 97% rename from cpp/tests/linear_programming/mip_unit_tests/empty_fixed_problems_test.cu rename to cpp/tests/mip/empty_fixed_problems_test.cu index 8fa91fe33..7df91676d 100644 --- a/cpp/tests/linear_programming/mip_unit_tests/empty_fixed_problems_test.cu +++ b/cpp/tests/mip/empty_fixed_problems_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 diff --git a/cpp/tests/linear_programming/mip_unit_tests/feasibility_jump_tests.cu b/cpp/tests/mip/feasibility_jump_tests.cu similarity index 99% rename from cpp/tests/linear_programming/mip_unit_tests/feasibility_jump_tests.cu rename to cpp/tests/mip/feasibility_jump_tests.cu index bee0d14cf..10061b8c1 100644 --- a/cpp/tests/linear_programming/mip_unit_tests/feasibility_jump_tests.cu +++ b/cpp/tests/mip/feasibility_jump_tests.cu @@ -10,7 +10,7 @@ * its affiliates is strictly prohibited. */ -#include "../utilities/pdlp_test_utilities.cuh" +#include "../linear_programming/utilities/pdlp_test_utilities.cuh" #include "mip_utils.cuh" #include diff --git a/cpp/tests/mip/incumbent_callback_test.cu b/cpp/tests/mip/incumbent_callback_test.cu new file mode 100644 index 000000000..2c4e0ca47 --- /dev/null +++ b/cpp/tests/mip/incumbent_callback_test.cu @@ -0,0 +1,143 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 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 "../linear_programming/utilities/pdlp_test_utilities.cuh" +#include "mip_utils.cuh" + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace cuopt::linear_programming::test { + +class test_set_solution_callback_t : public cuopt::internals::set_solution_callback_t { + public: + test_set_solution_callback_t( + std::vector, double>>& solutions_) + : solutions(solutions_), n_calls(0) + { + } + // 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); + 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(); + } + std::vector, double>>& solutions; + int n_calls; +}; + +class test_get_solution_callback_t : public cuopt::internals::get_solution_callback_t { + public: + test_get_solution_callback_t( + std::vector, double>>& solutions_in, int n_variables_) + : solutions(solutions_in), n_calls(0), n_variables(n_variables_) + { + } + 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); + auto h_cost = 0.; + raft::copy(&h_cost, static_cast(cost), 1, stream); + stream.synchronize(); + solutions.push_back(std::make_pair(std::move(assignment), h_cost)); + } + std::vector, double>>& solutions; + int n_calls; + int n_variables; +}; + +void check_solutions(const test_get_solution_callback_t& get_solution_callback, + const cuopt::mps_parser::mps_data_model_t& op_problem, + const cuopt::linear_programming::mip_solver_settings_t& settings) +{ + for (const auto& solution : get_solution_callback.solutions) { + EXPECT_EQ(solution.first.size(), op_problem.get_variable_lower_bounds().size()); + test_variable_bounds(op_problem, solution.first, settings); + const double unscaled_acceptable_tol = 0.1; + test_constraint_sanity_per_row( + op_problem, + solution.first, + // because of scaling the values are not as accurate, so add more relative tolerance + unscaled_acceptable_tol, + settings.tolerances.relative_tolerance); + test_objective_sanity(op_problem, solution.first, solution.second, 1e-4); + } +} + +void test_incumbent_callback(std::string test_instance) +{ + const raft::handle_t handle_{}; + std::cout << "Running: " << test_instance << std::endl; + auto path = make_path_absolute(test_instance); + cuopt::mps_parser::mps_data_model_t mps_problem = + cuopt::mps_parser::parse_mps(path, false); + handle_.sync_stream(); + auto op_problem = mps_data_model_to_optimization_problem(&handle_, mps_problem); + + auto settings = mip_solver_settings_t{}; + settings.time_limit = 30.; + std::vector, double>> solutions; + test_get_solution_callback_t get_solution_callback(solutions, op_problem.get_n_variables()); + test_set_solution_callback_t set_solution_callback(solutions); + settings.set_mip_callback(&get_solution_callback); + settings.set_mip_callback(&set_solution_callback); + auto solution = solve_mip(op_problem, settings); + EXPECT_GE(get_solution_callback.n_calls, 1); + EXPECT_GE(set_solution_callback.n_calls, 1); + check_solutions(get_solution_callback, mps_problem, settings); +} + +TEST(mip_solve, incumbent_callback_test) +{ + std::vector test_instances = {"mip/50v-10.mps", "mip/neos5.mps", "mip/swath1.mps"}; + for (const auto& test_instance : test_instances) { + test_incumbent_callback(test_instance); + } +} + +} // namespace cuopt::linear_programming::test diff --git a/cpp/tests/linear_programming/mip_unit_tests/mip_utils.cuh b/cpp/tests/mip/mip_utils.cuh similarity index 100% rename from cpp/tests/linear_programming/mip_unit_tests/mip_utils.cuh rename to cpp/tests/mip/mip_utils.cuh diff --git a/cpp/tests/linear_programming/mip_unit_tests/miplib_test.cu b/cpp/tests/mip/miplib_test.cu similarity index 97% rename from cpp/tests/linear_programming/mip_unit_tests/miplib_test.cu rename to cpp/tests/mip/miplib_test.cu index b82d741bc..306c6593d 100644 --- a/cpp/tests/linear_programming/mip_unit_tests/miplib_test.cu +++ b/cpp/tests/mip/miplib_test.cu @@ -10,7 +10,7 @@ * its affiliates is strictly prohibited. */ -#include "../utilities/pdlp_test_utilities.cuh" +#include "../linear_programming/utilities/pdlp_test_utilities.cuh" #include "mip_utils.cuh" #include diff --git a/cpp/tests/linear_programming/mip_unit_tests/multi_probe_test.cu b/cpp/tests/mip/multi_probe_test.cu similarity index 99% rename from cpp/tests/linear_programming/mip_unit_tests/multi_probe_test.cu rename to cpp/tests/mip/multi_probe_test.cu index 014543fe7..63cf93c79 100644 --- a/cpp/tests/linear_programming/mip_unit_tests/multi_probe_test.cu +++ b/cpp/tests/mip/multi_probe_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 diff --git a/cpp/tests/linear_programming/mip_unit_tests/problem_test.cu b/cpp/tests/mip/problem_test.cu similarity index 99% rename from cpp/tests/linear_programming/mip_unit_tests/problem_test.cu rename to cpp/tests/mip/problem_test.cu index a786716d7..01adc9edc 100644 --- a/cpp/tests/linear_programming/mip_unit_tests/problem_test.cu +++ b/cpp/tests/mip/problem_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 #include diff --git a/cpp/tests/linear_programming/mip_unit_tests/server_test.cu b/cpp/tests/mip/server_test.cu similarity index 98% rename from cpp/tests/linear_programming/mip_unit_tests/server_test.cu rename to cpp/tests/mip/server_test.cu index 257646e18..1bca86a62 100644 --- a/cpp/tests/linear_programming/mip_unit_tests/server_test.cu +++ b/cpp/tests/mip/server_test.cu @@ -10,7 +10,7 @@ * its affiliates is strictly prohibited. */ -#include "../utilities/pdlp_test_utilities.cuh" +#include "../linear_programming/utilities/pdlp_test_utilities.cuh" #include "mip_utils.cuh" #include diff --git a/cpp/tests/mip/termination_test.cu b/cpp/tests/mip/termination_test.cu new file mode 100644 index 000000000..cc6ff7440 --- /dev/null +++ b/cpp/tests/mip/termination_test.cu @@ -0,0 +1,103 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 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 "../linear_programming/utilities/pdlp_test_utilities.cuh" +#include "mip_utils.cuh" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace cuopt::linear_programming::test { + +static std::pair test_mps_file(std::string test_instance, + bool heuristics_only = true, + double time_limit = 10) +{ + const raft::handle_t handle_{}; + + auto path = make_path_absolute(test_instance); + cuopt::mps_parser::mps_data_model_t problem = + cuopt::mps_parser::parse_mps(path, false); + handle_.sync_stream(); + mip_solver_settings_t settings; + settings.set_time_limit(time_limit); + settings.set_heuristics_only(heuristics_only); + mip_solution_t solution = solve_mip(&handle_, problem, settings); + return std::make_pair(solution.get_termination_status(), solution.get_objective_value()); +} + +TEST(termination_status, presolve_infeasible_test) +{ + auto [termination_status, obj_val] = test_mps_file("mip/presolve-infeasible.mps"); + EXPECT_EQ(termination_status, mip_termination_status_t::Infeasible); +} + +TEST(termination_status, feasible_found_test) +{ + auto [termination_status, obj_val] = test_mps_file("mip/gen-ip054.mps"); + EXPECT_EQ(termination_status, mip_termination_status_t::FeasibleFound); +} + +TEST(termination_status, timeout_test) +{ + auto [termination_status, obj_val] = test_mps_file("mip/stein9inf.mps"); + EXPECT_EQ(termination_status, mip_termination_status_t::TimeLimit); +} + +TEST(termination_status, optimality_test) +{ + auto [termination_status, obj_val] = test_mps_file("mip/bb_optimality.mps", false); + EXPECT_EQ(termination_status, mip_termination_status_t::Optimal); +} + +TEST(termination_status, bb_infeasible_test) +{ + // First, check that presolve doesn't reduce the problem to infeasibility + { + auto [termination_status, obj_val] = test_mps_file("mip/stein9inf.mps", true, 1); + EXPECT_EQ(termination_status, mip_termination_status_t::TimeLimit); + } + // Ensure that B&B proves the MIP infeasible + { + auto [termination_status, obj_val] = test_mps_file("mip/stein9inf.mps", false, 30); + EXPECT_EQ(termination_status, mip_termination_status_t::Infeasible); + } +} + +} // namespace cuopt::linear_programming::test \ No newline at end of file diff --git a/cpp/tests/linear_programming/mip_unit_tests/unit_test.cu b/cpp/tests/mip/unit_test.cu similarity index 98% rename from cpp/tests/linear_programming/mip_unit_tests/unit_test.cu rename to cpp/tests/mip/unit_test.cu index ce3499e3d..cf8268b9f 100644 --- a/cpp/tests/linear_programming/mip_unit_tests/unit_test.cu +++ b/cpp/tests/mip/unit_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 From 08b20e4bdc6562994add5b28d203b85f066744e4 Mon Sep 17 00:00:00 2001 From: akifcorduk Date: Mon, 19 May 2025 10:54:32 -0700 Subject: [PATCH 2/7] fix bug in preprocess assignmnet --- cpp/src/mip/diversity/diversity_manager.cu | 20 ++++----- cpp/src/mip/problem/problem.cu | 52 ++++++++++------------ 2 files changed, 32 insertions(+), 40 deletions(-) diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index 72f6c2f21..26bdf135e 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()); } diff --git a/cpp/src/mip/problem/problem.cu b/cpp/src/mip/problem/problem.cu index 174d6af58..ce87d150d 100644 --- a/cpp/src/mip/problem/problem.cu +++ b/cpp/src/mip/problem/problem.cu @@ -594,41 +594,37 @@ bool problem_t::pre_process_assignment(rmm::device_uvector& assig } // 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()); + rmm::device_uvector internal_assignment(assignment, 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()); + 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 = 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] : 0.; + assgn[idx] += assgn[additional_var_id_per_var[idx]]; + } + }); + + thrust::gather(handle_ptr->get_thrust_policy(), + presolve_data.variable_mapping.begin(), + presolve_data.variable_mapping.end(), + internal_assignment.begin(), + assignment.begin()); + assignment.resize(presolve_data.variable_mapping.size(), 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()); handle_ptr->sync_stream(); return true; } From 712faede38e6a944bf32bf86edb826e1f333c8ca Mon Sep 17 00:00:00 2001 From: akifcorduk Date: Mon, 19 May 2025 12:04:53 -0700 Subject: [PATCH 3/7] fix empty problem bug after presolve, fix issues on emptry problem running kernels in other places too --- cpp/src/mip/diversity/diversity_manager.cu | 14 ++++++++------ cpp/src/mip/presolve/trivial_presolve.cuh | 5 +++-- cpp/src/mip/problem/problem.cu | 13 ++++++++----- cpp/src/mip/solver.cu | 8 +++++++- 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index 26bdf135e..153175fc0 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -246,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/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 ce87d150d..be1ca565d 100644 --- a/cpp/src/mip/problem/problem.cu +++ b/cpp/src/mip/problem/problem.cu @@ -639,11 +639,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; From 5e6b1ba32eb1547f91af3355f73f443774ddf61f Mon Sep 17 00:00:00 2001 From: akifcorduk Date: Mon, 19 May 2025 12:18:09 -0700 Subject: [PATCH 4/7] fix sizing on the temp_assignment --- cpp/src/mip/problem/problem.cu | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/cpp/src/mip/problem/problem.cu b/cpp/src/mip/problem/problem.cu index be1ca565d..053db6045 100644 --- a/cpp/src/mip/problem/problem.cu +++ b/cpp/src/mip/problem/problem.cu @@ -593,20 +593,24 @@ 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(assignment, handle_ptr->get_stream()); - + // 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()); + // 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 = internal_assignment.data()] __device__(auto idx) { + 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"); @@ -621,7 +625,7 @@ bool problem_t::pre_process_assignment(rmm::device_uvector& assig thrust::gather(handle_ptr->get_thrust_policy(), presolve_data.variable_mapping.begin(), presolve_data.variable_mapping.end(), - internal_assignment.begin(), + temp_assignment.begin(), assignment.begin()); assignment.resize(presolve_data.variable_mapping.size(), handle_ptr->get_stream()); assignment.shrink_to_fit(handle_ptr->get_stream()); From a07b13640b2cf73d93922d7af88a2cccebeb985c Mon Sep 17 00:00:00 2001 From: akifcorduk Date: Tue, 20 May 2025 02:47:22 -0700 Subject: [PATCH 5/7] fix duplicate set solution and change test to test free vars --- cpp/include/cuopt/semantic_version.hpp | 22 --------------------- cpp/src/mip/diversity/population.cu | 25 +----------------------- cpp/tests/mip/incumbent_callback_test.cu | 4 ++-- 3 files changed, 3 insertions(+), 48 deletions(-) delete mode 100644 cpp/include/cuopt/semantic_version.hpp diff --git a/cpp/include/cuopt/semantic_version.hpp b/cpp/include/cuopt/semantic_version.hpp deleted file mode 100644 index 7d3cac169..000000000 --- a/cpp/include/cuopt/semantic_version.hpp +++ /dev/null @@ -1,22 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 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 - -#define CUOPT_SEMANTIC_VERSION_MAJOR 0 -#define CUOPT_SEMANTIC_VERSION_MINOR 0 -#define CUOPT_SEMANTIC_VERSION_PATCH 0 diff --git a/cpp/src/mip/diversity/population.cu b/cpp/src/mip/diversity/population.cu index 85685e6bd..a3aba3432 100644 --- a/cpp/src/mip/diversity/population.cu +++ b/cpp/src/mip/diversity/population.cu @@ -208,30 +208,7 @@ 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()); - 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) { - context.scaling.scale_solutions(incumbent_assignment, dummy); - } - } - } - - for (auto callback : user_callbacks) { - if (callback->get_type() == internals::base_solution_callback_type::SET_SOLUTION) { - auto set_sol_callback = static_cast(callback); - rmm::device_uvector incumbent_assignment( - problem_ptr->original_problem_ptr->get_n_variables(), sol.handle_ptr->get_stream()); - 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()); + 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()); diff --git a/cpp/tests/mip/incumbent_callback_test.cu b/cpp/tests/mip/incumbent_callback_test.cu index 2c4e0ca47..f45efca72 100644 --- a/cpp/tests/mip/incumbent_callback_test.cu +++ b/cpp/tests/mip/incumbent_callback_test.cu @@ -59,7 +59,6 @@ class test_set_solution_callback_t : public cuopt::internals::set_solution_callb 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(); @@ -134,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); } From aa58390ff0b00272e30ac135e25b190d8dfc2b8d Mon Sep 17 00:00:00 2001 From: akifcorduk Date: Tue, 20 May 2025 05:28:13 -0700 Subject: [PATCH 6/7] fix more bugs: wrong place of resize assignment, tests call set_solution multiple times --- cpp/src/mip/diversity/population.cu | 16 +++++++++------- cpp/src/mip/problem/problem.cu | 8 ++++---- cpp/tests/mip/incumbent_callback_test.cu | 2 ++ 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/cpp/src/mip/diversity/population.cu b/cpp/src/mip/diversity/population.cu index a3aba3432..0780e77ce 100644 --- a/cpp/src/mip/diversity/population.cu +++ b/cpp/src/mip/diversity/population.cu @@ -208,11 +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) { @@ -220,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/problem/problem.cu b/cpp/src/mip/problem/problem.cu index 053db6045..5826fee48 100644 --- a/cpp/src/mip/problem/problem.cu +++ b/cpp/src/mip/problem/problem.cu @@ -617,18 +617,18 @@ bool problem_t::pre_process_assignment(rmm::device_uvector& assig // 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[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()); + 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()); - assignment.resize(presolve_data.variable_mapping.size(), handle_ptr->get_stream()); - assignment.shrink_to_fit(handle_ptr->get_stream()); handle_ptr->sync_stream(); return true; } diff --git a/cpp/tests/mip/incumbent_callback_test.cu b/cpp/tests/mip/incumbent_callback_test.cu index f45efca72..ac9e445ca 100644 --- a/cpp/tests/mip/incumbent_callback_test.cu +++ b/cpp/tests/mip/incumbent_callback_test.cu @@ -62,6 +62,8 @@ class test_set_solution_callback_t : public cuopt::internals::set_solution_callb raft::copy(assignment, last_assignment.data(), last_assignment.size(), stream); raft::copy(cost_ptr, &last_cost, 1, stream); stream.synchronize(); + // Erase the last element of solutions after copying it + solutions.pop_back(); } std::vector, double>>& solutions; int n_calls; From 74cc76956e398c5e323ec787e36357db1cf43aa1 Mon Sep 17 00:00:00 2001 From: akifcorduk Date: Tue, 20 May 2025 06:08:22 -0700 Subject: [PATCH 7/7] don't pop back solutions --- cpp/tests/mip/incumbent_callback_test.cu | 2 -- 1 file changed, 2 deletions(-) diff --git a/cpp/tests/mip/incumbent_callback_test.cu b/cpp/tests/mip/incumbent_callback_test.cu index ac9e445ca..f45efca72 100644 --- a/cpp/tests/mip/incumbent_callback_test.cu +++ b/cpp/tests/mip/incumbent_callback_test.cu @@ -62,8 +62,6 @@ class test_set_solution_callback_t : public cuopt::internals::set_solution_callb raft::copy(assignment, last_assignment.data(), last_assignment.size(), stream); raft::copy(cost_ptr, &last_cost, 1, stream); stream.synchronize(); - // Erase the last element of solutions after copying it - solutions.pop_back(); } std::vector, double>>& solutions; int n_calls;