diff --git a/cpp/include/cuopt/linear_programming/constants.h b/cpp/include/cuopt/linear_programming/constants.h index 86becfe06d..7da9231ddb 100644 --- a/cpp/include/cuopt/linear_programming/constants.h +++ b/cpp/include/cuopt/linear_programming/constants.h @@ -82,17 +82,18 @@ #define CUOPT_MODE_DETERMINISTIC 1 /* @brief LP/MIP termination status constants */ -#define CUOPT_TERIMINATION_STATUS_NO_TERMINATION 0 -#define CUOPT_TERIMINATION_STATUS_OPTIMAL 1 -#define CUOPT_TERIMINATION_STATUS_INFEASIBLE 2 -#define CUOPT_TERIMINATION_STATUS_UNBOUNDED 3 -#define CUOPT_TERIMINATION_STATUS_ITERATION_LIMIT 4 -#define CUOPT_TERIMINATION_STATUS_TIME_LIMIT 5 -#define CUOPT_TERIMINATION_STATUS_NUMERICAL_ERROR 6 -#define CUOPT_TERIMINATION_STATUS_PRIMAL_FEASIBLE 7 -#define CUOPT_TERIMINATION_STATUS_FEASIBLE_FOUND 8 -#define CUOPT_TERIMINATION_STATUS_CONCURRENT_LIMIT 9 -#define CUOPT_TERIMINATION_STATUS_WORK_LIMIT 10 +#define CUOPT_TERIMINATION_STATUS_NO_TERMINATION 0 +#define CUOPT_TERIMINATION_STATUS_OPTIMAL 1 +#define CUOPT_TERIMINATION_STATUS_INFEASIBLE 2 +#define CUOPT_TERIMINATION_STATUS_UNBOUNDED 3 +#define CUOPT_TERIMINATION_STATUS_ITERATION_LIMIT 4 +#define CUOPT_TERIMINATION_STATUS_TIME_LIMIT 5 +#define CUOPT_TERIMINATION_STATUS_NUMERICAL_ERROR 6 +#define CUOPT_TERIMINATION_STATUS_PRIMAL_FEASIBLE 7 +#define CUOPT_TERIMINATION_STATUS_FEASIBLE_FOUND 8 +#define CUOPT_TERIMINATION_STATUS_CONCURRENT_LIMIT 9 +#define CUOPT_TERIMINATION_STATUS_WORK_LIMIT 10 +#define CUOPT_TERIMINATION_STATUS_UNBOUNDED_OR_INFEASIBLE 11 /* @brief The objective sense constants */ #define CUOPT_MINIMIZE 1 diff --git a/cpp/include/cuopt/linear_programming/mip/solver_solution.hpp b/cpp/include/cuopt/linear_programming/mip/solver_solution.hpp index a6c28ac20d..67ffe60ac3 100644 --- a/cpp/include/cuopt/linear_programming/mip/solver_solution.hpp +++ b/cpp/include/cuopt/linear_programming/mip/solver_solution.hpp @@ -24,13 +24,14 @@ namespace cuopt::linear_programming { enum class mip_termination_status_t : int8_t { - NoTermination = CUOPT_TERIMINATION_STATUS_NO_TERMINATION, - Optimal = CUOPT_TERIMINATION_STATUS_OPTIMAL, - FeasibleFound = CUOPT_TERIMINATION_STATUS_FEASIBLE_FOUND, - Infeasible = CUOPT_TERIMINATION_STATUS_INFEASIBLE, - Unbounded = CUOPT_TERIMINATION_STATUS_UNBOUNDED, - TimeLimit = CUOPT_TERIMINATION_STATUS_TIME_LIMIT, - WorkLimit = CUOPT_TERIMINATION_STATUS_WORK_LIMIT, + NoTermination = CUOPT_TERIMINATION_STATUS_NO_TERMINATION, + Optimal = CUOPT_TERIMINATION_STATUS_OPTIMAL, + FeasibleFound = CUOPT_TERIMINATION_STATUS_FEASIBLE_FOUND, + Infeasible = CUOPT_TERIMINATION_STATUS_INFEASIBLE, + Unbounded = CUOPT_TERIMINATION_STATUS_UNBOUNDED, + TimeLimit = CUOPT_TERIMINATION_STATUS_TIME_LIMIT, + WorkLimit = CUOPT_TERIMINATION_STATUS_WORK_LIMIT, + UnboundedOrInfeasible = CUOPT_TERIMINATION_STATUS_UNBOUNDED_OR_INFEASIBLE, }; template diff --git a/cpp/include/cuopt/linear_programming/pdlp/solver_solution.hpp b/cpp/include/cuopt/linear_programming/pdlp/solver_solution.hpp index 45a47e7401..7f8ad71171 100644 --- a/cpp/include/cuopt/linear_programming/pdlp/solver_solution.hpp +++ b/cpp/include/cuopt/linear_programming/pdlp/solver_solution.hpp @@ -25,15 +25,16 @@ namespace cuopt::linear_programming { // Possible reasons for terminating enum class pdlp_termination_status_t : int8_t { - NoTermination = CUOPT_TERIMINATION_STATUS_NO_TERMINATION, - NumericalError = CUOPT_TERIMINATION_STATUS_NUMERICAL_ERROR, - Optimal = CUOPT_TERIMINATION_STATUS_OPTIMAL, - PrimalInfeasible = CUOPT_TERIMINATION_STATUS_INFEASIBLE, - DualInfeasible = CUOPT_TERIMINATION_STATUS_UNBOUNDED, - IterationLimit = CUOPT_TERIMINATION_STATUS_ITERATION_LIMIT, - TimeLimit = CUOPT_TERIMINATION_STATUS_TIME_LIMIT, - PrimalFeasible = CUOPT_TERIMINATION_STATUS_PRIMAL_FEASIBLE, - ConcurrentLimit = CUOPT_TERIMINATION_STATUS_CONCURRENT_LIMIT + NoTermination = CUOPT_TERIMINATION_STATUS_NO_TERMINATION, + NumericalError = CUOPT_TERIMINATION_STATUS_NUMERICAL_ERROR, + Optimal = CUOPT_TERIMINATION_STATUS_OPTIMAL, + PrimalInfeasible = CUOPT_TERIMINATION_STATUS_INFEASIBLE, + DualInfeasible = CUOPT_TERIMINATION_STATUS_UNBOUNDED, + IterationLimit = CUOPT_TERIMINATION_STATUS_ITERATION_LIMIT, + TimeLimit = CUOPT_TERIMINATION_STATUS_TIME_LIMIT, + PrimalFeasible = CUOPT_TERIMINATION_STATUS_PRIMAL_FEASIBLE, + ConcurrentLimit = CUOPT_TERIMINATION_STATUS_CONCURRENT_LIMIT, + UnboundedOrInfeasible = CUOPT_TERIMINATION_STATUS_UNBOUNDED_OR_INFEASIBLE }; /** diff --git a/cpp/src/mip_heuristics/presolve/third_party_presolve.cpp b/cpp/src/mip_heuristics/presolve/third_party_presolve.cpp index 2fa10f421b..8b09eb2ca9 100644 --- a/cpp/src/mip_heuristics/presolve/third_party_presolve.cpp +++ b/cpp/src/mip_heuristics/presolve/third_party_presolve.cpp @@ -477,6 +477,36 @@ void check_presolve_status(const papilo::PresolveStatus& status) } } +third_party_presolve_status_t convert_papilo_presolve_status_to_third_party_presolve_status( + const papilo::PresolveStatus& status) +{ + switch (status) { + case papilo::PresolveStatus::kUnchanged: return third_party_presolve_status_t::UNCHANGED; + case papilo::PresolveStatus::kReduced: return third_party_presolve_status_t::REDUCED; + case papilo::PresolveStatus::kUnbndOrInfeas: + return third_party_presolve_status_t::UNBNDORINFEAS; + case papilo::PresolveStatus::kInfeasible: return third_party_presolve_status_t::INFEASIBLE; + case papilo::PresolveStatus::kUnbounded: + return third_party_presolve_status_t::UNBOUNDED; + // Do not implement default case to trigger compile time error if new enum is added + } + return third_party_presolve_status_t::UNCHANGED; +} + +third_party_presolve_status_t convert_pslp_presolve_status_to_third_party_presolve_status( + const PresolveStatus& status) +{ + switch (status) { + case PresolveStatus_::UNCHANGED: return third_party_presolve_status_t::UNCHANGED; + case PresolveStatus_::REDUCED: return third_party_presolve_status_t::REDUCED; + case PresolveStatus_::INFEASIBLE: return third_party_presolve_status_t::INFEASIBLE; + case PresolveStatus_::UNBNDORINFEAS: + return third_party_presolve_status_t::UNBNDORINFEAS; + // Do not implement default case to trigger compile time error if new enum is added + } + return third_party_presolve_status_t::UNCHANGED; +} + void check_postsolve_status(const papilo::PostsolveStatus& status) { switch (status) { @@ -570,7 +600,7 @@ void set_presolve_parameters(papilo::Presolve& presolver, } template -std::optional> third_party_presolve_t::apply_pslp( +third_party_presolve_result_t third_party_presolve_t::apply_pslp( optimization_problem_t const& op_problem, const double time_limit) { if constexpr (std::is_same_v) { @@ -584,23 +614,24 @@ std::optional> third_party_presolve_t empty_problem(op_problem.get_handle_ptr()); + return third_party_presolve_result_t{status, std::move(empty_problem), {}, {}, {}}; } auto opt_problem = build_optimization_problem_from_pslp( pslp_presolver_, op_problem.get_handle_ptr(), maximize_, original_obj_offset); - opt_problem.set_problem_name(op_problem.get_problem_name()); - return std::make_optional(third_party_presolve_result_t{opt_problem, {}}); + return third_party_presolve_result_t{status, std::move(opt_problem), {}, {}, {}}; } else { cuopt_expects( false, error_type_t::ValidationError, "PSLP presolver only supports double precision"); - return std::nullopt; + return third_party_presolve_result_t{third_party_presolve_status_t::UNCHANGED, optimization_problem_t(op_problem.get_handle_ptr()), {}, {}, {}}; } } template -std::optional> third_party_presolve_t::apply( +third_party_presolve_result_t third_party_presolve_t::apply( optimization_problem_t const& op_problem, problem_category_t category, cuopt::linear_programming::presolver_t presolver, @@ -648,9 +679,12 @@ std::optional> third_party_presolve_t empty_problem(op_problem.get_handle_ptr()); + return third_party_presolve_result_t{status, std::move(empty_problem), {}, {}, {}}; } papilo_post_solve_storage_.reset(new papilo::PostsolveStorage(result.postsolve)); CUOPT_LOG_INFO("Presolve removed: %d constraints, %d variables, %d nonzeros", @@ -686,8 +720,11 @@ std::optional> third_party_presolve_t{ - opt_problem, implied_integer_indices, reduced_to_original_map_, original_to_reduced_map_}); + return third_party_presolve_result_t{status, + std::move(opt_problem), + implied_integer_indices, + reduced_to_original_map_, + original_to_reduced_map_}; } template diff --git a/cpp/src/mip_heuristics/presolve/third_party_presolve.hpp b/cpp/src/mip_heuristics/presolve/third_party_presolve.hpp index ee273b6497..73743715f3 100644 --- a/cpp/src/mip_heuristics/presolve/third_party_presolve.hpp +++ b/cpp/src/mip_heuristics/presolve/third_party_presolve.hpp @@ -27,8 +27,18 @@ struct papilo_postsolve_deleter { void operator()(papilo::PostsolveStorage* ptr) const; }; +enum class third_party_presolve_status_t { + INFEASIBLE, + UNBNDORINFEAS, + UNBOUNDED, + OPTIMAL, + REDUCED, + UNCHANGED, +}; + template struct third_party_presolve_result_t { + third_party_presolve_status_t status; optimization_problem_t reduced_problem; std::vector implied_integer_indices; std::vector reduced_to_original_map; @@ -48,15 +58,14 @@ class third_party_presolve_t { third_party_presolve_t(third_party_presolve_t&&) = delete; third_party_presolve_t& operator=(third_party_presolve_t&&) = delete; - std::optional> apply( - optimization_problem_t const& op_problem, - problem_category_t category, - cuopt::linear_programming::presolver_t presolver, - bool dual_postsolve, - f_t absolute_tolerance, - f_t relative_tolerance, - double time_limit, - i_t num_cpu_threads = 0); + third_party_presolve_result_t apply(optimization_problem_t const& op_problem, + problem_category_t category, + cuopt::linear_programming::presolver_t presolver, + bool dual_postsolve, + f_t absolute_tolerance, + f_t relative_tolerance, + double time_limit, + i_t num_cpu_threads = 0); void undo(rmm::device_uvector& primal_solution, rmm::device_uvector& dual_solution, @@ -74,7 +83,7 @@ class third_party_presolve_t { ~third_party_presolve_t(); private: - std::optional> apply_pslp( + third_party_presolve_result_t apply_pslp( optimization_problem_t const& op_problem, const double time_limit); void undo_pslp(rmm::device_uvector& primal_solution, diff --git a/cpp/src/mip_heuristics/solve.cu b/cpp/src/mip_heuristics/solve.cu index f5a2172f2e..cf85e21788 100644 --- a/cpp/src/mip_heuristics/solve.cu +++ b/cpp/src/mip_heuristics/solve.cu @@ -244,7 +244,7 @@ mip_solution_t solve_mip(optimization_problem_t& op_problem, double presolve_time = 0.0; std::unique_ptr> presolver; - std::optional> presolve_result; + std::optional> presolve_result_opt; detail::problem_t problem( op_problem, settings.get_tolerances(), settings.determinism_mode == CUOPT_MODE_DETERMINISTIC); @@ -283,22 +283,32 @@ mip_solution_t solve_mip(optimization_problem_t& op_problem, settings.tolerances.relative_tolerance, presolve_time_limit, settings.num_cpu_threads); - if (!result.has_value()) { + if (result.status == detail::third_party_presolve_status_t::INFEASIBLE) { return mip_solution_t(mip_termination_status_t::Infeasible, solver_stats_t{}, op_problem.get_handle_ptr()->get_stream()); } - presolve_result.emplace(std::move(*result)); + if (result.status == detail::third_party_presolve_status_t::UNBNDORINFEAS) { + return mip_solution_t(mip_termination_status_t::UnboundedOrInfeasible, + solver_stats_t{}, + op_problem.get_handle_ptr()->get_stream()); + } + if (result.status == detail::third_party_presolve_status_t::UNBOUNDED) { + return mip_solution_t(mip_termination_status_t::Unbounded, + solver_stats_t{}, + op_problem.get_handle_ptr()->get_stream()); + } + presolve_result_opt.emplace(std::move(result)); - problem = detail::problem_t(presolve_result->reduced_problem); + problem = detail::problem_t(presolve_result_opt->reduced_problem); problem.set_papilo_presolve_data(presolver.get(), - presolve_result->reduced_to_original_map, - presolve_result->original_to_reduced_map, + presolve_result_opt->reduced_to_original_map, + presolve_result_opt->original_to_reduced_map, op_problem.get_n_variables()); - problem.set_implied_integers(presolve_result->implied_integer_indices); + problem.set_implied_integers(presolve_result_opt->implied_integer_indices); presolve_time = timer.elapsed_time(); - if (presolve_result->implied_integer_indices.size() > 0) { - CUOPT_LOG_INFO("%d implied integers", presolve_result->implied_integer_indices.size()); + if (presolve_result_opt->implied_integer_indices.size() > 0) { + CUOPT_LOG_INFO("%d implied integers", presolve_result_opt->implied_integer_indices.size()); } CUOPT_LOG_INFO("Papilo presolve time: %.2f", presolve_time); } diff --git a/cpp/src/mip_heuristics/solver_solution.cu b/cpp/src/mip_heuristics/solver_solution.cu index e497a21c8f..a9bc6c5416 100644 --- a/cpp/src/mip_heuristics/solver_solution.cu +++ b/cpp/src/mip_heuristics/solver_solution.cu @@ -137,8 +137,9 @@ std::string mip_solution_t::get_termination_status_string( case mip_termination_status_t::Infeasible: return "Infeasible"; case mip_termination_status_t::TimeLimit: return "TimeLimit"; case mip_termination_status_t::WorkLimit: return "WorkLimit"; - case mip_termination_status_t::Unbounded: - return "Unbounded"; + case mip_termination_status_t::Unbounded: return "Unbounded"; + case mip_termination_status_t::UnboundedOrInfeasible: + return "UnboundedOrInfeasible"; // Do not implement default case to trigger compile time error if new enum is added } return std::string(); diff --git a/cpp/src/pdlp/solve.cu b/cpp/src/pdlp/solve.cu index 2fc9ec08d5..a39f1f3ccf 100644 --- a/cpp/src/pdlp/solve.cu +++ b/cpp/src/pdlp/solve.cu @@ -1361,7 +1361,7 @@ optimization_problem_solution_t solve_lp( auto run_presolve = settings.presolver != presolver_t::None; run_presolve = run_presolve && settings.get_pdlp_warm_start_data().total_pdlp_iterations_ == -1; - // Declare result at outer scope so that result->reduced_problem (which may be + // Declare result at outer scope so that result.reduced_problem (which may be // referenced by problem.original_problem_ptr) remains alive through the solve. std::optional> result; @@ -1380,10 +1380,19 @@ optimization_problem_solution_t solve_lp( settings.tolerances.absolute_primal_tolerance, settings.tolerances.relative_primal_tolerance, presolve_time_limit); - if (!result.has_value()) { + if (result->status == detail::third_party_presolve_status_t::INFEASIBLE) { return optimization_problem_solution_t( pdlp_termination_status_t::PrimalInfeasible, op_problem.get_handle_ptr()->get_stream()); } + if (result->status == detail::third_party_presolve_status_t::UNBNDORINFEAS) { + return optimization_problem_solution_t( + pdlp_termination_status_t::UnboundedOrInfeasible, + op_problem.get_handle_ptr()->get_stream()); + } + if (result->status == detail::third_party_presolve_status_t::UNBOUNDED) { + return optimization_problem_solution_t(pdlp_termination_status_t::DualInfeasible, + op_problem.get_handle_ptr()->get_stream()); + } // Handle case where presolve completely solved the problem (reduced to 0 rows/cols) // Must check before constructing problem_t since it fails on empty problems diff --git a/cpp/src/pdlp/solver_solution.cu b/cpp/src/pdlp/solver_solution.cu index 10e6a80593..ba68d20740 100644 --- a/cpp/src/pdlp/solver_solution.cu +++ b/cpp/src/pdlp/solver_solution.cu @@ -316,8 +316,12 @@ std::string optimization_problem_solution_t::get_termination_status_st case pdlp_termination_status_t::NumericalError: return "A numerical error was encountered."; case pdlp_termination_status_t::PrimalFeasible: return "Primal Feasible"; case pdlp_termination_status_t::ConcurrentLimit: return "Concurrent Limit"; - default: return "Unknown cuOpt status"; + case pdlp_termination_status_t::UnboundedOrInfeasible: return "UnboundedOrInfeasible"; + case pdlp_termination_status_t::NoTermination: + return "NoTermination"; + // Do not implement default case to trigger compile time error if new enum is added } + return std::string(); } template diff --git a/cpp/tests/mip/presolve_test.cu b/cpp/tests/mip/presolve_test.cu index cf8abd1b69..cf2532d0f2 100644 --- a/cpp/tests/mip/presolve_test.cu +++ b/cpp/tests/mip/presolve_test.cu @@ -45,11 +45,12 @@ TEST(problem, find_implied_integers) 1e-12, 20, 1); - ASSERT_TRUE(result.has_value()); + ASSERT_NE(result.status, detail::third_party_presolve_status_t::INFEASIBLE); + ASSERT_NE(result.status, detail::third_party_presolve_status_t::UNBNDORINFEAS); - auto problem = detail::problem_t(result->reduced_problem); - problem.set_implied_integers(result->implied_integer_indices); - ASSERT_TRUE(result->implied_integer_indices.size() > 0); + auto problem = detail::problem_t(result.reduced_problem); + problem.set_implied_integers(result.implied_integer_indices); + ASSERT_TRUE(result.implied_integer_indices.size() > 0); auto var_types = host_copy(problem.variable_types, handle_.get_stream()); // Find the index of the one continuous variable auto it = std::find_if(var_types.begin(), var_types.end(), [](var_t var_type) { diff --git a/docs/cuopt/source/cuopt-c/lp-qp-milp/examples/milp_mps_example.c b/docs/cuopt/source/cuopt-c/lp-qp-milp/examples/milp_mps_example.c index fe59f2fc45..48f285a95d 100644 --- a/docs/cuopt/source/cuopt-c/lp-qp-milp/examples/milp_mps_example.c +++ b/docs/cuopt/source/cuopt-c/lp-qp-milp/examples/milp_mps_example.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ /* @@ -29,6 +29,8 @@ const char* termination_status_to_string(cuopt_int_t termination_status) return "Primal feasible"; case CUOPT_TERIMINATION_STATUS_FEASIBLE_FOUND: return "Feasible found"; + case CUOPT_TERIMINATION_STATUS_UNBOUNDED_OR_INFEASIBLE: + return "Unbounded or infeasible"; default: return "Unknown"; } @@ -96,10 +98,17 @@ cuopt_int_t solve_mps_file(const char* filename) goto DONE; } - status = cuOptGetObjectiveValue(solution, &objective_value); - if (status != CUOPT_SUCCESS) { - printf("Error getting objective value: %d\n", status); - goto DONE; + const int has_primal_solution = + termination_status == CUOPT_TERIMINATION_STATUS_OPTIMAL || + termination_status == CUOPT_TERIMINATION_STATUS_PRIMAL_FEASIBLE || + termination_status == CUOPT_TERIMINATION_STATUS_FEASIBLE_FOUND; + + if (has_primal_solution) { + status = cuOptGetObjectiveValue(solution, &objective_value); + if (status != CUOPT_SUCCESS) { + printf("Error getting objective value: %d\n", status); + goto DONE; + } } // Print results @@ -111,11 +120,13 @@ cuopt_int_t solve_mps_file(const char* filename) printf("Objective value: %f\n", objective_value); // Get and print solution variables - solution_values = (cuopt_float_t*)malloc(num_variables * sizeof(cuopt_float_t)); - status = cuOptGetPrimalSolution(solution, solution_values); - if (status != CUOPT_SUCCESS) { - printf("Error getting solution values: %d\n", status); - goto DONE; + if (has_primal_solution) { + solution_values = (cuopt_float_t*)malloc(num_variables * sizeof(cuopt_float_t)); + status = cuOptGetPrimalSolution(solution, solution_values); + if (status != CUOPT_SUCCESS) { + printf("Error getting solution values: %d\n", status); + goto DONE; + } } printf("\nSolution: \n"); @@ -124,7 +135,9 @@ cuopt_int_t solve_mps_file(const char* filename) } DONE: - free(solution_values); + if (solution_values != NULL) { + free(solution_values); + } cuOptDestroyProblem(&problem); cuOptDestroySolverSettings(&settings); cuOptDestroySolution(&solution); diff --git a/docs/cuopt/source/cuopt-c/lp-qp-milp/examples/simple_qp_example.c b/docs/cuopt/source/cuopt-c/lp-qp-milp/examples/simple_qp_example.c index c888901156..9a210dea5c 100644 --- a/docs/cuopt/source/cuopt-c/lp-qp-milp/examples/simple_qp_example.c +++ b/docs/cuopt/source/cuopt-c/lp-qp-milp/examples/simple_qp_example.c @@ -46,6 +46,8 @@ const char* termination_status_to_string(cuopt_int_t termination_status) return "Primal feasible"; case CUOPT_TERIMINATION_STATUS_FEASIBLE_FOUND: return "Feasible found"; + case CUOPT_TERIMINATION_STATUS_UNBOUNDED_OR_INFEASIBLE: + return "Unbounded or infeasible"; default: return "Unknown"; } diff --git a/docs/cuopt/source/cuopt-c/lp-qp-milp/lp-qp-milp-c-api.rst b/docs/cuopt/source/cuopt-c/lp-qp-milp/lp-qp-milp-c-api.rst index d9a42301cb..c4c0f8a8af 100644 --- a/docs/cuopt/source/cuopt-c/lp-qp-milp/lp-qp-milp-c-api.rst +++ b/docs/cuopt/source/cuopt-c/lp-qp-milp/lp-qp-milp-c-api.rst @@ -276,3 +276,4 @@ These constants define the termination status received from the :c:func:`cuOptGe .. doxygendefine:: CUOPT_TERIMINATION_STATUS_PRIMAL_FEASIBLE .. doxygendefine:: CUOPT_TERIMINATION_STATUS_FEASIBLE_FOUND .. doxygendefine:: CUOPT_TERIMINATION_STATUS_CONCURRENT_LIMIT +.. doxygendefine:: CUOPT_TERIMINATION_STATUS_UNBOUNDED_OR_INFEASIBLE diff --git a/python/cuopt/cuopt/linear_programming/solver/solver.pxd b/python/cuopt/cuopt/linear_programming/solver/solver.pxd index 15597d48b0..2c3bb046d4 100644 --- a/python/cuopt/cuopt/linear_programming/solver/solver.pxd +++ b/python/cuopt/cuopt/linear_programming/solver/solver.pxd @@ -106,6 +106,8 @@ cdef extern from "cuopt/linear_programming/mip/solver_solution.hpp" namespace "c Infeasible "cuopt::linear_programming::mip_termination_status_t::Infeasible" # noqa Unbounded "cuopt::linear_programming::mip_termination_status_t::Unbounded" # noqa TimeLimit "cuopt::linear_programming::mip_termination_status_t::TimeLimit" # noqa + WorkLimit "cuopt::linear_programming::mip_termination_status_t::WorkLimit" # noqa + UnboundedOrInfeasible "cuopt::linear_programming::mip_termination_status_t::UnboundedOrInfeasible" # noqa cdef extern from "cuopt/linear_programming/pdlp/solver_solution.hpp" namespace "cuopt::linear_programming": # noqa @@ -119,6 +121,7 @@ cdef extern from "cuopt/linear_programming/pdlp/solver_solution.hpp" namespace " TimeLimit "cuopt::linear_programming::pdlp_termination_status_t::TimeLimit" # noqa ConcurrentLimit "cuopt::linear_programming::pdlp_termination_status_t::ConcurrentLimit" # noqa PrimalFeasible "cuopt::linear_programming::pdlp_termination_status_t::PrimalFeasible" # noqa + UnboundedOrInfeasible "cuopt::linear_programming::pdlp_termination_status_t::UnboundedOrInfeasible" # noqa cdef extern from "cuopt/linear_programming/utilities/cython_types.hpp" namespace "cuopt::cython": # noqa diff --git a/python/cuopt/cuopt/linear_programming/solver/solver_wrapper.pyx b/python/cuopt/cuopt/linear_programming/solver/solver_wrapper.pyx index 10ff04d0a2..ebd89ece40 100644 --- a/python/cuopt/cuopt/linear_programming/solver/solver_wrapper.pyx +++ b/python/cuopt/cuopt/linear_programming/solver/solver_wrapper.pyx @@ -80,6 +80,7 @@ class MILPTerminationStatus(IntEnum): Infeasible = mip_termination_status_t.Infeasible Unbounded = mip_termination_status_t.Unbounded TimeLimit = mip_termination_status_t.TimeLimit + UnboundedOrInfeasible = mip_termination_status_t.UnboundedOrInfeasible class LPTerminationStatus(IntEnum): @@ -91,6 +92,7 @@ class LPTerminationStatus(IntEnum): IterationLimit = pdlp_termination_status_t.IterationLimit TimeLimit = pdlp_termination_status_t.TimeLimit PrimalFeasible = pdlp_termination_status_t.PrimalFeasible + UnboundedOrInfeasible = pdlp_termination_status_t.UnboundedOrInfeasible class ErrorStatus(IntEnum): 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 e284ffc0ab..08cb0b4a70 100644 --- a/python/cuopt/cuopt/tests/linear_programming/test_lp_solver.py +++ b/python/cuopt/cuopt/tests/linear_programming/test_lp_solver.py @@ -7,7 +7,11 @@ import numpy as np import pytest -from cuopt.linear_programming import data_model, solver, solver_settings +from cuopt.linear_programming import ( + data_model, + solver, + solver_settings, +) from cuopt.linear_programming.solver.solver_parameters import ( CUOPT_ABSOLUTE_DUAL_TOLERANCE, CUOPT_ABSOLUTE_GAP_TOLERANCE, @@ -36,6 +40,12 @@ from cuopt.linear_programming.solver_settings import ( PDLPSolverMode, SolverMethod, + SolverSettings, +) +from cuopt.linear_programming.problem import ( + Problem, + CONTINUOUS, + MINIMIZE, ) RAPIDS_DATASET_ROOT_DIR = os.getenv("RAPIDS_DATASET_ROOT_DIR") @@ -725,6 +735,22 @@ def test_write_files(): os.remove("afiro.sol") +def test_unbounded_problem(): + problem = Problem("unbounded") + x = problem.addVariable(lb=0.0, vtype=CONTINUOUS, name="x") + y = problem.addVariable(lb=0.0, vtype=CONTINUOUS, name="y") + + problem.addConstraint(-1 * x + 2 * y <= 0, name="c1") + + problem.setObjective(-1 * x - 1 * y, sense=MINIMIZE) + + settings = SolverSettings() + + problem.solve(settings) + + assert problem.Status.name == "UnboundedOrInfeasible" + + def test_pdlp_precision_single(): file_path = ( RAPIDS_DATASET_ROOT_DIR + "/linear_programming/afiro_original.mps"