diff --git a/cpp/include/cuopt/linear_programming/mip/solver_solution.hpp b/cpp/include/cuopt/linear_programming/mip/solver_solution.hpp index 442c2f92b..b29db43db 100644 --- a/cpp/include/cuopt/linear_programming/mip/solver_solution.hpp +++ b/cpp/include/cuopt/linear_programming/mip/solver_solution.hpp @@ -19,6 +19,7 @@ #include #include +#include #include #include @@ -48,18 +49,16 @@ class mip_solution_t : public base_solution_t { std::vector var_names, f_t objective, f_t mip_gap, - f_t solution_bound, - double total_solve_time, - double presolve_time, mip_termination_status_t termination_status, f_t max_constraint_violation, f_t max_int_violation, f_t max_variable_bound_violation, - i_t num_nodes, - i_t num_simplex_iterations, + solver_stats_t stats, std::vector> solution_pool = {}); - mip_solution_t(mip_termination_status_t termination_status, rmm::cuda_stream_view stream_view); + mip_solution_t(mip_termination_status_t termination_status, + solver_stats_t stats, + rmm::cuda_stream_view stream_view); mip_solution_t(const cuopt::logic_error& error_status, rmm::cuda_stream_view stream_view); bool is_mip() const override { return true; } @@ -88,16 +87,12 @@ class mip_solution_t : public base_solution_t { std::vector var_names_; f_t objective_; f_t mip_gap_; - f_t solution_bound_; - double total_solve_time_; - double presolve_time_; mip_termination_status_t termination_status_; cuopt::logic_error error_status_; f_t max_constraint_violation_; f_t max_int_violation_; f_t max_variable_bound_violation_; - i_t num_nodes_; - i_t num_simplex_iterations_; + solver_stats_t stats_; std::vector> solution_pool_; }; diff --git a/cpp/src/mip/solver_stats.cuh b/cpp/include/cuopt/linear_programming/mip/solver_stats.hpp similarity index 100% rename from cpp/src/mip/solver_stats.cuh rename to cpp/include/cuopt/linear_programming/mip/solver_stats.hpp diff --git a/cpp/src/linear_programming/cuopt_c.cpp b/cpp/src/linear_programming/cuopt_c.cpp index 074566ba7..a8ccc9b6b 100644 --- a/cpp/src/linear_programming/cuopt_c.cpp +++ b/cpp/src/linear_programming/cuopt_c.cpp @@ -763,7 +763,7 @@ cuopt_int_t cuOptGetSolveTime(cuOptSolution solution, cuopt_float_t* solve_time_ optimization_problem_solution_t* optimization_problem_solution = static_cast*>( solution_and_stream_view->lp_solution_ptr); - *solve_time_ptr = (optimization_problem_solution->get_solve_time() / 1000.0); + *solve_time_ptr = (optimization_problem_solution->get_solve_time()); } return CUOPT_SUCCESS; } diff --git a/cpp/src/mip/diversity/diversity_manager.cuh b/cpp/src/mip/diversity/diversity_manager.cuh index 5c99c8049..4a2da6bbe 100644 --- a/cpp/src/mip/diversity/diversity_manager.cuh +++ b/cpp/src/mip/diversity/diversity_manager.cuh @@ -25,11 +25,11 @@ #include "recombiners/recombiner_stats.hpp" #include +#include #include #include #include -#include #include namespace cuopt::linear_programming::detail { diff --git a/cpp/src/mip/solution/solution.cu b/cpp/src/mip/solution/solution.cu index 05309fb3a..54c763641 100644 --- a/cpp/src/mip/solution/solution.cu +++ b/cpp/src/mip/solution/solution.cu @@ -598,18 +598,15 @@ mip_solution_t solution_t::get_solution(bool output_feasible problem_ptr->var_names, h_user_obj, rel_mip_gap, - solution_bound, - total_solve_time, - presolve_time, term_reason, max_constraint_violation, max_int_violation, max_variable_bound_violation, - num_nodes, - num_simplex_iterations); + stats); } else { return mip_solution_t{is_problem_fully_reduced ? mip_termination_status_t::Infeasible : mip_termination_status_t::TimeLimit, + stats, handle_ptr->get_stream()}; } } diff --git a/cpp/src/mip/solution/solution.cuh b/cpp/src/mip/solution/solution.cuh index fc139fb31..729a5c0e5 100644 --- a/cpp/src/mip/solution/solution.cuh +++ b/cpp/src/mip/solution/solution.cuh @@ -19,10 +19,10 @@ #include #include +#include #include #include #include -#include #include #include diff --git a/cpp/src/mip/solver.cuh b/cpp/src/mip/solver.cuh index 0a230bda7..beff11d9b 100644 --- a/cpp/src/mip/solver.cuh +++ b/cpp/src/mip/solver.cuh @@ -16,10 +16,10 @@ */ #include +#include #include #include #include -#include #include #pragma once diff --git a/cpp/src/mip/solver_context.cuh b/cpp/src/mip/solver_context.cuh index a714a25b4..3bb78380f 100644 --- a/cpp/src/mip/solver_context.cuh +++ b/cpp/src/mip/solver_context.cuh @@ -15,10 +15,11 @@ * limitations under the License. */ +#include + #include #include #include -#include #pragma once diff --git a/cpp/src/mip/solver_solution.cu b/cpp/src/mip/solver_solution.cu index 54532ce7b..a6761aaff 100644 --- a/cpp/src/mip/solver_solution.cu +++ b/cpp/src/mip/solver_solution.cu @@ -32,29 +32,21 @@ mip_solution_t::mip_solution_t(rmm::device_uvector solution, std::vector var_names, f_t objective, f_t mip_gap, - f_t solution_bound, - double total_solve_time, - double presolve_time, mip_termination_status_t termination_status, f_t max_constraint_violation, f_t max_int_violation, f_t max_variable_bound_violation, - i_t num_nodes, - i_t num_simplex_iterations, + solver_stats_t stats, std::vector> solution_pool) : solution_(std::move(solution)), var_names_(std::move(var_names)), objective_(objective), mip_gap_(mip_gap), - solution_bound_(solution_bound), - total_solve_time_(total_solve_time), - presolve_time_(presolve_time), termination_status_(termination_status), max_constraint_violation_(max_constraint_violation), max_int_violation_(max_int_violation), max_variable_bound_violation_(max_variable_bound_violation), - num_nodes_(num_nodes), - num_simplex_iterations_(num_simplex_iterations), + stats_(stats), solution_pool_(std::move(solution_pool)), error_status_(cuopt::logic_error("", cuopt::error_type_t::Success)) { @@ -62,19 +54,16 @@ mip_solution_t::mip_solution_t(rmm::device_uvector solution, template mip_solution_t::mip_solution_t(mip_termination_status_t termination_status, + solver_stats_t stats, rmm::cuda_stream_view stream_view) : solution_(0, stream_view), objective_(0), mip_gap_(0), - solution_bound_(0), - total_solve_time_(0), - presolve_time_(0), termination_status_(termination_status), max_constraint_violation_(0), max_int_violation_(0), max_variable_bound_violation_(0), - num_nodes_(0), - num_simplex_iterations_(0), + stats_(stats), error_status_(cuopt::logic_error("", cuopt::error_type_t::Success)) { } @@ -85,9 +74,6 @@ mip_solution_t::mip_solution_t(const cuopt::logic_error& error_status, : solution_(0, stream_view), objective_(0), mip_gap_(0), - solution_bound_(0), - total_solve_time_(0), - presolve_time_(0), termination_status_(mip_termination_status_t::NoTermination), max_constraint_violation_(0), max_int_violation_(0), @@ -129,19 +115,19 @@ f_t mip_solution_t::get_mip_gap() const template f_t mip_solution_t::get_solution_bound() const { - return solution_bound_; + return stats_.solution_bound; } template double mip_solution_t::get_total_solve_time() const { - return total_solve_time_; + return stats_.total_solve_time; } template double mip_solution_t::get_presolve_time() const { - return presolve_time_; + return stats_.presolve_time; } template @@ -194,13 +180,13 @@ f_t mip_solution_t::get_max_variable_bound_violation() const template i_t mip_solution_t::get_num_nodes() const { - return num_nodes_; + return stats_.num_nodes; } template i_t mip_solution_t::get_num_simplex_iterations() const { - return num_simplex_iterations_; + return stats_.num_simplex_iterations; } template diff --git a/cpp/tests/linear_programming/c_api_tests/c_api_test.c b/cpp/tests/linear_programming/c_api_tests/c_api_test.c index 453e95984..28472b201 100644 --- a/cpp/tests/linear_programming/c_api_tests/c_api_test.c +++ b/cpp/tests/linear_programming/c_api_tests/c_api_test.c @@ -287,7 +287,7 @@ cuopt_int_t burglar_problem() return status; } -int solve_mps_file(const char* filename, double time_limit, double iteration_limit, int* termination_status_ptr) +int solve_mps_file(const char* filename, double time_limit, double iteration_limit, int* termination_status_ptr, double* solve_time_ptr, int method) { cuOptOptimizationProblem problem = NULL; cuOptSolverSettings settings = NULL; @@ -313,7 +313,7 @@ int solve_mps_file(const char* filename, double time_limit, double iteration_lim printf("Error creating solver settings\n"); goto DONE; } - status = cuOptSetIntegerParameter(settings, CUOPT_METHOD, CUOPT_METHOD_DUAL_SIMPLEX); + status = cuOptSetIntegerParameter(settings, CUOPT_METHOD, method); if (status != CUOPT_SUCCESS) { printf("Error setting method\n"); goto DONE; @@ -345,6 +345,7 @@ int solve_mps_file(const char* filename, double time_limit, double iteration_lim goto DONE; } status = cuOptGetSolveTime(solution, &time); + if (solve_time_ptr) *solve_time_ptr = time; if (status != CUOPT_SUCCESS) { printf("Error getting solve time\n"); goto DONE; diff --git a/cpp/tests/linear_programming/c_api_tests/c_api_tests.cpp b/cpp/tests/linear_programming/c_api_tests/c_api_tests.cpp index f4a0cea0f..2e414b5bb 100644 --- a/cpp/tests/linear_programming/c_api_tests/c_api_tests.cpp +++ b/cpp/tests/linear_programming/c_api_tests/c_api_tests.cpp @@ -38,15 +38,37 @@ TEST(c_api, afiro) EXPECT_EQ(termination_status, CUOPT_TERIMINATION_STATUS_OPTIMAL); } -TEST(c_api, time_limit) +// Test both LP and MIP codepaths +class TimeLimitTestFixture : public ::testing::TestWithParam> { +}; +TEST_P(TimeLimitTestFixture, time_limit) { const std::string& rapidsDatasetRootDir = cuopt::test::get_rapids_dataset_root_dir(); - std::string filename = rapidsDatasetRootDir + "/linear_programming/" + "afiro_original.mps"; + std::string filename = rapidsDatasetRootDir + std::get<0>(GetParam()); + double target_solve_time = std::get<1>(GetParam()); + int method = std::get<2>(GetParam()); int termination_status; - EXPECT_EQ(solve_mps_file(filename.c_str(), 1e-6, CUOPT_INFINITY, &termination_status), + double solve_time = std::numeric_limits::quiet_NaN(); + EXPECT_EQ(solve_mps_file(filename.c_str(), + target_solve_time, + CUOPT_INFINITY, + &termination_status, + &solve_time, + method), CUOPT_SUCCESS); EXPECT_EQ(termination_status, CUOPT_TERIMINATION_STATUS_TIME_LIMIT); + EXPECT_NEAR(solve_time, target_solve_time, 0.1); } +INSTANTIATE_TEST_SUITE_P( + c_api, + TimeLimitTestFixture, + ::testing::Values( + std::make_tuple("/linear_programming/square41/square41.mps", + 5, + CUOPT_METHOD_DUAL_SIMPLEX), // LP, Dual Simplex + std::make_tuple("/linear_programming/square41/square41.mps", 5, CUOPT_METHOD_PDLP), // LP, PDLP + std::make_tuple("/mip/enlight_hard.mps", 5, CUOPT_METHOD_DUAL_SIMPLEX) // MIP + )); TEST(c_api, iteration_limit) { diff --git a/cpp/tests/linear_programming/c_api_tests/c_api_tests.h b/cpp/tests/linear_programming/c_api_tests/c_api_tests.h index 0faa9e94b..e5c9a965c 100644 --- a/cpp/tests/linear_programming/c_api_tests/c_api_tests.h +++ b/cpp/tests/linear_programming/c_api_tests/c_api_tests.h @@ -27,7 +27,14 @@ cuopt_int_t burglar_problem(); cuopt_int_t solve_mps_file(const char* filename, double time_limit, double iteration_limit, - cuopt_int_t* termination_status); + cuopt_int_t* termination_status, +#ifdef __cplusplus + cuopt_float_t* solve_time = 0, + cuopt_int_t method = CUOPT_METHOD_DUAL_SIMPLEX); +#else + cuopt_float_t* solve_time, + cuopt_int_t method); +#endif cuopt_int_t test_missing_file(); cuopt_int_t test_infeasible_problem(); cuopt_int_t test_bad_parameter_name(); diff --git a/cpp/tests/mip/bounds_standardization_test.cu b/cpp/tests/mip/bounds_standardization_test.cu index fd6318447..01b4a64ff 100644 --- a/cpp/tests/mip/bounds_standardization_test.cu +++ b/cpp/tests/mip/bounds_standardization_test.cu @@ -19,11 +19,11 @@ #include "mip_utils.cuh" #include +#include #include #include #include #include -#include #include #include #include diff --git a/datasets/mip/download_miplib_test_dataset.sh b/datasets/mip/download_miplib_test_dataset.sh index 070748342..3aafc9d9c 100755 --- a/datasets/mip/download_miplib_test_dataset.sh +++ b/datasets/mip/download_miplib_test_dataset.sh @@ -34,6 +34,7 @@ INSTANCES=( "stein9inf" "neos5" "swath1" + "enlight_hard" ) BASE_URL="https://miplib.zib.de/WebData/instances" 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 eeb5400ad..ed0d04ec1 100644 --- a/python/cuopt/cuopt/tests/linear_programming/test_lp_solver.py +++ b/python/cuopt/cuopt/tests/linear_programming/test_lp_solver.py @@ -161,7 +161,7 @@ def test_time_limit_solver(): solution = solver.Solve(data_model_obj, settings) assert solution.get_termination_status() == LPTerminationStatus.TimeLimit # Check that around 200 ms has passed with some tolerance - assert solution.get_solve_time() <= (time_limit_seconds * 10) * 1000 + assert solution.get_solve_time() <= (time_limit_seconds * 10) # Not all 0 assert solution.get_primal_objective() != 0.0 assert np.any(solution.get_primal_solution())