diff --git a/ci/test_wheel_cuopt.sh b/ci/test_wheel_cuopt.sh index 02f1d64ced..1b37ed020f 100755 --- a/ci/test_wheel_cuopt.sh +++ b/ci/test_wheel_cuopt.sh @@ -73,10 +73,8 @@ timeout 10m bash ./python/libcuopt/libcuopt/tests/test_cli.sh # Run Python tests RAPIDS_DATASET_ROOT_DIR=./datasets timeout 30m python -m pytest --verbose --capture=no ./python/cuopt/cuopt/tests/ -# run cvxpy integration tests -./ci/thirdparty-testing/run_cvxpy_tests.sh - -# run jump tests for only nightly builds +# run jump tests and cvxpy integration tests for only nightly builds if [[ "${RAPIDS_BUILD_TYPE}" == "nightly" ]]; then ./ci/thirdparty-testing/run_jump_tests.sh + ./ci/thirdparty-testing/run_cvxpy_tests.sh fi diff --git a/ci/utils/install_boost_tbb.sh b/ci/utils/install_boost_tbb.sh index 0334f00d64..bf7cca4db1 100644 --- a/ci/utils/install_boost_tbb.sh +++ b/ci/utils/install_boost_tbb.sh @@ -24,7 +24,8 @@ if [ -f /etc/os-release ]; then echo "Detected Rocky Linux. Installing Boost and TBB via dnf..." dnf clean all dnf -y update - dnf install -y boost-devel tbb-devel + dnf install -y epel-release + dnf install -y boost1.78-devel tbb-devel if [[ "$(uname -m)" == "x86_64" ]]; then dnf install -y gcc-toolset-14-libquadmath-devel fi diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index b377f659b4..05da6e8525 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -183,7 +183,14 @@ endif() FetchContent_Declare( papilo GIT_REPOSITORY "https://github.com/scipopt/papilo.git" - GIT_TAG "v2.4.3" + # We would want to get the main branch. However, the main branch + # does not have some of the presolvers and settings that we need + # Mainly, probing and clique merging. + # This is the reason we are using the development branch + # commit from Sep 26, 2025. Once these changes are merged into the main branch, + #we can switch to the main branch. + GIT_TAG "34a40781fa14f8870cb6368cffb6c0eda2f47511" + GIT_PROGRESS TRUE SYSTEM ) @@ -422,7 +429,6 @@ if(NOT BUILD_LP_ONLY) cuopt OpenMP::OpenMP_CXX PRIVATE - papilo-core ) set_property(TARGET cuopt_cli PROPERTY INSTALL_RPATH "$ORIGIN/../${lib_dir}") @@ -446,7 +452,6 @@ if(BUILD_MIP_BENCHMARKS AND NOT BUILD_LP_ONLY) cuopt OpenMP::OpenMP_CXX PRIVATE - papilo-core ) endif() @@ -462,7 +467,6 @@ if(BUILD_LP_BENCHMARKS) cuopt OpenMP::OpenMP_CXX PRIVATE - papilo-core ) endif() diff --git a/cpp/include/cuopt/linear_programming/constants.h b/cpp/include/cuopt/linear_programming/constants.h index 092e427fc1..203bc72d51 100644 --- a/cpp/include/cuopt/linear_programming/constants.h +++ b/cpp/include/cuopt/linear_programming/constants.h @@ -51,6 +51,7 @@ #define CUOPT_LOG_TO_CONSOLE "log_to_console" #define CUOPT_CROSSOVER "crossover" #define CUOPT_PRESOLVE "presolve" +#define CUOPT_DUAL_POSTSOLVE "dual_postsolve" #define CUOPT_MIP_ABSOLUTE_TOLERANCE "mip_absolute_tolerance" #define CUOPT_MIP_RELATIVE_TOLERANCE "mip_relative_tolerance" #define CUOPT_MIP_INTEGRALITY_TOLERANCE "mip_integrality_tolerance" diff --git a/cpp/include/cuopt/linear_programming/pdlp/solver_settings.hpp b/cpp/include/cuopt/linear_programming/pdlp/solver_settings.hpp index 0660ede6ec..4671da7e15 100644 --- a/cpp/include/cuopt/linear_programming/pdlp/solver_settings.hpp +++ b/cpp/include/cuopt/linear_programming/pdlp/solver_settings.hpp @@ -211,6 +211,7 @@ class pdlp_solver_settings_t { bool save_best_primal_so_far{false}; bool first_primal_feasible{false}; bool presolve{false}; + bool dual_postsolve{true}; method_t method{method_t::Concurrent}; // For concurrent termination std::atomic* concurrent_halt; diff --git a/cpp/src/dual_simplex/branch_and_bound.cpp b/cpp/src/dual_simplex/branch_and_bound.cpp index 641fa127fd..1a7ea7d825 100644 --- a/cpp/src/dual_simplex/branch_and_bound.cpp +++ b/cpp/src/dual_simplex/branch_and_bound.cpp @@ -567,8 +567,6 @@ dual::status_t branch_and_bound_t::node_dual_simplex( leaf_problem, lp_start_time, lp_settings, leaf_solution, leaf_vstatus, leaf_edge_norms); lp_status = convert_lp_status_to_dual_status(second_status); } - } else { - log.printf("Infeasible after bounds strengthening. Fathoming node %d.\n", leaf_id); } mutex_stats_.lock(); diff --git a/cpp/src/linear_programming/solve.cu b/cpp/src/linear_programming/solve.cu index 730820222c..337b29ec97 100644 --- a/cpp/src/linear_programming/solve.cu +++ b/cpp/src/linear_programming/solve.cu @@ -700,11 +700,12 @@ optimization_problem_solution_t solve_lp(optimization_problem_t>(); auto [reduced_problem, feasible] = presolver->apply(op_problem, cuopt::linear_programming::problem_category_t::LP, + settings.dual_postsolve, settings.tolerances.absolute_primal_tolerance, settings.tolerances.relative_primal_tolerance, presolve_time_limit); @@ -714,7 +715,7 @@ optimization_problem_solution_t solve_lp(optimization_problem_t(reduced_problem); presolve_time = lp_timer.elapsed_time(); - CUOPT_LOG_INFO("Third party presolve time: %f", presolve_time); + CUOPT_LOG_INFO("Papilo presolve time: %f", presolve_time); } CUOPT_LOG_INFO( diff --git a/cpp/src/math_optimization/solver_settings.cu b/cpp/src/math_optimization/solver_settings.cu index 9e1381cbbd..14986df664 100644 --- a/cpp/src/math_optimization/solver_settings.cu +++ b/cpp/src/math_optimization/solver_settings.cu @@ -107,7 +107,8 @@ solver_settings_t::solver_settings_t() : pdlp_settings(), mip_settings {CUOPT_LOG_TO_CONSOLE, &mip_settings.log_to_console, true}, {CUOPT_CROSSOVER, &pdlp_settings.crossover, false}, {CUOPT_PRESOLVE, &pdlp_settings.presolve, false}, - {CUOPT_PRESOLVE, &mip_settings.presolve, true} + {CUOPT_PRESOLVE, &mip_settings.presolve, true}, + {CUOPT_DUAL_POSTSOLVE, &pdlp_settings.dual_postsolve, true} }; // String parameters string_parameters = { diff --git a/cpp/src/mip/presolve/third_party_presolve.cpp b/cpp/src/mip/presolve/third_party_presolve.cpp index dc2d4b00e8..a351143087 100644 --- a/cpp/src/mip/presolve/third_party_presolve.cpp +++ b/cpp/src/mip/presolve/third_party_presolve.cpp @@ -34,7 +34,8 @@ static papilo::PostsolveStorage post_solve_storage_; static bool maximize_ = false; template -papilo::Problem build_papilo_problem(const optimization_problem_t& op_problem) +papilo::Problem build_papilo_problem(const optimization_problem_t& op_problem, + problem_category_t category) { // Build papilo problem from optimization problem papilo::ProblemBuilder builder; @@ -167,7 +168,11 @@ papilo::Problem build_papilo_problem(const optimization_problem_t if (h_entries.size()) { auto constexpr const sorted_entries = true; - auto csr_storage = papilo::SparseStorage(h_entries, num_rows, num_cols, sorted_entries); + // MIP reductions like clique merging and substituition require more fillin + const double spare_ratio = category == problem_category_t::MIP ? 4.0 : 2.0; + const int min_inter_row_space = category == problem_category_t::MIP ? 30 : 4; + auto csr_storage = papilo::SparseStorage( + h_entries, num_rows, num_cols, sorted_entries, spare_ratio, min_inter_row_space); problem.setConstraintMatrix(csr_storage, h_constr_lb, h_constr_ub, h_row_flags); papilo::ConstraintMatrix& matrix = problem.getConstraintMatrix(); @@ -304,14 +309,16 @@ void check_postsolve_status(const papilo::PostsolveStatus& status) } template -void set_presolve_methods(papilo::Presolve& presolver, problem_category_t category) +void set_presolve_methods(papilo::Presolve& presolver, + problem_category_t category, + bool dual_postsolve) { using uptr = std::unique_ptr>; - // cuopt custom presolvers - if (category == problem_category_t::MIP) + if (category == problem_category_t::MIP) { + // cuOpt custom GF2 presolver presolver.addPresolveMethod(uptr(new cuopt::linear_programming::detail::GF2Presolve())); - + } // fast presolvers presolver.addPresolveMethod(uptr(new papilo::SingletonCols())); presolver.addPresolveMethod(uptr(new papilo::CoefficientStrengthening())); @@ -326,16 +333,21 @@ void set_presolve_methods(papilo::Presolve& presolver, problem_category_t c presolver.addPresolveMethod(uptr(new papilo::SingletonStuffing())); presolver.addPresolveMethod(uptr(new papilo::DualFix())); presolver.addPresolveMethod(uptr(new papilo::SimplifyInequalities())); + presolver.addPresolveMethod(uptr(new papilo::CliqueMerging())); // exhaustive presolvers presolver.addPresolveMethod(uptr(new papilo::ImplIntDetection())); presolver.addPresolveMethod(uptr(new papilo::DominatedCols())); presolver.addPresolveMethod(uptr(new papilo::Probing())); - presolver.addPresolveMethod(uptr(new papilo::DualInfer)); - presolver.addPresolveMethod(uptr(new papilo::SimpleSubstitution())); - presolver.addPresolveMethod(uptr(new papilo::Sparsify())); - presolver.addPresolveMethod(uptr(new papilo::Substitution())); + if (!dual_postsolve) { + presolver.addPresolveMethod(uptr(new papilo::DualInfer())); + presolver.addPresolveMethod(uptr(new papilo::SimpleSubstitution())); + presolver.addPresolveMethod(uptr(new papilo::Sparsify())); + presolver.addPresolveMethod(uptr(new papilo::Substitution())); + } else { + CUOPT_LOG_INFO("Disabling the presolver methods that do not support dual postsolve"); + } } template @@ -351,26 +363,51 @@ void set_presolve_options(papilo::Presolve& presolver, presolver.getPresolveOptions().feastol = 1e-5; } +template +void set_presolve_parameters(papilo::Presolve& presolver, + problem_category_t category, + int nrows, + int ncols) +{ + // It looks like a copy. But this copy has the pointers to relevant variables in papilo + auto params = presolver.getParameters(); + if (category == problem_category_t::MIP) { + // Papilo has work unit measurements for probing. Because of this when the first batch fails to + // produce any reductions, the algorithm stops. To avoid stopping the algorithm, we set a + // minimum badge size to a huge value. The time limit makes sure that we exit if it takes too + // long + int min_badgesize = std::max(ncols / 2, 32); + params.setParameter("probing.minbadgesize", min_badgesize); + params.setParameter("cliquemerging.enabled", true); + params.setParameter("cliquemerging.maxcalls", 50); + } +} + template std::pair, bool> third_party_presolve_t::apply( optimization_problem_t const& op_problem, problem_category_t category, + bool dual_postsolve, f_t absolute_tolerance, f_t relative_tolerance, double time_limit, i_t num_cpu_threads) { - papilo::Problem papilo_problem = build_papilo_problem(op_problem); + papilo::Problem papilo_problem = build_papilo_problem(op_problem, category); CUOPT_LOG_INFO("Unpresolved problem:: %d constraints, %d variables, %d nonzeros", papilo_problem.getNRows(), papilo_problem.getNCols(), papilo_problem.getConstraintMatrix().getNnz()); + CUOPT_LOG_INFO("Calling Papilo presolver"); + if (category == problem_category_t::MIP) { dual_postsolve = false; } papilo::Presolve presolver; - set_presolve_methods(presolver, category); + set_presolve_methods(presolver, category, dual_postsolve); set_presolve_options( presolver, category, absolute_tolerance, relative_tolerance, time_limit, num_cpu_threads); + set_presolve_parameters( + presolver, category, op_problem.get_n_constraints(), op_problem.get_n_variables()); // Disable papilo logs presolver.setVerbosityLevel(papilo::VerbosityLevel::kQuiet); @@ -423,9 +460,12 @@ void third_party_presolve_t::undo(rmm::device_uvector& primal_sol check_postsolve_status(status); primal_solution.resize(full_sol.primal.size(), stream_view); - dual_solution.resize(full_sol.primal.size(), stream_view); - reduced_costs.resize(full_sol.primal.size(), stream_view); + dual_solution.resize(full_sol.dual.size(), stream_view); + reduced_costs.resize(full_sol.reducedCosts.size(), stream_view); raft::copy(primal_solution.data(), full_sol.primal.data(), full_sol.primal.size(), stream_view); + raft::copy(dual_solution.data(), full_sol.dual.data(), full_sol.dual.size(), stream_view); + raft::copy( + reduced_costs.data(), full_sol.reducedCosts.data(), full_sol.reducedCosts.size(), stream_view); } #if MIP_INSTANTIATE_FLOAT diff --git a/cpp/src/mip/presolve/third_party_presolve.hpp b/cpp/src/mip/presolve/third_party_presolve.hpp index 5631fc12e2..6e5092de2f 100644 --- a/cpp/src/mip/presolve/third_party_presolve.hpp +++ b/cpp/src/mip/presolve/third_party_presolve.hpp @@ -29,6 +29,7 @@ class third_party_presolve_t { std::pair, bool> apply( optimization_problem_t const& op_problem, problem_category_t category, + bool dual_postsolve, f_t absolute_tolerance, f_t relative_tolerance, double time_limit, diff --git a/cpp/src/mip/solve.cu b/cpp/src/mip/solve.cu index 22638c4f96..547abee49d 100644 --- a/cpp/src/mip/solve.cu +++ b/cpp/src/mip/solve.cu @@ -202,11 +202,13 @@ mip_solution_t solve_mip(optimization_problem_t& op_problem, if (run_presolve) { // allocate not more than 10% of the time limit to presolve. // Note that this is not the presolve time, but the time limit for presolve. - const double presolve_time_limit = 0.1 * time_limit; + const double presolve_time_limit = std::min(0.1 * time_limit, 60.0); + const bool dual_postsolve = false; presolver = std::make_unique>(); auto [reduced_op_problem, feasible] = presolver->apply(op_problem, cuopt::linear_programming::problem_category_t::MIP, + dual_postsolve, settings.tolerances.absolute_tolerance, settings.tolerances.relative_tolerance, presolve_time_limit, @@ -219,7 +221,7 @@ mip_solution_t solve_mip(optimization_problem_t& op_problem, problem = detail::problem_t(reduced_op_problem); presolve_time = timer.elapsed_time(); - CUOPT_LOG_INFO("Third party presolve time: %f", presolve_time); + CUOPT_LOG_INFO("Papilo presolve time: %f", presolve_time); } if (settings.user_problem_file != "") { CUOPT_LOG_INFO("Writing user problem to file: %s", settings.user_problem_file.c_str()); diff --git a/cpp/tests/CMakeLists.txt b/cpp/tests/CMakeLists.txt index 691b6992ef..ff6da8894c 100644 --- a/cpp/tests/CMakeLists.txt +++ b/cpp/tests/CMakeLists.txt @@ -34,7 +34,6 @@ if(BUILD_TESTS) cuopt GTest::gmock GTest::gtest - papilo-core ) endif() diff --git a/cpp/tests/examples/routing/CMakeLists.txt b/cpp/tests/examples/routing/CMakeLists.txt index 3b29d259d6..52bc3e6828 100644 --- a/cpp/tests/examples/routing/CMakeLists.txt +++ b/cpp/tests/examples/routing/CMakeLists.txt @@ -48,6 +48,5 @@ foreach(target cuopt cuopttestutils OpenMP::OpenMP_CXX - papilo-core ) endforeach() diff --git a/docs/cuopt/source/lp-features.rst b/docs/cuopt/source/lp-features.rst index b89ace5d38..c5d5899079 100644 --- a/docs/cuopt/source/lp-features.rst +++ b/docs/cuopt/source/lp-features.rst @@ -78,6 +78,13 @@ Crossover Crossover allows you to obtain a high-quality basic solution from the results of a PDLP solve. More details can be found :ref:`here `. +Presolve +-------- + +Presolve procedure is applied to the problem before the solver is called. It can be used to reduce the problem size and improve solve time. It is enabled by default for MIP problems, and disabled by default for LP problems. +Furthermore, for LP problems, when the dual solution is not needed, additional presolve procedures can be applied to further improve solve times. This is achived by turned off dual postsolve. + + Logging ------- diff --git a/docs/cuopt/source/lp-milp-settings.rst b/docs/cuopt/source/lp-milp-settings.rst index c06a739689..2586954191 100644 --- a/docs/cuopt/source/lp-milp-settings.rst +++ b/docs/cuopt/source/lp-milp-settings.rst @@ -65,6 +65,11 @@ Presolve ^^^^^^^^ ``CUOPT_PRESOLVE`` controls whether presolve is enabled. Presolve can reduce problem size and improve solve time. Enabled by default for MIP, disabled by default for LP. +Dual Postsolve +^^^^^^^^^^^^^^ +``CUOPT_DUAL_POSTSOLVE`` controls whether dual postsolve is enabled. Disabling dual postsolve can improve solve time at the expense of not having +access to the dual solution. Enabled by default for LP when presolve is enabled. This is not relevant for MIP problems. + Linear Programming ------------------ diff --git a/python/cuopt/cuopt/linear_programming/solver/solver_parameters.pyx b/python/cuopt/cuopt/linear_programming/solver/solver_parameters.pyx index 413ec6e755..6eac557ffc 100644 --- a/python/cuopt/cuopt/linear_programming/solver/solver_parameters.pyx +++ b/python/cuopt/cuopt/linear_programming/solver/solver_parameters.pyx @@ -60,6 +60,7 @@ cdef extern from "cuopt/linear_programming/constants.h": # noqa cdef const char* c_CUOPT_LOG_TO_CONSOLE "CUOPT_LOG_TO_CONSOLE" # noqa cdef const char* c_CUOPT_CROSSOVER "CUOPT_CROSSOVER" # noqa cdef const char* c_CUOPT_PRESOLVE "CUOPT_PRESOLVE" # noqa + cdef const char* c_CUOPT_DUAL_POSTSOLVE "CUOPT_DUAL_POSTSOLVE" # noqa cdef const char* c_CUOPT_MIP_ABSOLUTE_TOLERANCE "CUOPT_MIP_ABSOLUTE_TOLERANCE" # noqa cdef const char* c_CUOPT_MIP_RELATIVE_TOLERANCE "CUOPT_MIP_RELATIVE_TOLERANCE" # noqa cdef const char* c_CUOPT_MIP_INTEGRALITY_TOLERANCE "CUOPT_MIP_INTEGRALITY_TOLERANCE" # noqa @@ -94,6 +95,7 @@ CUOPT_LOG_FILE = c_CUOPT_LOG_FILE.decode('utf-8') # noqa CUOPT_LOG_TO_CONSOLE = c_CUOPT_LOG_TO_CONSOLE.decode('utf-8') # noqa CUOPT_CROSSOVER = c_CUOPT_CROSSOVER.decode('utf-8') # noqa CUOPT_PRESOLVE = c_CUOPT_PRESOLVE.decode('utf-8') # noqa +CUOPT_DUAL_POSTSOLVE = c_CUOPT_DUAL_POSTSOLVE.decode('utf-8') # noqa CUOPT_MIP_ABSOLUTE_TOLERANCE = c_CUOPT_MIP_ABSOLUTE_TOLERANCE.decode('utf-8') # noqa CUOPT_MIP_RELATIVE_TOLERANCE = c_CUOPT_MIP_RELATIVE_TOLERANCE.decode('utf-8') # noqa CUOPT_MIP_INTEGRALITY_TOLERANCE = c_CUOPT_MIP_INTEGRALITY_TOLERANCE.decode('utf-8') # noqa diff --git a/python/cuopt/cuopt/linear_programming/solver_settings/solver_settings.py b/python/cuopt/cuopt/linear_programming/solver_settings/solver_settings.py index 3c25086eec..d78a36c76d 100644 --- a/python/cuopt/cuopt/linear_programming/solver_settings/solver_settings.py +++ b/python/cuopt/cuopt/linear_programming/solver_settings/solver_settings.py @@ -21,6 +21,7 @@ CUOPT_ABSOLUTE_PRIMAL_TOLERANCE, CUOPT_CROSSOVER, CUOPT_DUAL_INFEASIBLE_TOLERANCE, + CUOPT_DUAL_POSTSOLVE, CUOPT_FIRST_PRIMAL_FEASIBLE, CUOPT_INFEASIBILITY_DETECTION, CUOPT_ITERATION_LIMIT, @@ -37,6 +38,7 @@ CUOPT_NUM_CPU_THREADS, CUOPT_PDLP_SOLVER_MODE, CUOPT_PER_CONSTRAINT_RESIDUAL, + CUOPT_PRESOLVE, CUOPT_PRIMAL_INFEASIBLE_TOLERANCE, CUOPT_RELATIVE_DUAL_TOLERANCE, CUOPT_RELATIVE_GAP_TOLERANCE, @@ -370,6 +372,8 @@ def toDict(self): "iteration_limit": self.get_parameter(CUOPT_ITERATION_LIMIT), "pdlp_solver_mode": self.get_parameter(CUOPT_PDLP_SOLVER_MODE), "method": self.get_parameter(CUOPT_METHOD), + "presolve": self.get_parameter(CUOPT_PRESOLVE), + "dual_postsolve": self.get_parameter(CUOPT_DUAL_POSTSOLVE), "mip_scaling": self.get_parameter(CUOPT_MIP_SCALING), "mip_heuristics_only": self.get_parameter( CUOPT_MIP_HEURISTICS_ONLY 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 dc2b9997c1..7d3c550e24 100644 --- a/python/cuopt/cuopt/tests/linear_programming/test_lp_solver.py +++ b/python/cuopt/cuopt/tests/linear_programming/test_lp_solver.py @@ -25,11 +25,13 @@ CUOPT_ABSOLUTE_GAP_TOLERANCE, CUOPT_ABSOLUTE_PRIMAL_TOLERANCE, CUOPT_DUAL_INFEASIBLE_TOLERANCE, + CUOPT_DUAL_POSTSOLVE, CUOPT_INFEASIBILITY_DETECTION, CUOPT_ITERATION_LIMIT, CUOPT_METHOD, CUOPT_MIP_HEURISTICS_ONLY, CUOPT_PDLP_SOLVER_MODE, + CUOPT_PRESOLVE, CUOPT_PRIMAL_INFEASIBLE_TOLERANCE, CUOPT_RELATIVE_DUAL_TOLERANCE, CUOPT_RELATIVE_GAP_TOLERANCE, @@ -164,9 +166,6 @@ def test_time_limit_solver(): 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) - # Not all 0 - assert solution.get_primal_objective() != 0.0 - assert np.any(solution.get_primal_solution()) def test_set_get_fields(): @@ -601,6 +600,8 @@ def test_dual_simplex(): settings = solver_settings.SolverSettings() settings.set_parameter(CUOPT_METHOD, SolverMethod.DualSimplex) + settings.set_parameter(CUOPT_PRESOLVE, True) + settings.set_parameter(CUOPT_DUAL_POSTSOLVE, False) solution = solver.Solve(data_model_obj, settings) diff --git a/python/cuopt_server/cuopt_server/utils/linear_programming/data_definition.py b/python/cuopt_server/cuopt_server/utils/linear_programming/data_definition.py index 160e1e9305..3f68dd2d3a 100644 --- a/python/cuopt_server/cuopt_server/utils/linear_programming/data_definition.py +++ b/python/cuopt_server/cuopt_server/utils/linear_programming/data_definition.py @@ -545,6 +545,14 @@ class SolverConfig(StrictModel): "Presolve can reduce problem size and improve solve time. " "Default is True for MIP problems and False for LP problems.", ) + dual_postsolve: Optional[bool] = Field( + default=None, + description="Set True to enable dual postsolve, False to disable dual postsolve. " # noqa + "Dual postsolve can improve solve time at the expense of not having " + "access to the dual solution. " + "Default is True for LP problems when presolve is enabled. " + "This is not relevant for MIP problems.", + ) log_to_console: Optional[bool] = Field( default=True, description="Set True to write logs to console, False to " diff --git a/python/cuopt_server/cuopt_server/utils/linear_programming/solver.py b/python/cuopt_server/cuopt_server/utils/linear_programming/solver.py index 05ef2e2ad5..11e94ec38f 100644 --- a/python/cuopt_server/cuopt_server/utils/linear_programming/solver.py +++ b/python/cuopt_server/cuopt_server/utils/linear_programming/solver.py @@ -27,6 +27,7 @@ CUOPT_ABSOLUTE_PRIMAL_TOLERANCE, CUOPT_CROSSOVER, CUOPT_DUAL_INFEASIBLE_TOLERANCE, + CUOPT_DUAL_POSTSOLVE, CUOPT_FIRST_PRIMAL_FEASIBLE, CUOPT_INFEASIBILITY_DETECTION, CUOPT_ITERATION_LIMIT, @@ -398,6 +399,11 @@ def is_mip(var_types): CUOPT_PRESOLVE, solver_config.presolve ) + if solver_config.dual_postsolve is not None: + solver_settings.set_parameter( + CUOPT_DUAL_POSTSOLVE, solver_config.dual_postsolve + ) + if solver_config.log_to_console is not None: solver_settings.set_parameter( CUOPT_LOG_TO_CONSOLE, solver_config.log_to_console diff --git a/python/libcuopt/CMakeLists.txt b/python/libcuopt/CMakeLists.txt index b1cf980b7e..5cce3d0296 100644 --- a/python/libcuopt/CMakeLists.txt +++ b/python/libcuopt/CMakeLists.txt @@ -43,6 +43,16 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(argparse) + +find_package(Boost 1.65 REQUIRED) +if(Boost_FOUND) + message(STATUS "Found Boost ${Boost_VERSION} in ${Boost_INCLUDE_DIRS}") +else() + message(FATAL_ERROR "Boost not found. Please install boost-devel.") +endif() + +include_directories(${Boost_INCLUDE_DIRS}) + set(BUILD_TESTING OFF CACHE BOOL "Disable test build for papilo") set(PAPILO_NO_BINARIES ON) option(LUSOL "Disable LUSOL" OFF)