diff --git a/cpp/include/cuopt/linear_programming/pdlp/pdlp_warm_start_data.hpp b/cpp/include/cuopt/linear_programming/pdlp/pdlp_warm_start_data.hpp index 7a0ebc495..918717dbc 100644 --- a/cpp/include/cuopt/linear_programming/pdlp/pdlp_warm_start_data.hpp +++ b/cpp/include/cuopt/linear_programming/pdlp/pdlp_warm_start_data.hpp @@ -49,6 +49,7 @@ struct pdlp_warm_start_data_t { f_t last_restart_kkt_score_{-1}; f_t sum_solution_weight_{-1}; i_t iterations_since_last_restart_{-1}; + bool solved_by_pdlp_{false}; // Constructor when building it in the solution object pdlp_warm_start_data_t(rmm::device_uvector& current_primal_solution, @@ -67,7 +68,8 @@ struct pdlp_warm_start_data_t { f_t last_candidate_kkt_score, f_t last_restart_kkt_score, f_t sum_solution_weight, - i_t iterations_since_last_restart); + i_t iterations_since_last_restart, + bool solved_by_pdlp); // Empty constructor pdlp_warm_start_data_t(); @@ -104,6 +106,7 @@ struct pdlp_warm_start_data_view_t { f_t last_restart_kkt_score_{-1}; f_t sum_solution_weight_{-1}; i_t iterations_since_last_restart_{-1}; + bool solved_by_pdlp_{false}; }; } // namespace cuopt::linear_programming \ No newline at end of file diff --git a/cpp/include/cuopt/linear_programming/pdlp/solver_settings.hpp b/cpp/include/cuopt/linear_programming/pdlp/solver_settings.hpp index 9dcccf7a7..39dc78fb8 100644 --- a/cpp/include/cuopt/linear_programming/pdlp/solver_settings.hpp +++ b/cpp/include/cuopt/linear_programming/pdlp/solver_settings.hpp @@ -160,10 +160,23 @@ class pdlp_solver_settings_t { f_t last_candidate_kkt_score_, f_t last_restart_kkt_score_, f_t sum_solution_weight_, - i_t iterations_since_last_restart_); + i_t iterations_since_last_restart_, + bool solved_by_pdlp_); + + /** + * @brief Check if the pdlp warm start data is set + * + * @return true if the pdlp warm start data is set, false otherwise + */ + bool has_pdlp_warm_start_data() const; + /** * @brief Get the pdlp warm start data * + * @note PDLP warm start data is an optional field, it is not set by default. + * You need to make sure that the warm start data is set before calling this function. + * You can check if the warm start data is set by calling has_pdlp_warm_start_data(). + * * @return pdlp warm start data */ const pdlp_warm_start_data_t& get_pdlp_warm_start_data() const noexcept; @@ -216,7 +229,7 @@ class pdlp_solver_settings_t { /** Initial dual solution */ std::shared_ptr> initial_dual_solution_; // For the C++ interface - pdlp_warm_start_data_t pdlp_warm_start_data_; + std::optional> pdlp_warm_start_data_; // For the Cython interface pdlp_warm_start_data_view_t pdlp_warm_start_data_view_; diff --git a/cpp/include/cuopt/linear_programming/solve.hpp b/cpp/include/cuopt/linear_programming/solve.hpp index 880982ad9..899d867da 100644 --- a/cpp/include/cuopt/linear_programming/solve.hpp +++ b/cpp/include/cuopt/linear_programming/solve.hpp @@ -29,6 +29,11 @@ namespace cuopt::linear_programming { +namespace detail { +template +class problem_t; +} + /** * @brief Linear programming solve function. * @note Both primal and dual solutions are zero-initialized. For custom initialization, see @@ -54,7 +59,33 @@ optimization_problem_solution_t solve_lp( bool use_pdlp_solver_mode = true); /** - * @brief Linear programming solve function. + * @brief Linear programming solve function. Used in the context of a MIP when the input is a + * detail::problem_t. + * @note Both primal and dual solutions are zero-initialized. For custom initialization, see + * op_problem.initial_primal/dual_solution + * + * @tparam i_t Data type of indexes + * @tparam f_t Data type of the variables and their weights in the equations + * + * @param[in] op_problem An optimization_problem_t object with a + * representation of a linear program + * @param[in] settings A pdlp_solver_settings_t object with the settings for the PDLP + * solver. + * @param[in] use_pdlp_solver_modes If true, the PDLP hyperparameters coming from the + * pdlp_solver_mode are used (instead of the ones comming from a potential hyper-params file). + * @param[in] inside_mip If true, the problem is being solved in the context of a MIP. + * @return optimization_problem_solution_t owning container for the solver solution + */ +template +optimization_problem_solution_t solve_lp( + detail::problem_t& problem, + pdlp_solver_settings_t const& settings = pdlp_solver_settings_t{}, + bool use_pdlp_solver_mode = true, + bool inside_mip = false); + +/** + * @brief Linear programming solve function. This is a wrapper around the solve_lp function taking a + * detail::problem_t as input. * @note Both primal and dual solutions are zero-initialized. For custom initialization, see * op_problem.initial_primal/dual_solution * diff --git a/cpp/include/cuopt/linear_programming/solver_settings.hpp b/cpp/include/cuopt/linear_programming/solver_settings.hpp index 5512b9de1..211d3e0b9 100644 --- a/cpp/include/cuopt/linear_programming/solver_settings.hpp +++ b/cpp/include/cuopt/linear_programming/solver_settings.hpp @@ -82,7 +82,8 @@ class solver_settings_t { f_t last_candidate_kkt_score_, f_t last_restart_kkt_score_, f_t sum_solution_weight_, - i_t iterations_since_last_restart_); + i_t iterations_since_last_restart_, + bool solved_by_pdlp_); const rmm::device_uvector& get_initial_pdlp_primal_solution() const; const rmm::device_uvector& get_initial_pdlp_dual_solution() const; diff --git a/cpp/include/cuopt/linear_programming/utilities/cython_solve.hpp b/cpp/include/cuopt/linear_programming/utilities/cython_solve.hpp index cc30ff7a0..87bcf66ce 100644 --- a/cpp/include/cuopt/linear_programming/utilities/cython_solve.hpp +++ b/cpp/include/cuopt/linear_programming/utilities/cython_solve.hpp @@ -70,6 +70,12 @@ struct linear_programming_ret_t { double gap_; int nb_iterations_; double solve_time_; + // This parameter is stored twice in both the C++ and the Python layer: inside the solution object + // and in the warm start data It is required in the solution object to know if the problem was + // solved by PDLP or Dual Simplex, whether or not the warm start data was populated It is required + // in the warm start data as only this object and not the solution object is passed to the solver + // settings In this adapter between the C++ and the Python layer, we can carry the information + // through a single field bool solved_by_pdlp_; }; diff --git a/cpp/src/linear_programming/pdlp.cu b/cpp/src/linear_programming/pdlp.cu index 7acadae50..bbc57b04e 100644 --- a/cpp/src/linear_programming/pdlp.cu +++ b/cpp/src/linear_programming/pdlp.cu @@ -128,56 +128,65 @@ pdlp_solver_t::pdlp_solver_t(problem_t& op_problem, set_initial_dual_solution(dual_sol); } - if (settings.get_pdlp_warm_start_data().last_restart_duality_gap_dual_solution_.size() != 0) { - set_initial_primal_solution(settings.get_pdlp_warm_start_data().current_primal_solution_); - set_initial_dual_solution(settings.get_pdlp_warm_start_data().current_dual_solution_); - initial_step_size_ = settings.get_pdlp_warm_start_data().initial_step_size_; - initial_primal_weight_ = settings.get_pdlp_warm_start_data().initial_primal_weight_; - total_pdlp_iterations_ = settings.get_pdlp_warm_start_data().total_pdlp_iterations_; - pdhg_solver_.total_pdhg_iterations_ = - settings.get_pdlp_warm_start_data().total_pdhg_iterations_; - pdhg_solver_.get_d_total_pdhg_iterations().set_value_async( - settings.get_pdlp_warm_start_data().total_pdhg_iterations_, stream_view_); - restart_strategy_.last_candidate_kkt_score = - settings.get_pdlp_warm_start_data().last_candidate_kkt_score_; - restart_strategy_.last_restart_kkt_score = - settings.get_pdlp_warm_start_data().last_restart_kkt_score_; - raft::copy(restart_strategy_.weighted_average_solution_.sum_primal_solutions_.data(), - settings.get_pdlp_warm_start_data().sum_primal_solutions_.data(), - settings.get_pdlp_warm_start_data().sum_primal_solutions_.size(), - stream_view_); - raft::copy(restart_strategy_.weighted_average_solution_.sum_dual_solutions_.data(), - settings.get_pdlp_warm_start_data().sum_dual_solutions_.data(), - settings.get_pdlp_warm_start_data().sum_dual_solutions_.size(), - stream_view_); - raft::copy(unscaled_primal_avg_solution_.data(), - settings.get_pdlp_warm_start_data().initial_primal_average_.data(), - settings.get_pdlp_warm_start_data().initial_primal_average_.size(), - stream_view_); - raft::copy(unscaled_dual_avg_solution_.data(), - settings.get_pdlp_warm_start_data().initial_dual_average_.data(), - settings.get_pdlp_warm_start_data().initial_dual_average_.size(), - stream_view_); - raft::copy(pdhg_solver_.get_saddle_point_state().get_current_AtY().data(), - settings.get_pdlp_warm_start_data().current_ATY_.data(), - settings.get_pdlp_warm_start_data().current_ATY_.size(), - stream_view_); - raft::copy(restart_strategy_.last_restart_duality_gap_.primal_solution_.data(), - settings.get_pdlp_warm_start_data().last_restart_duality_gap_primal_solution_.data(), - settings.get_pdlp_warm_start_data().last_restart_duality_gap_primal_solution_.size(), - stream_view_); - raft::copy(restart_strategy_.last_restart_duality_gap_.dual_solution_.data(), - settings.get_pdlp_warm_start_data().last_restart_duality_gap_dual_solution_.data(), - settings.get_pdlp_warm_start_data().last_restart_duality_gap_dual_solution_.size(), - stream_view_); + if (settings.has_pdlp_warm_start_data()) { + const auto& warm_start_data = settings.get_pdlp_warm_start_data(); + if (!warm_start_data.solved_by_pdlp_) { + CUOPT_LOG_DEBUG( + "Warm start data coming from a solution which was not solved by PDLP, skipping warm start"); + } else if (pdlp_hyper_params::restart_strategy == + static_cast( + pdlp_restart_strategy_t::restart_strategy_t::TRUST_REGION_RESTART)) { + CUOPT_LOG_DEBUG( + "Trying to use warm start with trust region restart (neither Stable nor Fast1), skipping " + "warm start"); + } else { + set_initial_primal_solution(warm_start_data.current_primal_solution_); + set_initial_dual_solution(warm_start_data.current_dual_solution_); + initial_step_size_ = warm_start_data.initial_step_size_; + initial_primal_weight_ = warm_start_data.initial_primal_weight_; + total_pdlp_iterations_ = warm_start_data.total_pdlp_iterations_; + pdhg_solver_.total_pdhg_iterations_ = warm_start_data.total_pdhg_iterations_; + pdhg_solver_.get_d_total_pdhg_iterations().set_value_async( + warm_start_data.total_pdhg_iterations_, stream_view_); + restart_strategy_.last_candidate_kkt_score = warm_start_data.last_candidate_kkt_score_; + restart_strategy_.last_restart_kkt_score = warm_start_data.last_restart_kkt_score_; + raft::copy(restart_strategy_.weighted_average_solution_.sum_primal_solutions_.data(), + warm_start_data.sum_primal_solutions_.data(), + warm_start_data.sum_primal_solutions_.size(), + stream_view_); + raft::copy(restart_strategy_.weighted_average_solution_.sum_dual_solutions_.data(), + warm_start_data.sum_dual_solutions_.data(), + warm_start_data.sum_dual_solutions_.size(), + stream_view_); + raft::copy(unscaled_primal_avg_solution_.data(), + warm_start_data.initial_primal_average_.data(), + warm_start_data.initial_primal_average_.size(), + stream_view_); + raft::copy(unscaled_dual_avg_solution_.data(), + warm_start_data.initial_dual_average_.data(), + warm_start_data.initial_dual_average_.size(), + stream_view_); + raft::copy(pdhg_solver_.get_saddle_point_state().get_current_AtY().data(), + warm_start_data.current_ATY_.data(), + warm_start_data.current_ATY_.size(), + stream_view_); + raft::copy(restart_strategy_.last_restart_duality_gap_.primal_solution_.data(), + warm_start_data.last_restart_duality_gap_primal_solution_.data(), + warm_start_data.last_restart_duality_gap_primal_solution_.size(), + stream_view_); + raft::copy(restart_strategy_.last_restart_duality_gap_.dual_solution_.data(), + warm_start_data.last_restart_duality_gap_dual_solution_.data(), + warm_start_data.last_restart_duality_gap_dual_solution_.size(), + stream_view_); - const auto value = settings.get_pdlp_warm_start_data().sum_solution_weight_; - restart_strategy_.weighted_average_solution_.sum_primal_solution_weights_.set_value_async( - value, stream_view_); - restart_strategy_.weighted_average_solution_.sum_dual_solution_weights_.set_value_async( - value, stream_view_); - restart_strategy_.weighted_average_solution_.iterations_since_last_restart_ = - settings.get_pdlp_warm_start_data().iterations_since_last_restart_; + const auto value = warm_start_data.sum_solution_weight_; + restart_strategy_.weighted_average_solution_.sum_primal_solution_weights_.set_value_async( + value, stream_view_); + restart_strategy_.weighted_average_solution_.sum_dual_solution_weights_.set_value_async( + value, stream_view_); + restart_strategy_.weighted_average_solution_.iterations_since_last_restart_ = + warm_start_data.iterations_since_last_restart_; + } } // Checks performed below are assert only best_primal_quality_so_far_.primal_objective = (op_problem_scaled_.maximize) @@ -323,7 +332,7 @@ std::optional> pdlp_solver_t pdhg_solver_, pdhg_solver_.get_primal_solution(), pdhg_solver_.get_dual_solution(), - get_filled_warmed_start_data(), + get_filled_warmed_start_data(false), pdlp_termination_status_t::ConcurrentLimit); } @@ -466,7 +475,8 @@ void pdlp_solver_t::record_best_primal_so_far( } template -pdlp_warm_start_data_t pdlp_solver_t::get_filled_warmed_start_data() +pdlp_warm_start_data_t pdlp_solver_t::get_filled_warmed_start_data( + bool solved_by_pdlp) { return pdlp_warm_start_data_t( pdhg_solver_.get_primal_solution(), @@ -485,7 +495,8 @@ pdlp_warm_start_data_t pdlp_solver_t::get_filled_warmed_star restart_strategy_.last_candidate_kkt_score, restart_strategy_.last_restart_kkt_score, restart_strategy_.weighted_average_solution_.sum_primal_solution_weights_.value(stream_view_), - restart_strategy_.weighted_average_solution_.iterations_since_last_restart_); + restart_strategy_.weighted_average_solution_.iterations_since_last_restart_, + solved_by_pdlp); } template @@ -545,9 +556,14 @@ std::optional> pdlp_solver_t #ifdef PDLP_VERBOSE_MODE RAFT_CUDA_TRY(cudaDeviceSynchronize()); printf("Termination criteria current\n"); - current_termination_strategy_.print_termination_criteria(); + const auto current_time = std::chrono::high_resolution_clock::now(); + const f_t elapsed = + std::chrono::duration_cast(current_time - start_time).count() / + 1000.0; + current_termination_strategy_.print_termination_criteria(total_pdlp_iterations_, elapsed); RAFT_CUDA_TRY(cudaDeviceSynchronize()); #endif + pdlp_termination_status_t termination_current = current_termination_strategy_.evaluate_termination_criteria( pdhg_solver_, @@ -559,7 +575,7 @@ std::optional> pdlp_solver_t #ifdef PDLP_VERBOSE_MODE RAFT_CUDA_TRY(cudaDeviceSynchronize()); std::cout << "Termination criteria average:" << std::endl; - average_termination_strategy_.print_termination_criteria(); + average_termination_strategy_.print_termination_criteria(total_pdlp_iterations_, elapsed); RAFT_CUDA_TRY(cudaDeviceSynchronize()); #endif @@ -984,6 +1000,8 @@ template optimization_problem_solution_t pdlp_solver_t::run_solver( const std::chrono::high_resolution_clock::time_point& start_time) { + raft::common::nvtx::range fun_scope("Run PDLP Solver"); + bool verbose; #ifdef PDLP_VERBOSE_MODE verbose = true; @@ -1071,8 +1089,7 @@ optimization_problem_solution_t pdlp_solver_t::run_solver( raft::print_device_vector("Initial dual_step_size", dual_step_size_.data(), 1, std::cout); } - bool warm_start_was_given = - settings_.get_pdlp_warm_start_data().last_restart_duality_gap_dual_solution_.size() != 0; + bool warm_start_was_given = settings_.has_pdlp_warm_start_data(); if (!inside_mip_) { CUOPT_LOG_INFO( diff --git a/cpp/src/linear_programming/pdlp.cuh b/cpp/src/linear_programming/pdlp.cuh index 10a028f26..e837fa368 100644 --- a/cpp/src/linear_programming/pdlp.cuh +++ b/cpp/src/linear_programming/pdlp.cuh @@ -171,7 +171,7 @@ class pdlp_solver_t { // Intentionnaly take a copy to avoid an unintentional modification in the calling context const pdlp_solver_settings_t settings_; - pdlp_warm_start_data_t get_filled_warmed_start_data(); + pdlp_warm_start_data_t get_filled_warmed_start_data(bool solved_by_pdlp = true); // Initial scaling strategy detail::pdlp_initial_scaling_strategy_t initial_scaling_strategy_; diff --git a/cpp/src/linear_programming/pdlp_warm_start_data.cu b/cpp/src/linear_programming/pdlp_warm_start_data.cu index 6d8444ea3..e3a0fff4d 100644 --- a/cpp/src/linear_programming/pdlp_warm_start_data.cu +++ b/cpp/src/linear_programming/pdlp_warm_start_data.cu @@ -46,7 +46,8 @@ pdlp_warm_start_data_t::pdlp_warm_start_data_t( f_t last_candidate_kkt_score, f_t last_restart_kkt_score, f_t sum_solution_weight, - i_t iterations_since_last_restart) + i_t iterations_since_last_restart, + bool solved_by_pdlp) : // When initially creating this object, we can't move neither the primal/dual solution nor // the average since they might be used as a solution by the solution object, they have to be // copied @@ -66,7 +67,8 @@ pdlp_warm_start_data_t::pdlp_warm_start_data_t( last_candidate_kkt_score_(last_candidate_kkt_score), last_restart_kkt_score_(last_restart_kkt_score), sum_solution_weight_(sum_solution_weight), - iterations_since_last_restart_(iterations_since_last_restart) + iterations_since_last_restart_(iterations_since_last_restart), + solved_by_pdlp_(solved_by_pdlp) { check_sizes(); } @@ -107,7 +109,8 @@ pdlp_warm_start_data_t::pdlp_warm_start_data_t( last_candidate_kkt_score_(other.last_candidate_kkt_score_), last_restart_kkt_score_(other.last_restart_kkt_score_), sum_solution_weight_(other.sum_solution_weight_), - iterations_since_last_restart_(other.iterations_since_last_restart_) + iterations_since_last_restart_(other.iterations_since_last_restart_), + solved_by_pdlp_(other.solved_by_pdlp_) { raft::copy(current_primal_solution_.data(), other.current_primal_solution_.data(), @@ -168,7 +171,8 @@ pdlp_warm_start_data_t::pdlp_warm_start_data_t(const pdlp_warm_start_d last_candidate_kkt_score_(other.last_candidate_kkt_score_), last_restart_kkt_score_(other.last_restart_kkt_score_), sum_solution_weight_(other.sum_solution_weight_), - iterations_since_last_restart_(other.iterations_since_last_restart_) + iterations_since_last_restart_(other.iterations_since_last_restart_), + solved_by_pdlp_(other.solved_by_pdlp_) { check_sizes(); } diff --git a/cpp/src/linear_programming/solve.cu b/cpp/src/linear_programming/solve.cu index c3ba4f2c7..0f90665d8 100644 --- a/cpp/src/linear_programming/solve.cu +++ b/cpp/src/linear_programming/solve.cu @@ -295,7 +295,8 @@ optimization_problem_solution_t convert_dual_simplex_sol( template std::tuple, dual_simplex::lp_status_t, f_t, f_t, f_t> run_dual_simplex(dual_simplex::user_problem_t& user_problem, - pdlp_solver_settings_t const& settings) + pdlp_solver_settings_t const& settings, + bool inside_mip) { auto start_solver = std::chrono::high_resolution_clock::now(); @@ -306,6 +307,7 @@ run_dual_simplex(dual_simplex::user_problem_t& user_problem, dual_simplex_settings.time_limit = settings.time_limit; dual_simplex_settings.iteration_limit = settings.iteration_limit; dual_simplex_settings.concurrent_halt = settings.concurrent_halt; + dual_simplex_settings.inside_mip = inside_mip; if (dual_simplex_settings.concurrent_halt != nullptr) { // Don't show the dual simplex log in concurrent mode. Show the PDLP log instead dual_simplex_settings.log.log = false; @@ -332,12 +334,14 @@ run_dual_simplex(dual_simplex::user_problem_t& user_problem, template optimization_problem_solution_t run_dual_simplex( - detail::problem_t& problem, pdlp_solver_settings_t const& settings) + detail::problem_t& problem, + pdlp_solver_settings_t const& settings, + bool inside_mip) { // Convert data structures to dual simplex format and back dual_simplex::user_problem_t dual_simplex_problem = - cuopt_problem_to_simplex_problem(problem); - auto sol_dual_simplex = run_dual_simplex(dual_simplex_problem, settings); + cuopt_problem_to_simplex_problem(problem, inside_mip); + auto sol_dual_simplex = run_dual_simplex(dual_simplex_problem, settings, inside_mip); return convert_dual_simplex_sol(problem, std::get<0>(sol_dual_simplex), std::get<1>(sol_dual_simplex), @@ -350,7 +354,8 @@ template static optimization_problem_solution_t run_pdlp_solver( detail::problem_t& problem, pdlp_solver_settings_t const& settings, - const std::chrono::high_resolution_clock::time_point& start_time) + const std::chrono::high_resolution_clock::time_point& start_time, + bool inside_mip) { if (problem.n_constraints == 0) { CUOPT_LOG_INFO("No constraints in the problem: PDLP can't be run, use Dual Simplex instead."); @@ -358,16 +363,18 @@ static optimization_problem_solution_t run_pdlp_solver( problem.handle_ptr->get_stream()}; } detail::pdlp_solver_t solver(problem, settings); + solver.set_inside_mip(inside_mip); return solver.run_solver(start_time); } template optimization_problem_solution_t run_pdlp(detail::problem_t& problem, - pdlp_solver_settings_t const& settings) + pdlp_solver_settings_t const& settings, + bool inside_mip) { auto start_solver = std::chrono::high_resolution_clock::now(); f_t start_time = dual_simplex::tic(); - auto sol = run_pdlp_solver(problem, settings, start_solver); + auto sol = run_pdlp_solver(problem, settings, start_solver, inside_mip); auto end = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration_cast(end - start_solver); sol.set_solve_time(duration.count() / 1000.0); @@ -455,26 +462,26 @@ void run_dual_simplex_thread( pdlp_solver_settings_t const& settings, std::unique_ptr< std::tuple, dual_simplex::lp_status_t, f_t, f_t, f_t>>& - sol_ptr) + sol_ptr, + bool inside_mip) { // We will return the solution from the thread as a unique_ptr sol_ptr = std::make_unique< std::tuple, dual_simplex::lp_status_t, f_t, f_t, f_t>>( - run_dual_simplex(problem, settings)); + run_dual_simplex(problem, settings, inside_mip)); } template optimization_problem_solution_t run_concurrent( - optimization_problem_t& op_problem, detail::problem_t& problem, - pdlp_solver_settings_t const& settings) + pdlp_solver_settings_t const& settings, + bool inside_mip) { CUOPT_LOG_INFO("Running concurrent\n"); f_t start_time = dual_simplex::tic(); // Copy the settings so that we can set the concurrent halt pointer - pdlp_solver_settings_t settings_pdlp(settings, - op_problem.get_handle_ptr()->get_stream()); + pdlp_solver_settings_t settings_pdlp(settings, problem.handle_ptr->get_stream()); // Set the concurrent halt pointer global_concurrent_halt.store(0, std::memory_order_release); @@ -484,7 +491,7 @@ optimization_problem_solution_t run_concurrent( // Otherwise, CUDA API calls to the problem stream may occur in both threads and throw graph // capture off dual_simplex::user_problem_t dual_simplex_problem = - cuopt_problem_to_simplex_problem(problem); + cuopt_problem_to_simplex_problem(problem, inside_mip); // Create a thread for dual simplex std::unique_ptr< std::tuple, dual_simplex::lp_status_t, f_t, f_t, f_t>> @@ -492,10 +499,11 @@ optimization_problem_solution_t run_concurrent( std::thread dual_simplex_thread(run_dual_simplex_thread, std::ref(dual_simplex_problem), std::ref(settings_pdlp), - std::ref(sol_dual_simplex_ptr)); + std::ref(sol_dual_simplex_ptr), + inside_mip); // Run pdlp in the main thread - auto sol_pdlp = run_pdlp(problem, settings_pdlp); + auto sol_pdlp = run_pdlp(problem, settings_pdlp, inside_mip); // Wait for dual simplex thread to finish dual_simplex_thread.join(); @@ -515,7 +523,7 @@ optimization_problem_solution_t run_concurrent( sol_dual_simplex.get_termination_status() == pdlp_termination_status_t::PrimalInfeasible || sol_dual_simplex.get_termination_status() == pdlp_termination_status_t::DualInfeasible) { CUOPT_LOG_INFO("Solved with dual simplex"); - sol_pdlp.copy_from(op_problem.get_handle_ptr(), sol_dual_simplex); + sol_pdlp.copy_from(problem.handle_ptr, sol_dual_simplex); sol_pdlp.set_solve_time(end_time); CUOPT_LOG_INFO("Status: %s Objective: %.8e Iterations: %d Time: %.3fs", sol_pdlp.get_termination_status_string().c_str(), @@ -537,24 +545,24 @@ optimization_problem_solution_t run_concurrent( template optimization_problem_solution_t solve_lp_with_method( - optimization_problem_t& op_problem, detail::problem_t& problem, - pdlp_solver_settings_t const& settings) + pdlp_solver_settings_t const& settings, + bool inside_mip) { if (settings.method == method_t::DualSimplex) { - return run_dual_simplex(problem, settings); + return run_dual_simplex(problem, settings, inside_mip); } else if (settings.method == method_t::Concurrent) { - return run_concurrent(op_problem, problem, settings); + return run_concurrent(problem, settings, inside_mip); } else { - return run_pdlp(problem, settings); + return run_pdlp(problem, settings, inside_mip); } } template -optimization_problem_solution_t solve_lp(optimization_problem_t& op_problem, +optimization_problem_solution_t solve_lp(detail::problem_t& problem, pdlp_solver_settings_t const& settings, - bool problem_checking, - bool use_pdlp_solver_mode) + bool use_pdlp_solver_mode, + bool inside_mip) { try { // Create log stream for file logging and add it to default logger @@ -562,17 +570,14 @@ optimization_problem_solution_t solve_lp(optimization_problem_t::check_problem_representation(op_problem); - problem_checking_t::check_initial_solution_representation(op_problem, settings); - } - detail::problem_t problem(op_problem); + cuopt_func_call(problem.check_problem_representation(true, inside_mip)); + using problem_checking_t = problem_checking_t; // Can't use "," inside a macro + cuopt_func_call(problem_checking_t::check_initial_solution_representation(problem, settings)); + CUOPT_LOG_INFO( "Solving a problem with %d constraints %d variables (%d integers) and %d nonzeros", problem.n_constraints, @@ -591,16 +596,42 @@ optimization_problem_solution_t solve_lp(optimization_problem_tget_stream()); + setup_device_symbols(problem.handle_ptr->get_stream()); - auto sol = solve_lp_with_method(op_problem, problem, settings); + auto sol = solve_lp_with_method(problem, settings, inside_mip); if (settings.sol_file != "") { CUOPT_LOG_INFO("Writing solution to file %s", settings.sol_file.c_str()); - sol.write_to_sol_file(settings.sol_file, op_problem.get_handle_ptr()->get_stream()); + sol.write_to_sol_file(settings.sol_file, problem.handle_ptr->get_stream()); } return sol; + } catch (const cuopt::logic_error& e) { + CUOPT_LOG_ERROR("Error in solve_lp: %s", e.what()); + return optimization_problem_solution_t{e, problem.handle_ptr->get_stream()}; + } catch (const std::bad_alloc& e) { + CUOPT_LOG_ERROR("Error in solve_lp: %s", e.what()); + return optimization_problem_solution_t{ + cuopt::logic_error("Memory allocation failed", cuopt::error_type_t::RuntimeError), + problem.handle_ptr->get_stream()}; + } +} + +template +optimization_problem_solution_t solve_lp(optimization_problem_t& op_problem, + pdlp_solver_settings_t const& settings, + bool problem_checking, + bool use_pdlp_solver_mode) +{ + try { + if (problem_checking) { + raft::common::nvtx::range fun_scope("Check problem representation"); + problem_checking_t::check_problem_representation(op_problem); + problem_checking_t::check_initial_solution_representation(op_problem, settings); + } + detail::problem_t problem(op_problem); + const bool inside_mip = false; + return solve_lp(problem, settings, use_pdlp_solver_mode, inside_mip); } catch (const cuopt::logic_error& e) { CUOPT_LOG_ERROR("Error in solve_lp: %s", e.what()); return optimization_problem_solution_t{e, op_problem.get_handle_ptr()->get_stream()}; @@ -700,7 +731,11 @@ optimization_problem_solution_t solve_lp( pdlp_solver_settings_t const& settings, \ bool problem_checking, \ bool use_pdlp_solver_mode); \ - \ + template optimization_problem_solution_t solve_lp( \ + detail::problem_t& problem, \ + pdlp_solver_settings_t const& settings, \ + bool use_pdlp_solver_mode, \ + bool inside_mip); \ template optimization_problem_solution_t solve_lp( \ raft::handle_t const* handle_ptr, \ const cuopt::mps_parser::mps_data_model_t& mps_data_model, \ diff --git a/cpp/src/linear_programming/solver_settings.cu b/cpp/src/linear_programming/solver_settings.cu index b8b555982..a82639fc1 100644 --- a/cpp/src/linear_programming/solver_settings.cu +++ b/cpp/src/linear_programming/solver_settings.cu @@ -22,6 +22,7 @@ #include #include #include +#include #include @@ -47,9 +48,12 @@ pdlp_solver_settings_t::pdlp_solver_settings_t(const pdlp_solver_setti crossover(other.crossover), save_best_primal_so_far(other.save_best_primal_so_far), first_primal_feasible(other.first_primal_feasible), - pdlp_warm_start_data_(other.pdlp_warm_start_data_, stream_view), concurrent_halt(other.concurrent_halt) { + if (other.pdlp_warm_start_data_.has_value()) { + pdlp_warm_start_data_ = std::make_optional>( + other.pdlp_warm_start_data_.value(), stream_view); + } } template @@ -96,81 +100,81 @@ void pdlp_solver_settings_t::set_pdlp_warm_start_data( { pdlp_warm_start_data_ = std::move(pdlp_warm_start_data_view); + auto& pdlp_warm_start_data = pdlp_warm_start_data_.value(); + // A var_mapping was given if (var_mapping.size() != 0) { // If less variables, scatter using the passed argument and reduce the size of all primal // related vectors if (var_mapping.size() < - pdlp_warm_start_data_.last_restart_duality_gap_primal_solution_.size()) { + pdlp_warm_start_data.last_restart_duality_gap_primal_solution_.size()) { thrust::scatter(rmm::exec_policy(var_mapping.stream()), - pdlp_warm_start_data_.current_primal_solution_.begin(), - pdlp_warm_start_data_.current_primal_solution_.end(), + pdlp_warm_start_data.current_primal_solution_.begin(), + pdlp_warm_start_data.current_primal_solution_.end(), var_mapping.begin(), - pdlp_warm_start_data_.current_primal_solution_.begin()); + pdlp_warm_start_data.current_primal_solution_.begin()); thrust::scatter(rmm::exec_policy(var_mapping.stream()), - pdlp_warm_start_data_.initial_primal_average_.begin(), - pdlp_warm_start_data_.initial_primal_average_.end(), + pdlp_warm_start_data.initial_primal_average_.begin(), + pdlp_warm_start_data.initial_primal_average_.end(), var_mapping.begin(), - pdlp_warm_start_data_.initial_primal_average_.begin()); + pdlp_warm_start_data.initial_primal_average_.begin()); thrust::scatter(rmm::exec_policy(var_mapping.stream()), - pdlp_warm_start_data_.current_ATY_.begin(), - pdlp_warm_start_data_.current_ATY_.end(), + pdlp_warm_start_data.current_ATY_.begin(), + pdlp_warm_start_data.current_ATY_.end(), var_mapping.begin(), - pdlp_warm_start_data_.current_ATY_.begin()); + pdlp_warm_start_data.current_ATY_.begin()); thrust::scatter(rmm::exec_policy(var_mapping.stream()), - pdlp_warm_start_data_.sum_primal_solutions_.begin(), - pdlp_warm_start_data_.sum_primal_solutions_.end(), + pdlp_warm_start_data.sum_primal_solutions_.begin(), + pdlp_warm_start_data.sum_primal_solutions_.end(), var_mapping.begin(), - pdlp_warm_start_data_.sum_primal_solutions_.begin()); + pdlp_warm_start_data.sum_primal_solutions_.begin()); thrust::scatter(rmm::exec_policy(var_mapping.stream()), - pdlp_warm_start_data_.last_restart_duality_gap_primal_solution_.begin(), - pdlp_warm_start_data_.last_restart_duality_gap_primal_solution_.end(), + pdlp_warm_start_data.last_restart_duality_gap_primal_solution_.begin(), + pdlp_warm_start_data.last_restart_duality_gap_primal_solution_.end(), var_mapping.begin(), - pdlp_warm_start_data_.last_restart_duality_gap_primal_solution_.begin()); + pdlp_warm_start_data.last_restart_duality_gap_primal_solution_.begin()); - pdlp_warm_start_data_.current_primal_solution_.resize(var_mapping.size(), - var_mapping.stream()); - pdlp_warm_start_data_.initial_primal_average_.resize(var_mapping.size(), + pdlp_warm_start_data.current_primal_solution_.resize(var_mapping.size(), var_mapping.stream()); - pdlp_warm_start_data_.current_ATY_.resize(var_mapping.size(), var_mapping.stream()); - pdlp_warm_start_data_.sum_primal_solutions_.resize(var_mapping.size(), var_mapping.stream()); - pdlp_warm_start_data_.last_restart_duality_gap_primal_solution_.resize(var_mapping.size(), - var_mapping.stream()); + pdlp_warm_start_data.initial_primal_average_.resize(var_mapping.size(), var_mapping.stream()); + pdlp_warm_start_data.current_ATY_.resize(var_mapping.size(), var_mapping.stream()); + pdlp_warm_start_data.sum_primal_solutions_.resize(var_mapping.size(), var_mapping.stream()); + pdlp_warm_start_data.last_restart_duality_gap_primal_solution_.resize(var_mapping.size(), + var_mapping.stream()); } else if (var_mapping.size() > - pdlp_warm_start_data_.last_restart_duality_gap_primal_solution_.size()) { + pdlp_warm_start_data.last_restart_duality_gap_primal_solution_.size()) { const auto previous_size = - pdlp_warm_start_data_.last_restart_duality_gap_primal_solution_.size(); + pdlp_warm_start_data.last_restart_duality_gap_primal_solution_.size(); // If more variables just pad with 0s - pdlp_warm_start_data_.current_primal_solution_.resize(var_mapping.size(), - var_mapping.stream()); - pdlp_warm_start_data_.initial_primal_average_.resize(var_mapping.size(), + pdlp_warm_start_data.current_primal_solution_.resize(var_mapping.size(), var_mapping.stream()); - pdlp_warm_start_data_.current_ATY_.resize(var_mapping.size(), var_mapping.stream()); - pdlp_warm_start_data_.sum_primal_solutions_.resize(var_mapping.size(), var_mapping.stream()); - pdlp_warm_start_data_.last_restart_duality_gap_primal_solution_.resize(var_mapping.size(), - var_mapping.stream()); + pdlp_warm_start_data.initial_primal_average_.resize(var_mapping.size(), var_mapping.stream()); + pdlp_warm_start_data.current_ATY_.resize(var_mapping.size(), var_mapping.stream()); + pdlp_warm_start_data.sum_primal_solutions_.resize(var_mapping.size(), var_mapping.stream()); + pdlp_warm_start_data.last_restart_duality_gap_primal_solution_.resize(var_mapping.size(), + var_mapping.stream()); thrust::fill(rmm::exec_policy(var_mapping.stream()), - pdlp_warm_start_data_.current_primal_solution_.begin() + previous_size, - pdlp_warm_start_data_.current_primal_solution_.end(), + pdlp_warm_start_data.current_primal_solution_.begin() + previous_size, + pdlp_warm_start_data.current_primal_solution_.end(), f_t(0)); thrust::fill(rmm::exec_policy(var_mapping.stream()), - pdlp_warm_start_data_.initial_primal_average_.begin() + previous_size, - pdlp_warm_start_data_.initial_primal_average_.end(), + pdlp_warm_start_data.initial_primal_average_.begin() + previous_size, + pdlp_warm_start_data.initial_primal_average_.end(), f_t(0)); thrust::fill(rmm::exec_policy(var_mapping.stream()), - pdlp_warm_start_data_.current_ATY_.begin() + previous_size, - pdlp_warm_start_data_.current_ATY_.end(), + pdlp_warm_start_data.current_ATY_.begin() + previous_size, + pdlp_warm_start_data.current_ATY_.end(), f_t(0)); thrust::fill(rmm::exec_policy(var_mapping.stream()), - pdlp_warm_start_data_.sum_primal_solutions_.begin() + previous_size, - pdlp_warm_start_data_.sum_primal_solutions_.end(), + pdlp_warm_start_data.sum_primal_solutions_.begin() + previous_size, + pdlp_warm_start_data.sum_primal_solutions_.end(), f_t(0)); thrust::fill( rmm::exec_policy(var_mapping.stream()), - pdlp_warm_start_data_.last_restart_duality_gap_primal_solution_.begin() + previous_size, - pdlp_warm_start_data_.last_restart_duality_gap_primal_solution_.end(), + pdlp_warm_start_data.last_restart_duality_gap_primal_solution_.begin() + previous_size, + pdlp_warm_start_data.last_restart_duality_gap_primal_solution_.end(), f_t(0)); } } @@ -180,67 +184,67 @@ void pdlp_solver_settings_t::set_pdlp_warm_start_data( // If less variables, scatter using the passed argument and reduce the size of all dual related // vectors if (constraint_mapping.size() < - pdlp_warm_start_data_.last_restart_duality_gap_dual_solution_.size()) { + pdlp_warm_start_data.last_restart_duality_gap_dual_solution_.size()) { thrust::scatter(rmm::exec_policy(constraint_mapping.stream()), - pdlp_warm_start_data_.current_dual_solution_.begin(), - pdlp_warm_start_data_.current_dual_solution_.end(), + pdlp_warm_start_data.current_dual_solution_.begin(), + pdlp_warm_start_data.current_dual_solution_.end(), constraint_mapping.begin(), - pdlp_warm_start_data_.current_dual_solution_.begin()); + pdlp_warm_start_data.current_dual_solution_.begin()); thrust::scatter(rmm::exec_policy(constraint_mapping.stream()), - pdlp_warm_start_data_.initial_dual_average_.begin(), - pdlp_warm_start_data_.initial_dual_average_.end(), + pdlp_warm_start_data.initial_dual_average_.begin(), + pdlp_warm_start_data.initial_dual_average_.end(), constraint_mapping.begin(), - pdlp_warm_start_data_.initial_dual_average_.begin()); + pdlp_warm_start_data.initial_dual_average_.begin()); thrust::scatter(rmm::exec_policy(constraint_mapping.stream()), - pdlp_warm_start_data_.sum_dual_solutions_.begin(), - pdlp_warm_start_data_.sum_dual_solutions_.end(), + pdlp_warm_start_data.sum_dual_solutions_.begin(), + pdlp_warm_start_data.sum_dual_solutions_.end(), constraint_mapping.begin(), - pdlp_warm_start_data_.sum_dual_solutions_.begin()); + pdlp_warm_start_data.sum_dual_solutions_.begin()); thrust::scatter(rmm::exec_policy(constraint_mapping.stream()), - pdlp_warm_start_data_.last_restart_duality_gap_dual_solution_.begin(), - pdlp_warm_start_data_.last_restart_duality_gap_dual_solution_.end(), + pdlp_warm_start_data.last_restart_duality_gap_dual_solution_.begin(), + pdlp_warm_start_data.last_restart_duality_gap_dual_solution_.end(), constraint_mapping.begin(), - pdlp_warm_start_data_.last_restart_duality_gap_dual_solution_.begin()); + pdlp_warm_start_data.last_restart_duality_gap_dual_solution_.begin()); - pdlp_warm_start_data_.current_dual_solution_.resize(constraint_mapping.size(), - constraint_mapping.stream()); - pdlp_warm_start_data_.initial_dual_average_.resize(constraint_mapping.size(), + pdlp_warm_start_data.current_dual_solution_.resize(constraint_mapping.size(), constraint_mapping.stream()); - pdlp_warm_start_data_.sum_dual_solutions_.resize(constraint_mapping.size(), - constraint_mapping.stream()); - pdlp_warm_start_data_.last_restart_duality_gap_dual_solution_.resize( + pdlp_warm_start_data.initial_dual_average_.resize(constraint_mapping.size(), + constraint_mapping.stream()); + pdlp_warm_start_data.sum_dual_solutions_.resize(constraint_mapping.size(), + constraint_mapping.stream()); + pdlp_warm_start_data.last_restart_duality_gap_dual_solution_.resize( constraint_mapping.size(), constraint_mapping.stream()); } else if (constraint_mapping.size() > - pdlp_warm_start_data_.last_restart_duality_gap_dual_solution_.size()) { + pdlp_warm_start_data.last_restart_duality_gap_dual_solution_.size()) { const auto previous_size = - pdlp_warm_start_data_.last_restart_duality_gap_dual_solution_.size(); + pdlp_warm_start_data.last_restart_duality_gap_dual_solution_.size(); // If more variables just pad with 0s - pdlp_warm_start_data_.current_dual_solution_.resize(constraint_mapping.size(), - constraint_mapping.stream()); - pdlp_warm_start_data_.initial_dual_average_.resize(constraint_mapping.size(), + pdlp_warm_start_data.current_dual_solution_.resize(constraint_mapping.size(), constraint_mapping.stream()); - pdlp_warm_start_data_.sum_dual_solutions_.resize(constraint_mapping.size(), - constraint_mapping.stream()); - pdlp_warm_start_data_.last_restart_duality_gap_dual_solution_.resize( + pdlp_warm_start_data.initial_dual_average_.resize(constraint_mapping.size(), + constraint_mapping.stream()); + pdlp_warm_start_data.sum_dual_solutions_.resize(constraint_mapping.size(), + constraint_mapping.stream()); + pdlp_warm_start_data.last_restart_duality_gap_dual_solution_.resize( constraint_mapping.size(), constraint_mapping.stream()); thrust::fill(rmm::exec_policy(constraint_mapping.stream()), - pdlp_warm_start_data_.current_dual_solution_.begin() + previous_size, - pdlp_warm_start_data_.current_dual_solution_.end(), + pdlp_warm_start_data.current_dual_solution_.begin() + previous_size, + pdlp_warm_start_data.current_dual_solution_.end(), f_t(0)); thrust::fill(rmm::exec_policy(constraint_mapping.stream()), - pdlp_warm_start_data_.initial_dual_average_.begin() + previous_size, - pdlp_warm_start_data_.initial_dual_average_.end(), + pdlp_warm_start_data.initial_dual_average_.begin() + previous_size, + pdlp_warm_start_data.initial_dual_average_.end(), f_t(0)); thrust::fill(rmm::exec_policy(constraint_mapping.stream()), - pdlp_warm_start_data_.sum_dual_solutions_.begin() + previous_size, - pdlp_warm_start_data_.sum_dual_solutions_.end(), + pdlp_warm_start_data.sum_dual_solutions_.begin() + previous_size, + pdlp_warm_start_data.sum_dual_solutions_.end(), f_t(0)); thrust::fill( rmm::exec_policy(constraint_mapping.stream()), - pdlp_warm_start_data_.last_restart_duality_gap_dual_solution_.begin() + previous_size, - pdlp_warm_start_data_.last_restart_duality_gap_dual_solution_.end(), + pdlp_warm_start_data.last_restart_duality_gap_dual_solution_.begin() + previous_size, + pdlp_warm_start_data.last_restart_duality_gap_dual_solution_.end(), f_t(0)); } } @@ -266,7 +270,8 @@ void pdlp_solver_settings_t::set_pdlp_warm_start_data( f_t last_candidate_kkt_score, f_t last_restart_kkt_score, f_t sum_solution_weight, - i_t iterations_since_last_restart) + i_t iterations_since_last_restart, + bool solved_by_pdlp) { cuopt_expects(current_primal_solution != nullptr, error_type_t::ValidationError, @@ -321,6 +326,7 @@ void pdlp_solver_settings_t::set_pdlp_warm_start_data( pdlp_warm_start_data_view_.last_restart_kkt_score_ = last_restart_kkt_score; pdlp_warm_start_data_view_.sum_solution_weight_ = sum_solution_weight; pdlp_warm_start_data_view_.iterations_since_last_restart_ = iterations_since_last_restart; + pdlp_warm_start_data_view_.solved_by_pdlp_ = solved_by_pdlp; } template @@ -354,17 +360,27 @@ bool pdlp_solver_settings_t::has_initial_dual_solution() const return initial_dual_solution_.get() != nullptr; } +template +bool pdlp_solver_settings_t::has_pdlp_warm_start_data() const +{ + return pdlp_warm_start_data_.has_value(); +} + template const pdlp_warm_start_data_t& pdlp_solver_settings_t::get_pdlp_warm_start_data() const noexcept { - return pdlp_warm_start_data_; + cuopt_assert(pdlp_warm_start_data_.has_value(), + "PDLP warm start data was not set, but accessed!"); + return pdlp_warm_start_data_.value(); } template pdlp_warm_start_data_t& pdlp_solver_settings_t::get_pdlp_warm_start_data() { - return pdlp_warm_start_data_; + cuopt_assert(pdlp_warm_start_data_.has_value(), + "PDLP warm start data was not set, but accessed!"); + return pdlp_warm_start_data_.value(); } template diff --git a/cpp/src/linear_programming/translate.hpp b/cpp/src/linear_programming/translate.hpp index 6731be6ec..59750623f 100644 --- a/cpp/src/linear_programming/translate.hpp +++ b/cpp/src/linear_programming/translate.hpp @@ -28,7 +28,7 @@ namespace cuopt::linear_programming { template static dual_simplex::user_problem_t cuopt_problem_to_simplex_problem( - detail::problem_t& model) + detail::problem_t& model, bool inside_mip) { dual_simplex::user_problem_t user_problem; @@ -84,13 +84,13 @@ static dual_simplex::user_problem_t cuopt_problem_to_simplex_problem( user_problem.lower = cuopt::host_copy(model.variable_lower_bounds); user_problem.upper = cuopt::host_copy(model.variable_upper_bounds); user_problem.problem_name = model.original_problem_ptr->get_problem_name(); - if (model.row_names.size() > 0) { + if (model.row_names.size() > 0 && !inside_mip) { user_problem.row_names.resize(m); for (int i = 0; i < m; ++i) { user_problem.row_names[i] = model.row_names[i]; } } - if (model.var_names.size() > 0) { + if (model.var_names.size() > 0 && !inside_mip) { user_problem.col_names.resize(n); for (int j = 0; j < n; ++j) { user_problem.col_names[j] = model.var_names[j]; diff --git a/cpp/src/linear_programming/utilities/problem_checking.cu b/cpp/src/linear_programming/utilities/problem_checking.cu index b7104fd8f..8b2c7d0d5 100644 --- a/cpp/src/linear_programming/utilities/problem_checking.cu +++ b/cpp/src/linear_programming/utilities/problem_checking.cu @@ -61,37 +61,34 @@ void problem_checking_t::check_csr_representation( template void problem_checking_t::check_initial_primal_representation( - const optimization_problem_t& op_problem, + const rmm::device_uvector& objective_coefficients, const rmm::device_uvector& primal_initial_solution) { // Inital solution check if set if (!primal_initial_solution.is_empty()) { cuopt_expects( - (primal_initial_solution.size() == op_problem.get_objective_coefficients().size()), + (primal_initial_solution.size() == objective_coefficients.size()), error_type_t::ValidationError, "Sizes for vectors related to the variables are not the same. The initial primal variable " "has size %zu, while objective vector has size %zu.", primal_initial_solution.size(), - op_problem.get_objective_coefficients().size()); + objective_coefficients.size()); } } template void problem_checking_t::check_initial_dual_representation( - const optimization_problem_t& op_problem, + const rmm::device_uvector& constraints, const rmm::device_uvector& dual_initial_solution) { if (!dual_initial_solution.is_empty()) { - const std::size_t n_constraints = (op_problem.get_constraint_lower_bounds().is_empty()) - ? op_problem.get_constraint_bounds().size() - : op_problem.get_constraint_lower_bounds().size(); cuopt_expects( - (dual_initial_solution.size() == n_constraints), + (dual_initial_solution.size() == constraints.size()), error_type_t::ValidationError, "Sizes for vectors related to the variables are not the same. The initial dual variable " "has size %zu, while constraint vector has size %zu.", dual_initial_solution.size(), - n_constraints); + constraints.size()); } } @@ -101,10 +98,28 @@ void problem_checking_t::check_initial_solution_representation( const pdlp_solver_settings_t& settings) { if (settings.initial_primal_solution_.get() != nullptr) { - check_initial_primal_representation(op_problem, settings.get_initial_primal_solution()); + check_initial_primal_representation(op_problem.get_objective_coefficients(), + settings.get_initial_primal_solution()); + } + if (settings.initial_dual_solution_.get() != nullptr) { + const auto& constraints = (op_problem.get_constraint_lower_bounds().is_empty()) + ? op_problem.get_constraint_bounds() + : op_problem.get_constraint_lower_bounds(); + check_initial_dual_representation(constraints, settings.get_initial_dual_solution()); + } +} + +template +void problem_checking_t::check_initial_solution_representation( + const detail::problem_t& problem, const pdlp_solver_settings_t& settings) +{ + if (settings.initial_primal_solution_.get() != nullptr) { + check_initial_primal_representation(problem.objective_coefficients, + settings.get_initial_primal_solution()); } if (settings.initial_dual_solution_.get() != nullptr) { - check_initial_dual_representation(op_problem, settings.get_initial_dual_solution()); + check_initial_dual_representation(problem.constraint_lower_bounds, + settings.get_initial_dual_solution()); } } @@ -114,7 +129,8 @@ void problem_checking_t::check_initial_solution_representation( const mip_solver_settings_t& settings) { if (settings.initial_solution_.get() != nullptr) { - check_initial_primal_representation(op_problem, settings.get_initial_solution()); + check_initial_primal_representation(op_problem.get_objective_coefficients(), + settings.get_initial_solution()); } } diff --git a/cpp/src/linear_programming/utilities/problem_checking.cuh b/cpp/src/linear_programming/utilities/problem_checking.cuh index 2df0a517a..8aa265afd 100644 --- a/cpp/src/linear_programming/utilities/problem_checking.cuh +++ b/cpp/src/linear_programming/utilities/problem_checking.cuh @@ -36,14 +36,16 @@ class problem_checking_t { static void check_unscaled_solution(detail::problem_t& op_problem, rmm::device_uvector const& assignment); static void check_initial_primal_representation( - const optimization_problem_t& op_problem, + const rmm::device_uvector& op_problem, const rmm::device_uvector& primal_initial_solution); static void check_initial_dual_representation( - const optimization_problem_t& op_problem, + const rmm::device_uvector& op_problem, const rmm::device_uvector& dual_initial_solution); static void check_initial_solution_representation( const optimization_problem_t& op_problem, const pdlp_solver_settings_t& settings); + static void check_initial_solution_representation( + const detail::problem_t& problem, const pdlp_solver_settings_t& settings); static void check_initial_solution_representation( const optimization_problem_t& op_problem, const mip_solver_settings_t& settings); diff --git a/cpp/src/math_optimization/solver_settings.cu b/cpp/src/math_optimization/solver_settings.cu index ac60c168c..ceffa3b08 100644 --- a/cpp/src/math_optimization/solver_settings.cu +++ b/cpp/src/math_optimization/solver_settings.cu @@ -334,7 +334,8 @@ void solver_settings_t::set_pdlp_warm_start_data( f_t last_candidate_kkt_score, f_t last_restart_kkt_score, f_t sum_solution_weight, - i_t iterations_since_last_restart) + i_t iterations_since_last_restart, + bool solved_by_pdlp) { pdlp_settings.set_pdlp_warm_start_data(current_primal_solution, current_dual_solution, @@ -354,7 +355,8 @@ void solver_settings_t::set_pdlp_warm_start_data( last_candidate_kkt_score, last_restart_kkt_score, sum_solution_weight, - iterations_since_last_restart); + iterations_since_last_restart, + solved_by_pdlp); } template diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index f04a2a814..08c6fd6f5 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -22,6 +22,7 @@ #include #include +#include #include #include "cuda_profiler_api.h" @@ -265,6 +266,8 @@ bool diversity_manager_t::run_presolve(f_t time_limit) template void diversity_manager_t::generate_quick_feasible_solution() { + raft::common::nvtx::range fun_scope("Generate Quick Feasible Solution"); + solution_t solution(*problem_ptr); // min 1 second, max 10 seconds const f_t generate_fast_solution_time = std::min(10., std::max(1., timer.remaining_time() / 20.)); diff --git a/cpp/src/mip/local_search/feasibility_pump/feasibility_pump.cu b/cpp/src/mip/local_search/feasibility_pump/feasibility_pump.cu index cd959b652..e448848f6 100644 --- a/cpp/src/mip/local_search/feasibility_pump/feasibility_pump.cu +++ b/cpp/src/mip/local_search/feasibility_pump/feasibility_pump.cu @@ -146,6 +146,8 @@ bool feasibility_pump_t::linear_project_onto_polytope(solution_tvariable_upper_bounds, diff --git a/cpp/src/mip/relaxed_lp/relaxed_lp.cu b/cpp/src/mip/relaxed_lp/relaxed_lp.cu index 05fcf3205..35f0db798 100644 --- a/cpp/src/mip/relaxed_lp/relaxed_lp.cu +++ b/cpp/src/mip/relaxed_lp/relaxed_lp.cu @@ -19,6 +19,7 @@ #include #include +#include #include #include @@ -71,7 +72,6 @@ optimization_problem_solution_t get_relaxed_lp_solution( // settings.set_save_best_primal_so_far(true); // currently disable first primal setting as it is not supported without per constraint mode settings.first_primal_feasible = return_first_feasible; - pdlp_solver_t lp_solver(op_problem, settings); if (save_state) { i_t prev_size = lp_state.prev_dual.size(); CUOPT_LOG_DEBUG( @@ -95,17 +95,21 @@ optimization_problem_solution_t get_relaxed_lp_solution( if (!isfinite(x) || i >= prev_size) { return 0.0; } return x; }); - lp_solver.set_initial_primal_solution(lp_state.prev_primal); - lp_solver.set_initial_dual_solution(lp_state.prev_dual); + settings.set_initial_primal_solution(lp_state.prev_primal.data(), + lp_state.prev_primal.size(), + op_problem.handle_ptr->get_stream()); + settings.set_initial_dual_solution( + lp_state.prev_dual.data(), lp_state.prev_dual.size(), op_problem.handle_ptr->get_stream()); } CUOPT_LOG_DEBUG( "running LP with n_vars %d n_cstr %d", op_problem.n_variables, op_problem.n_constraints); // before LP flush the logs as it takes quite some time cuopt::default_logger().flush(); - // temporarily add timer - auto start_time = std::chrono::high_resolution_clock::now(); - lp_solver.set_inside_mip(true); - auto solver_response = lp_solver.run_solver(start_time); + // Check the problem only when in assert or debug mode + const bool use_pdlp_solver_mode = true; + const bool inside_mip = true; + + auto solver_response = solve_lp(op_problem, settings, use_pdlp_solver_mode, inside_mip); if (solver_response.get_primal_solution().size() != 0 && solver_response.get_dual_solution().size() != 0 && save_state) { diff --git a/cpp/tests/linear_programming/unit_tests/solver_settings_test.cu b/cpp/tests/linear_programming/unit_tests/solver_settings_test.cu index b573bea03..11b535de0 100644 --- a/cpp/tests/linear_programming/unit_tests/solver_settings_test.cu +++ b/cpp/tests/linear_programming/unit_tests/solver_settings_test.cu @@ -134,19 +134,21 @@ TEST(SolverSettingsTest, warm_start_smaller_vector) -1, -1, -1, - -1); + -1, + true); solver_settings.set_pdlp_warm_start_data(warm_start_data, d_primal_mapping, d_dual_mapping); + const auto& _warm_start_data = solver_settings.get_pdlp_warm_start_data(); + std::vector h_current_primal_solution = - cuopt::host_copy(solver_settings.get_pdlp_warm_start_data().current_primal_solution_); + cuopt::host_copy(_warm_start_data.current_primal_solution_); std::vector h_initial_primal_average = - cuopt::host_copy(solver_settings.get_pdlp_warm_start_data().initial_primal_average_); - std::vector h_current_ATY = - cuopt::host_copy(solver_settings.get_pdlp_warm_start_data().current_ATY_); + cuopt::host_copy(_warm_start_data.initial_primal_average_); + std::vector h_current_ATY = cuopt::host_copy(_warm_start_data.current_ATY_); std::vector h_sum_primal_solutions = - cuopt::host_copy(solver_settings.get_pdlp_warm_start_data().sum_primal_solutions_); - std::vector h_last_restart_duality_gap_primal_solution = cuopt::host_copy( - solver_settings.get_pdlp_warm_start_data().last_restart_duality_gap_primal_solution_); + cuopt::host_copy(_warm_start_data.sum_primal_solutions_); + std::vector h_last_restart_duality_gap_primal_solution = + cuopt::host_copy(_warm_start_data.last_restart_duality_gap_primal_solution_); EXPECT_EQ(h_current_primal_solution.size(), primal_expected.size()); EXPECT_EQ(h_initial_primal_average.size(), primal_expected.size()); @@ -159,15 +161,15 @@ TEST(SolverSettingsTest, warm_start_smaller_vector) EXPECT_EQ(h_current_ATY, primal_expected); EXPECT_EQ(h_sum_primal_solutions, primal_expected); EXPECT_EQ(h_last_restart_duality_gap_primal_solution, primal_expected); + EXPECT_EQ(_warm_start_data.solved_by_pdlp_, true); std::vector h_current_dual_solution = - cuopt::host_copy(solver_settings.get_pdlp_warm_start_data().current_dual_solution_); + cuopt::host_copy(_warm_start_data.current_dual_solution_); std::vector h_initial_dual_average = - cuopt::host_copy(solver_settings.get_pdlp_warm_start_data().initial_dual_average_); - std::vector h_sum_dual_solutions = - cuopt::host_copy(solver_settings.get_pdlp_warm_start_data().sum_dual_solutions_); - std::vector h_last_restart_duality_gap_dual_solution = cuopt::host_copy( - solver_settings.get_pdlp_warm_start_data().last_restart_duality_gap_dual_solution_); + cuopt::host_copy(_warm_start_data.initial_dual_average_); + std::vector h_sum_dual_solutions = cuopt::host_copy(_warm_start_data.sum_dual_solutions_); + std::vector h_last_restart_duality_gap_dual_solution = + cuopt::host_copy(_warm_start_data.last_restart_duality_gap_dual_solution_); EXPECT_EQ(h_current_dual_solution.size(), dual_expected.size()); EXPECT_EQ(h_initial_dual_average.size(), dual_expected.size()); @@ -189,9 +191,8 @@ TEST(SolverSettingsTest, warm_start_bigger_vector) std::vector primal = {0.0, 1.0, 2.0, 3.0}; std::vector dual = {0.0, 1.0, 2.0}; - std::vector primal_mapping = {0, 1, 2, 3, 4, 5}; // Only two variables and 0 - 1 swapped - std::vector dual_mapping = { - 0, 1, 2, 3, 4, 5, 6}; // Only three constraints and 1 - 2 swapped + std::vector primal_mapping = {0, 1, 2, 3, 4, 5}; + std::vector dual_mapping = {0, 1, 2, 3, 4, 5, 6}; std::vector primal_expected = {0.0, 1.0, 2.0, 3.0, 0.0, 0.0}; std::vector dual_expected = {0.0, 1.0, 2.0, 0.0, 0.0, 0.0, 0.0}; @@ -234,19 +235,22 @@ TEST(SolverSettingsTest, warm_start_bigger_vector) -1, -1, -1, - -1); + -1, + true); solver_settings.set_pdlp_warm_start_data(warm_start_data, d_primal_mapping, d_dual_mapping); + const pdlp_warm_start_data_t& _warm_start_data = + solver_settings.get_pdlp_warm_start_data(); + std::vector h_current_primal_solution = - cuopt::host_copy(solver_settings.get_pdlp_warm_start_data().current_primal_solution_); + cuopt::host_copy(_warm_start_data.current_primal_solution_); std::vector h_initial_primal_average = - cuopt::host_copy(solver_settings.get_pdlp_warm_start_data().initial_primal_average_); - std::vector h_current_ATY = - cuopt::host_copy(solver_settings.get_pdlp_warm_start_data().current_ATY_); + cuopt::host_copy(_warm_start_data.initial_primal_average_); + std::vector h_current_ATY = cuopt::host_copy(_warm_start_data.current_ATY_); std::vector h_sum_primal_solutions = - cuopt::host_copy(solver_settings.get_pdlp_warm_start_data().sum_primal_solutions_); - std::vector h_last_restart_duality_gap_primal_solution = cuopt::host_copy( - solver_settings.get_pdlp_warm_start_data().last_restart_duality_gap_primal_solution_); + cuopt::host_copy(_warm_start_data.sum_primal_solutions_); + std::vector h_last_restart_duality_gap_primal_solution = + cuopt::host_copy(_warm_start_data.last_restart_duality_gap_primal_solution_); EXPECT_EQ(h_current_primal_solution.size(), primal_expected.size()); EXPECT_EQ(h_initial_primal_average.size(), primal_expected.size()); @@ -261,13 +265,12 @@ TEST(SolverSettingsTest, warm_start_bigger_vector) EXPECT_EQ(h_last_restart_duality_gap_primal_solution, primal_expected); std::vector h_current_dual_solution = - cuopt::host_copy(solver_settings.get_pdlp_warm_start_data().current_dual_solution_); + cuopt::host_copy(_warm_start_data.current_dual_solution_); std::vector h_initial_dual_average = - cuopt::host_copy(solver_settings.get_pdlp_warm_start_data().initial_dual_average_); - std::vector h_sum_dual_solutions = - cuopt::host_copy(solver_settings.get_pdlp_warm_start_data().sum_dual_solutions_); - std::vector h_last_restart_duality_gap_dual_solution = cuopt::host_copy( - solver_settings.get_pdlp_warm_start_data().last_restart_duality_gap_dual_solution_); + cuopt::host_copy(_warm_start_data.initial_dual_average_); + std::vector h_sum_dual_solutions = cuopt::host_copy(_warm_start_data.sum_dual_solutions_); + std::vector h_last_restart_duality_gap_dual_solution = + cuopt::host_copy(_warm_start_data.last_restart_duality_gap_dual_solution_); EXPECT_EQ(h_current_dual_solution.size(), dual_expected.size()); EXPECT_EQ(h_initial_dual_average.size(), dual_expected.size()); diff --git a/python/cuopt/cuopt/linear_programming/solution/solution.py b/python/cuopt/cuopt/linear_programming/solution/solution.py index 952ecb73b..25380d4d0 100644 --- a/python/cuopt/cuopt/linear_programming/solution/solution.py +++ b/python/cuopt/cuopt/linear_programming/solution/solution.py @@ -40,6 +40,7 @@ def __init__( last_restart_kkt_score, sum_solution_weight, iterations_since_last_restart, + solved_by_pdlp, ): self.current_primal_solution = current_primal_solution self.current_dual_solution = current_dual_solution @@ -62,6 +63,7 @@ def __init__( self.last_restart_kkt_score = last_restart_kkt_score self.sum_solution_weight = sum_solution_weight self.iterations_since_last_restart = iterations_since_last_restart + self.solved_by_pdlp = solved_by_pdlp class Solution: @@ -197,6 +199,7 @@ def __init__( last_restart_kkt_score, sum_solution_weight, iterations_since_last_restart, + solved_by_pdlp, ) self._set_termination_status(termination_status) self.error_status = error_status diff --git a/python/cuopt/cuopt/linear_programming/solver/solver.pxd b/python/cuopt/cuopt/linear_programming/solver/solver.pxd index 3982e2ff8..c872f9ebf 100644 --- a/python/cuopt/cuopt/linear_programming/solver/solver.pxd +++ b/python/cuopt/cuopt/linear_programming/solver/solver.pxd @@ -64,7 +64,8 @@ cdef extern from "cuopt/linear_programming/solver_settings.hpp" namespace "cuopt f_t last_candidate_kkt_score_, f_t last_restart_kkt_score_, f_t sum_solution_weight_, - i_t iterations_since_last_restart_) except + + i_t iterations_since_last_restart_, + bool solved_by_pdlp_) except + void set_parameter_from_string( const string& name, diff --git a/python/cuopt/cuopt/linear_programming/solver/solver_wrapper.pyx b/python/cuopt/cuopt/linear_programming/solver/solver_wrapper.pyx index 9ba65bbc0..38a007265 100644 --- a/python/cuopt/cuopt/linear_programming/solver/solver_wrapper.pyx +++ b/python/cuopt/cuopt/linear_programming/solver/solver_wrapper.pyx @@ -344,86 +344,90 @@ cdef set_solver_setting( if settings.get_pdlp_warm_start_data() is not None: # noqa - if len(data_model_obj.get_objective_coefficients()) != len( - settings.get_pdlp_warm_start_data().current_primal_solution - ): - raise Exception( - "Invalid PDLPWarmStart data. Passed problem and PDLPWarmStart " # noqa - "data should have the same amount of variables." - ) - if len(data_model_obj.get_constraint_matrix_offsets()) - 1 != len( # noqa - settings.get_pdlp_warm_start_data().current_dual_solution - ): - raise Exception( - "Invalid PDLPWarmStart data. Passed problem and PDLPWarmStart " # noqa - "data should have the same amount of constraints." + if not settings.get_pdlp_warm_start_data().solved_by_pdlp: + warnings.warn("PDLPWarmStart data was passed to the solver, but the problem was solved by Dual Simplex. This data will be ignored.") # noqa + else: + if len(data_model_obj.get_objective_coefficients()) != len( + settings.get_pdlp_warm_start_data().current_primal_solution + ): + raise Exception( + "Invalid PDLPWarmStart data. Passed problem and PDLPWarmStart " # noqa + "data should have the same amount of variables." + ) + if len(data_model_obj.get_constraint_matrix_offsets()) - 1 != len( # noqa + settings.get_pdlp_warm_start_data().current_dual_solution + ): + raise Exception( + "Invalid PDLPWarmStart data. Passed problem and PDLPWarmStart " # noqa + "data should have the same amount of constraints." + ) + c_current_primal_solution = ( + get_data_ptr( + settings.get_pdlp_warm_start_data().current_primal_solution # noqa + ) ) - c_current_primal_solution = ( - get_data_ptr( - settings.get_pdlp_warm_start_data().current_primal_solution # noqa + c_current_dual_solution = ( + get_data_ptr( + settings.get_pdlp_warm_start_data().current_dual_solution + ) ) - ) - c_current_dual_solution = ( - get_data_ptr( - settings.get_pdlp_warm_start_data().current_dual_solution + c_initial_primal_average = ( + get_data_ptr( + settings.get_pdlp_warm_start_data().initial_primal_average # noqa + ) ) - ) - c_initial_primal_average = ( - get_data_ptr( - settings.get_pdlp_warm_start_data().initial_primal_average # noqa + c_initial_dual_average = ( + get_data_ptr( + settings.get_pdlp_warm_start_data().initial_dual_average + ) ) - ) - c_initial_dual_average = ( - get_data_ptr( - settings.get_pdlp_warm_start_data().initial_dual_average + c_current_ATY = ( + get_data_ptr( + settings.get_pdlp_warm_start_data().current_ATY + ) ) - ) - c_current_ATY = ( - get_data_ptr( - settings.get_pdlp_warm_start_data().current_ATY + c_sum_primal_solutions = ( + get_data_ptr( + settings.get_pdlp_warm_start_data().sum_primal_solutions + ) ) - ) - c_sum_primal_solutions = ( - get_data_ptr( - settings.get_pdlp_warm_start_data().sum_primal_solutions + c_sum_dual_solutions = ( + get_data_ptr( + settings.get_pdlp_warm_start_data().sum_dual_solutions + ) ) - ) - c_sum_dual_solutions = ( - get_data_ptr( - settings.get_pdlp_warm_start_data().sum_dual_solutions + c_last_restart_duality_gap_primal_solution = ( + get_data_ptr( + settings.get_pdlp_warm_start_data().last_restart_duality_gap_primal_solution # noqa + ) ) - ) - c_last_restart_duality_gap_primal_solution = ( - get_data_ptr( - settings.get_pdlp_warm_start_data().last_restart_duality_gap_primal_solution # noqa + c_last_restart_duality_gap_dual_solution = ( + get_data_ptr( + settings.get_pdlp_warm_start_data().last_restart_duality_gap_dual_solution # noqa + ) ) - ) - c_last_restart_duality_gap_dual_solution = ( - get_data_ptr( - settings.get_pdlp_warm_start_data().last_restart_duality_gap_dual_solution # noqa + c_solver_settings.set_pdlp_warm_start_data( + c_current_primal_solution, + c_current_dual_solution, + c_initial_primal_average, + c_initial_dual_average, + c_current_ATY, + c_sum_primal_solutions, + c_sum_dual_solutions, + c_last_restart_duality_gap_primal_solution, + c_last_restart_duality_gap_dual_solution, + settings.get_pdlp_warm_start_data().last_restart_duality_gap_primal_solution.shape[0], # Primal size # noqa + settings.get_pdlp_warm_start_data().last_restart_duality_gap_dual_solution.shape[0], # Dual size # noqa + settings.get_pdlp_warm_start_data().initial_primal_weight, + settings.get_pdlp_warm_start_data().initial_step_size, + settings.get_pdlp_warm_start_data().total_pdlp_iterations, + settings.get_pdlp_warm_start_data().total_pdhg_iterations, + settings.get_pdlp_warm_start_data().last_candidate_kkt_score, + settings.get_pdlp_warm_start_data().last_restart_kkt_score, + settings.get_pdlp_warm_start_data().sum_solution_weight, + settings.get_pdlp_warm_start_data().iterations_since_last_restart, # noqa + settings.get_pdlp_warm_start_data().solved_by_pdlp ) - ) - c_solver_settings.set_pdlp_warm_start_data( - c_current_primal_solution, - c_current_dual_solution, - c_initial_primal_average, - c_initial_dual_average, - c_current_ATY, - c_sum_primal_solutions, - c_sum_dual_solutions, - c_last_restart_duality_gap_primal_solution, - c_last_restart_duality_gap_dual_solution, - settings.get_pdlp_warm_start_data().last_restart_duality_gap_primal_solution.shape[0], # Primal size # noqa - settings.get_pdlp_warm_start_data().last_restart_duality_gap_dual_solution.shape[0], # Dual size # noqa - settings.get_pdlp_warm_start_data().initial_primal_weight, - settings.get_pdlp_warm_start_data().initial_step_size, - settings.get_pdlp_warm_start_data().total_pdlp_iterations, - settings.get_pdlp_warm_start_data().total_pdhg_iterations, - settings.get_pdlp_warm_start_data().last_candidate_kkt_score, - settings.get_pdlp_warm_start_data().last_restart_kkt_score, - settings.get_pdlp_warm_start_data().sum_solution_weight, - settings.get_pdlp_warm_start_data().iterations_since_last_restart # noqa - ) # Common to LP and MIP @@ -648,7 +652,7 @@ cdef create_solution(unique_ptr[solver_ret_t] sol_ret_ptr, dual_objective, gap, nb_iterations, - solved_by_pdlp, + solved_by_pdlp=solved_by_pdlp, ) return Solution( problem_category=ProblemCategory(sol_ret.problem_type), 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 9159ba933..54f8ecf97 100644 --- a/python/cuopt/cuopt/linear_programming/solver_settings/solver_settings.py +++ b/python/cuopt/cuopt/linear_programming/solver_settings/solver_settings.py @@ -226,10 +226,14 @@ def set_pdlp_warm_start_data(self, pdlp_warm_start_data): For now, the problem must have the same number of variables and constraints as the one found in the previous solution. - Only supported solver modes are Stable2 and Fast1. + Only supported PDLP solver modes are Stable2 and Fast1. + Only supported if both the previous and new problem are solved by PDLP. Examples -------- + >>> # Set PDLP as the solver method to make sure Dual Simplex will not + >>> # be used + >>> settings.set_parameter(CUOPT_METHOD, SolverMethod.PDLP) >>> solution = solver.Solve(first_problem, settings) >>> settings.set_pdlp_warm_start_data( >>> solution.get_pdlp_warm_start_data() 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 ed0d04ec1..7c8b4876f 100644 --- a/python/cuopt/cuopt/tests/linear_programming/test_lp_solver.py +++ b/python/cuopt/cuopt/tests/linear_programming/test_lp_solver.py @@ -324,7 +324,7 @@ def test_check_data_model_validity(): data_model_obj = data_model.DataModel() - # Test if exception is thrown when A_CSR is not set + # Test if error is returned when A_CSR is not set solution = solver.Solve(data_model_obj) assert solution.get_error_status() == ErrorStatus.ValidationError @@ -334,7 +334,7 @@ def test_check_data_model_validity(): A_offsets = np.array([0, 1], dtype=np.int32) data_model_obj.set_csr_constraint_matrix(A_values, A_indices, A_offsets) - # Test if exception is thrown when b is not set + # Test if error is returned when b is not set solution = solver.Solve(data_model_obj) assert solution.get_error_status() == ErrorStatus.ValidationError @@ -342,7 +342,7 @@ def test_check_data_model_validity(): b = np.array([1.0], dtype=np.float64) data_model_obj.set_constraint_bounds(b) - # Test if exception is thrown when c is not set + # Test if error is returned when c is not set solution = solver.Solve(data_model_obj) assert solution.get_error_status() == ErrorStatus.ValidationError @@ -353,14 +353,14 @@ def test_check_data_model_validity(): # Set maximize data_model_obj.set_maximize(True) - # Test if exception is thrown when maximize is set to true + # Test if error is returned when maximize is set to true solution = solver.Solve(data_model_obj) assert solution.get_error_status() == ErrorStatus.ValidationError # Set maximize to correct value data_model_obj.set_maximize(False) - # Test if exception is thrown when row_type is not set + # Test if error is returned when row_type is not set solution = solver.Solve(data_model_obj) assert solution.get_error_status() == ErrorStatus.ValidationError @@ -368,7 +368,7 @@ def test_check_data_model_validity(): row_type = np.array(["E"]) data_model_obj.set_row_types(row_type) - # Test if no exception is thrown when row_type is set + # Test if no error is returned when row_type is set solver.Solve(data_model_obj) # Set constraint_lower_bounds with np.array @@ -379,7 +379,7 @@ def test_check_data_model_validity(): constraint_upper_bounds = np.array([1.0], dtype=np.float64) data_model_obj.set_constraint_upper_bounds(constraint_upper_bounds) - # Test if no exception is thrown when upper constraints bounds are not set + # Test if no error is returned when upper constraints bounds are not set solver.Solve(data_model_obj) @@ -510,6 +510,22 @@ def test_parser_and_batch_solver(): ) +def test_solved_by_pdlp(): + + file_path = ( + RAPIDS_DATASET_ROOT_DIR + "/linear_programming/afiro_original.mps" + ) + data_model_obj = cuopt_mps_parser.ParseMps(file_path) + + settings = solver_settings.SolverSettings() + settings.set_parameter(CUOPT_METHOD, SolverMethod.PDLP) + + solution = solver.Solve(data_model_obj, settings) + + assert solution.get_solved_by_pdlp() + assert solution.get_pdlp_warm_start_data().solved_by_pdlp + + def test_warm_start(): file_path = RAPIDS_DATASET_ROOT_DIR + "/linear_programming/a2864/a2864.mps" 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 d66b7c817..e596fe237 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 @@ -574,6 +574,7 @@ class WarmStartData(StrictModel): last_restart_kkt_score: float sum_solution_weight: float iterations_since_last_restart: int + solved_by_pdlp: bool = False class SolutionData(StrictModel): 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 fcb9d0764..d45cb2fcf 100644 --- a/python/cuopt_server/cuopt_server/utils/linear_programming/solver.py +++ b/python/cuopt_server/cuopt_server/utils/linear_programming/solver.py @@ -330,6 +330,7 @@ def extract_pdlpwarmstart_data(data): "last_restart_kkt_score": data.last_restart_kkt_score, "sum_solution_weight": data.sum_solution_weight, "iterations_since_last_restart": data.iterations_since_last_restart, # noqa + "solved_by_pdlp": data.solved_by_pdlp, } return pdlpwarmstart_data