From 0e5889a19d5f8e4ece4a7e1ed2a5926b491663c6 Mon Sep 17 00:00:00 2001 From: Nicolas Blin Date: Wed, 28 May 2025 08:29:03 +0000 Subject: [PATCH 1/8] improve warm start handling and add concurrent lp to get_relaxed_lp --- .../pdlp/pdlp_warm_start_data.hpp | 5 +- .../pdlp/solver_settings.hpp | 15 +- .../cuopt/linear_programming/solve.hpp | 30 +++ .../linear_programming/solver_settings.hpp | 3 +- cpp/src/linear_programming/pdlp.cu | 149 ++++++++------- cpp/src/linear_programming/pdlp.cuh | 2 +- .../pdlp_warm_start_data.cu | 12 +- cpp/src/linear_programming/solve.cu | 55 +++--- cpp/src/linear_programming/solver_settings.cu | 178 ++++++++++-------- cpp/src/math_optimization/solver_settings.cu | 6 +- cpp/src/mip/diversity/diversity_manager.cu | 4 + cpp/src/mip/relaxed_lp/relaxed_lp.cu | 11 +- 12 files changed, 288 insertions(+), 182 deletions(-) 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..36928eb52 100644 --- a/cpp/include/cuopt/linear_programming/pdlp/solver_settings.hpp +++ b/cpp/include/cuopt/linear_programming/pdlp/solver_settings.hpp @@ -110,6 +110,9 @@ class pdlp_solver_settings_t { i_t size, rmm::cuda_stream_view stream = rmm::cuda_stream_default); + // TODO comment + void set_initial_primal_solution(const rmm::device_uvector& initial_primal_solution); + /** * @brief Set an initial dual solution. * @@ -124,6 +127,9 @@ class pdlp_solver_settings_t { i_t size, rmm::cuda_stream_view stream = rmm::cuda_stream_default); + // TODO comment + void set_initial_dual_solution(const rmm::device_uvector& initial_dual_solution); + /** * @brief Set the pdlp warm start data. This allows to restart PDLP with a previous solution * @@ -160,14 +166,15 @@ 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 Get the pdlp warm start data * * @return pdlp warm start data */ - const pdlp_warm_start_data_t& get_pdlp_warm_start_data() const noexcept; - pdlp_warm_start_data_t& get_pdlp_warm_start_data(); + const std::optional>& get_pdlp_warm_start_data() const noexcept; + std::optional>& get_pdlp_warm_start_data(); const pdlp_warm_start_data_view_t& get_pdlp_warm_start_data_view() const noexcept; const rmm::device_uvector& get_initial_primal_solution() const; @@ -216,7 +223,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..20684955d 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 @@ -53,6 +58,31 @@ optimization_problem_solution_t solve_lp( bool problem_checking = true, bool use_pdlp_solver_mode = true); +/** + * @brief Linear programming solve function. + * @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] problem_checking If true, the problem is checked for consistency. + * @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). + * @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 problem_checking = true, + bool use_pdlp_solver_mode = true, + bool inside_mip = false); + /** * @brief Linear programming solve function. * @note Both primal and dual solutions are zero-initialized. For custom initialization, see 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/src/linear_programming/pdlp.cu b/cpp/src/linear_programming/pdlp.cu index 7acadae50..8f8f61265 100644 --- a/cpp/src/linear_programming/pdlp.cu +++ b/cpp/src/linear_programming/pdlp.cu @@ -128,56 +128,64 @@ 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_); - - 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_; + if (settings.get_pdlp_warm_start_data().has_value()) { + const auto& warm_start_data = settings.get_pdlp_warm_start_data().value(); + if (!warm_start_data.solved_by_pdlp_) { + CUOPT_LOG_INFO("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_INFO("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 = 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) @@ -283,7 +291,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(true), pdlp_termination_status_t::TimeLimit); } @@ -307,7 +315,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(true), pdlp_termination_status_t::IterationLimit); } @@ -323,7 +331,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 +474,7 @@ 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 +493,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 +554,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 +573,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 @@ -600,7 +614,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(true), termination_current); } else // Average has better overall residual { @@ -609,7 +623,7 @@ std::optional> pdlp_solver_t pdhg_solver_, unscaled_primal_avg_solution_, unscaled_dual_avg_solution_, - get_filled_warmed_start_data(), + get_filled_warmed_start_data(true), termination_average); } } else if (termination_current == pdlp_termination_status_t::PrimalFeasible) { @@ -618,7 +632,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(true), termination_current); } else if (termination_average == pdlp_termination_status_t::PrimalFeasible) { return average_termination_strategy_.fill_return_problem_solution( @@ -626,7 +640,7 @@ std::optional> pdlp_solver_t pdhg_solver_, unscaled_primal_avg_solution_, unscaled_dual_avg_solution_, - get_filled_warmed_start_data(), + get_filled_warmed_start_data(true), termination_average); } // else neither of the two is primal feasible @@ -660,7 +674,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(true), termination_current); } else { #ifdef PDLP_VERBOSE_MODE @@ -676,7 +690,7 @@ std::optional> pdlp_solver_t pdhg_solver_, unscaled_primal_avg_solution_, unscaled_dual_avg_solution_, - get_filled_warmed_start_data(), + get_filled_warmed_start_data(true), termination_average); } } @@ -696,7 +710,7 @@ std::optional> pdlp_solver_t pdhg_solver_, unscaled_primal_avg_solution_, unscaled_dual_avg_solution_, - get_filled_warmed_start_data(), + get_filled_warmed_start_data(true), termination_average); } if (termination_current == pdlp_termination_status_t::Optimal) { @@ -711,7 +725,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(true), termination_current); } @@ -984,6 +998,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 +1087,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_.get_pdlp_warm_start_data().has_value(); if (!inside_mip_) { CUOPT_LOG_INFO( diff --git a/cpp/src/linear_programming/pdlp.cuh b/cpp/src/linear_programming/pdlp.cuh index 10a028f26..a3de4976f 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); // 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..0cd1c4b7a 100644 --- a/cpp/src/linear_programming/solve.cu +++ b/cpp/src/linear_programming/solve.cu @@ -363,7 +363,8 @@ static optimization_problem_solution_t run_pdlp_solver( 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(); @@ -465,16 +466,15 @@ void run_dual_simplex_thread( 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); @@ -495,7 +495,7 @@ optimization_problem_solution_t run_concurrent( std::ref(sol_dual_simplex_ptr)); // 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 +515,8 @@ 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); + // TODO confirm with Akif that using the problem handle ptr is not a problem + 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 +538,25 @@ 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); } 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 +564,16 @@ optimization_problem_solution_t solve_lp(optimization_problem_t::check_problem_representation(op_problem); - problem_checking_t::check_initial_solution_representation(op_problem, settings); + problem_checking_t::check_problem_representation(*problem.original_problem_ptr); + problem_checking_t::check_initial_solution_representation(*problem.original_problem_ptr, settings); } - detail::problem_t problem(op_problem); CUOPT_LOG_INFO( "Solving a problem with %d constraints %d variables (%d integers) and %d nonzeros", problem.n_constraints, @@ -591,27 +592,37 @@ 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, op_problem.get_handle_ptr()->get_stream()}; + 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), - op_problem.get_handle_ptr()->get_stream()}; + 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) +{ + detail::problem_t problem(op_problem); + return solve_lp(problem, settings, problem_checking, use_pdlp_solver_mode); +} + template cuopt::linear_programming::optimization_problem_t mps_data_model_to_optimization_problem( raft::handle_t const* handle_ptr, const cuopt::mps_parser::mps_data_model_t& data_model) diff --git a/cpp/src/linear_programming/solver_settings.cu b/cpp/src/linear_programming/solver_settings.cu index b8b555982..9fb89fc4a 100644 --- a/cpp/src/linear_programming/solver_settings.cu +++ b/cpp/src/linear_programming/solver_settings.cu @@ -47,9 +47,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 @@ -75,6 +78,17 @@ void pdlp_solver_settings_t::set_initial_primal_solution( raft::copy(initial_primal_solution_.get()->data(), initial_primal_solution, size, stream); } +template +void pdlp_solver_settings_t::set_initial_primal_solution( + const rmm::device_uvector& initial_primal_solution) +{ + initial_primal_solution_ = std::make_shared>(initial_primal_solution.size(), initial_primal_solution.stream()); + raft::copy(initial_primal_solution_.get()->data(), + initial_primal_solution.data(), + initial_primal_solution.size(), + initial_primal_solution.stream()); +} + template void pdlp_solver_settings_t::set_initial_dual_solution(const f_t* initial_dual_solution, i_t size, @@ -88,6 +102,18 @@ void pdlp_solver_settings_t::set_initial_dual_solution(const f_t* init raft::copy(initial_dual_solution_.get()->data(), initial_dual_solution, size, stream); } +// TODO could use a move constructor here +template +void pdlp_solver_settings_t::set_initial_dual_solution( + const rmm::device_uvector& initial_dual_solution) +{ + initial_dual_solution_ = std::make_shared>(initial_dual_solution.size(), initial_dual_solution.stream()); + raft::copy(initial_dual_solution_.get()->data(), + initial_dual_solution.data(), + initial_dual_solution.size(), + initial_dual_solution.stream()); +} + template void pdlp_solver_settings_t::set_pdlp_warm_start_data( pdlp_warm_start_data_t& pdlp_warm_start_data_view, @@ -96,81 +122,83 @@ 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(), + 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.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(), + 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(), + 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.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(), + 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 +208,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(), + 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.initial_dual_average_.resize(constraint_mapping.size(), constraint_mapping.stream()); - pdlp_warm_start_data_.sum_dual_solutions_.resize(constraint_mapping.size(), + 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.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(), + 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.initial_dual_average_.resize(constraint_mapping.size(), constraint_mapping.stream()); - pdlp_warm_start_data_.sum_dual_solutions_.resize(constraint_mapping.size(), + 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.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 +294,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 +350,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 @@ -355,14 +385,14 @@ bool pdlp_solver_settings_t::has_initial_dual_solution() const } template -const pdlp_warm_start_data_t& pdlp_solver_settings_t::get_pdlp_warm_start_data() - const noexcept +const std::optional>& +pdlp_solver_settings_t::get_pdlp_warm_start_data() const noexcept { return pdlp_warm_start_data_; } template -pdlp_warm_start_data_t& pdlp_solver_settings_t::get_pdlp_warm_start_data() +std::optional>& pdlp_solver_settings_t::get_pdlp_warm_start_data() { return pdlp_warm_start_data_; } diff --git a/cpp/src/math_optimization/solver_settings.cu b/cpp/src/math_optimization/solver_settings.cu index 142ef55c5..8127c1950 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 a858c1095..bbcd45760 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -22,6 +22,8 @@ #include #include +#include + #include "cuda_profiler_api.h" namespace cuopt::linear_programming::detail { @@ -263,6 +265,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 = min(10., max(1., timer.remaining_time() / 20.)); diff --git a/cpp/src/mip/relaxed_lp/relaxed_lp.cu b/cpp/src/mip/relaxed_lp/relaxed_lp.cu index 05fcf3205..e134cb575 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,16 @@ 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); + settings.set_initial_dual_solution(lp_state.prev_dual); } 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); + // TODO check that we do want to do problem checking here + auto solver_response = solve_lp(op_problem, settings, true, true, true); if (solver_response.get_primal_solution().size() != 0 && solver_response.get_dual_solution().size() != 0 && save_state) { From 90e248237b886b8d9e11f53bf0b4405c68c38e60 Mon Sep 17 00:00:00 2001 From: Nicolas Blin Date: Tue, 1 Jul 2025 13:01:25 +0000 Subject: [PATCH 2/8] properly handle pdlp warm start information if dual simplex was used --- .../pdlp/solver_settings.hpp | 5 +- .../cuopt/linear_programming/solve.hpp | 2 +- .../utilities/cython_solve.hpp | 6 + cpp/src/linear_programming/pdlp.cu | 80 +++++----- cpp/src/linear_programming/solve.cu | 12 +- cpp/src/linear_programming/solver_settings.cu | 51 +++--- .../feasibility_pump/feasibility_pump.cu | 2 + cpp/src/mip/relaxed_lp/relaxed_lp.cu | 1 - .../unit_tests/solver_settings_test.cu | 65 ++++---- .../linear_programming/solution/solution.py | 3 + .../linear_programming/solver/solver.pxd | 3 +- .../solver/solver_wrapper.pyx | 146 +++++++++--------- .../solver_settings/solver_settings.py | 6 +- .../linear_programming/test_lp_solver.py | 16 ++ .../linear_programming/data_definition.py | 1 + 15 files changed, 229 insertions(+), 170 deletions(-) diff --git a/cpp/include/cuopt/linear_programming/pdlp/solver_settings.hpp b/cpp/include/cuopt/linear_programming/pdlp/solver_settings.hpp index 36928eb52..a89543f50 100644 --- a/cpp/include/cuopt/linear_programming/pdlp/solver_settings.hpp +++ b/cpp/include/cuopt/linear_programming/pdlp/solver_settings.hpp @@ -173,8 +173,9 @@ class pdlp_solver_settings_t { * * @return pdlp warm start data */ - const std::optional>& get_pdlp_warm_start_data() const noexcept; - std::optional>& get_pdlp_warm_start_data(); + bool has_pdlp_warm_start_data() const; + const pdlp_warm_start_data_t& get_pdlp_warm_start_data() const noexcept; + pdlp_warm_start_data_t& get_pdlp_warm_start_data(); const pdlp_warm_start_data_view_t& get_pdlp_warm_start_data_view() const noexcept; const rmm::device_uvector& get_initial_primal_solution() const; diff --git a/cpp/include/cuopt/linear_programming/solve.hpp b/cpp/include/cuopt/linear_programming/solve.hpp index 20684955d..dd51f2755 100644 --- a/cpp/include/cuopt/linear_programming/solve.hpp +++ b/cpp/include/cuopt/linear_programming/solve.hpp @@ -81,7 +81,7 @@ optimization_problem_solution_t solve_lp( pdlp_solver_settings_t const& settings = pdlp_solver_settings_t{}, bool problem_checking = true, bool use_pdlp_solver_mode = true, - bool inside_mip = false); + bool inside_mip = false); /** * @brief Linear programming solve function. 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 8f8f61265..57e70f2b2 100644 --- a/cpp/src/linear_programming/pdlp.cu +++ b/cpp/src/linear_programming/pdlp.cu @@ -128,55 +128,56 @@ pdlp_solver_t::pdlp_solver_t(problem_t& op_problem, set_initial_dual_solution(dual_sol); } - if (settings.get_pdlp_warm_start_data().has_value()) { - const auto& warm_start_data = settings.get_pdlp_warm_start_data().value(); + 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_INFO("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_INFO("Trying to use warm start with trust region restart (neither Stable nor Fast1), skipping warm start"); - } - else { + CUOPT_LOG_INFO( + "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_INFO( + "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_; + 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_; + 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_); + 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_); + 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_); + 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_); + 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_); + 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_); + 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_); + 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 = warm_start_data.sum_solution_weight_; restart_strategy_.weighted_average_solution_.sum_primal_solution_weights_.set_value_async( @@ -184,7 +185,7 @@ pdlp_solver_t::pdlp_solver_t(problem_t& op_problem, 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_; + warm_start_data.iterations_since_last_restart_; } } // Checks performed below are assert only @@ -474,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(bool solved_by_pdlp) +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(), @@ -1087,7 +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().has_value(); + bool warm_start_was_given = settings_.has_pdlp_warm_start_data(); if (!inside_mip_) { CUOPT_LOG_INFO( diff --git a/cpp/src/linear_programming/solve.cu b/cpp/src/linear_programming/solve.cu index 0cd1c4b7a..fc8699bfe 100644 --- a/cpp/src/linear_programming/solve.cu +++ b/cpp/src/linear_programming/solve.cu @@ -572,7 +572,8 @@ optimization_problem_solution_t solve_lp(detail::problem_t& raft::common::nvtx::range fun_scope("Check problem representation"); // This is required as user might forget to set some fields problem_checking_t::check_problem_representation(*problem.original_problem_ptr); - problem_checking_t::check_initial_solution_representation(*problem.original_problem_ptr, settings); + problem_checking_t::check_initial_solution_representation( + *problem.original_problem_ptr, settings); } CUOPT_LOG_INFO( "Solving a problem with %d constraints %d variables (%d integers) and %d nonzeros", @@ -594,7 +595,7 @@ optimization_problem_solution_t solve_lp(detail::problem_t& setup_device_symbols(problem.handle_ptr->get_stream()); - auto sol = solve_lp_with_method( problem, settings, inside_mip); + 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()); @@ -711,7 +712,12 @@ 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 problem_checking, \ + 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 9fb89fc4a..5e042eac2 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 @@ -82,7 +83,8 @@ template void pdlp_solver_settings_t::set_initial_primal_solution( const rmm::device_uvector& initial_primal_solution) { - initial_primal_solution_ = std::make_shared>(initial_primal_solution.size(), initial_primal_solution.stream()); + initial_primal_solution_ = std::make_shared>( + initial_primal_solution.size(), initial_primal_solution.stream()); raft::copy(initial_primal_solution_.get()->data(), initial_primal_solution.data(), initial_primal_solution.size(), @@ -107,7 +109,8 @@ template void pdlp_solver_settings_t::set_initial_dual_solution( const rmm::device_uvector& initial_dual_solution) { - initial_dual_solution_ = std::make_shared>(initial_dual_solution.size(), initial_dual_solution.stream()); + initial_dual_solution_ = std::make_shared>( + initial_dual_solution.size(), initial_dual_solution.stream()); raft::copy(initial_dual_solution_.get()->data(), initial_dual_solution.data(), initial_dual_solution.size(), @@ -157,13 +160,12 @@ void pdlp_solver_settings_t::set_pdlp_warm_start_data( 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(), 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()); + var_mapping.stream()); } else if (var_mapping.size() > pdlp_warm_start_data.last_restart_duality_gap_primal_solution_.size()) { const auto previous_size = @@ -171,13 +173,12 @@ void pdlp_solver_settings_t::set_pdlp_warm_start_data( // 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(), 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()); + var_mapping.stream()); thrust::fill(rmm::exec_policy(var_mapping.stream()), pdlp_warm_start_data.current_primal_solution_.begin() + previous_size, @@ -231,11 +232,11 @@ void pdlp_solver_settings_t::set_pdlp_warm_start_data( 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(), constraint_mapping.stream()); + 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()); + 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() > @@ -245,11 +246,11 @@ void pdlp_solver_settings_t::set_pdlp_warm_start_data( // 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(), constraint_mapping.stream()); + 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()); + constraint_mapping.stream()); pdlp_warm_start_data.last_restart_duality_gap_dual_solution_.resize( constraint_mapping.size(), constraint_mapping.stream()); @@ -350,7 +351,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; + pdlp_warm_start_data_view_.solved_by_pdlp_ = solved_by_pdlp; } template @@ -385,16 +386,26 @@ bool pdlp_solver_settings_t::has_initial_dual_solution() const } template -const std::optional>& -pdlp_solver_settings_t::get_pdlp_warm_start_data() const noexcept +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 -std::optional>& pdlp_solver_settings_t::get_pdlp_warm_start_data() +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/mip/local_search/feasibility_pump/feasibility_pump.cu b/cpp/src/mip/local_search/feasibility_pump/feasibility_pump.cu index c15a5d2f4..c8b1ecf92 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 e134cb575..de2f92ee1 100644 --- a/cpp/src/mip/relaxed_lp/relaxed_lp.cu +++ b/cpp/src/mip/relaxed_lp/relaxed_lp.cu @@ -102,7 +102,6 @@ optimization_problem_solution_t get_relaxed_lp_solution( "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 // TODO check that we do want to do problem checking here auto solver_response = solve_lp(op_problem, settings, true, true, true); 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..2194b7454 100644 --- a/python/cuopt/cuopt/tests/linear_programming/test_lp_solver.py +++ b/python/cuopt/cuopt/tests/linear_programming/test_lp_solver.py @@ -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): From 39c8e76bb914bc35576051193e1a558c31513b55 Mon Sep 17 00:00:00 2001 From: Nicolas Blin Date: Tue, 1 Jul 2025 14:13:37 +0000 Subject: [PATCH 3/8] code cleaning --- .../pdlp/solver_settings.hpp | 19 +++++++++----- .../cuopt/linear_programming/solve.hpp | 7 +++-- cpp/src/linear_programming/pdlp.cu | 20 +++++++------- cpp/src/linear_programming/pdlp.cuh | 2 +- cpp/src/linear_programming/solve.cu | 26 ++++++++++++------- cpp/src/linear_programming/solver_settings.cu | 25 ------------------ cpp/src/mip/relaxed_lp/relaxed_lp.cu | 7 +++-- 7 files changed, 50 insertions(+), 56 deletions(-) diff --git a/cpp/include/cuopt/linear_programming/pdlp/solver_settings.hpp b/cpp/include/cuopt/linear_programming/pdlp/solver_settings.hpp index a89543f50..39dc78fb8 100644 --- a/cpp/include/cuopt/linear_programming/pdlp/solver_settings.hpp +++ b/cpp/include/cuopt/linear_programming/pdlp/solver_settings.hpp @@ -110,9 +110,6 @@ class pdlp_solver_settings_t { i_t size, rmm::cuda_stream_view stream = rmm::cuda_stream_default); - // TODO comment - void set_initial_primal_solution(const rmm::device_uvector& initial_primal_solution); - /** * @brief Set an initial dual solution. * @@ -127,9 +124,6 @@ class pdlp_solver_settings_t { i_t size, rmm::cuda_stream_view stream = rmm::cuda_stream_default); - // TODO comment - void set_initial_dual_solution(const rmm::device_uvector& initial_dual_solution); - /** * @brief Set the pdlp warm start data. This allows to restart PDLP with a previous solution * @@ -168,12 +162,23 @@ class pdlp_solver_settings_t { f_t sum_solution_weight_, 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 */ - bool has_pdlp_warm_start_data() const; const pdlp_warm_start_data_t& get_pdlp_warm_start_data() const noexcept; pdlp_warm_start_data_t& get_pdlp_warm_start_data(); const pdlp_warm_start_data_view_t& get_pdlp_warm_start_data_view() const noexcept; diff --git a/cpp/include/cuopt/linear_programming/solve.hpp b/cpp/include/cuopt/linear_programming/solve.hpp index dd51f2755..bc72c5eb2 100644 --- a/cpp/include/cuopt/linear_programming/solve.hpp +++ b/cpp/include/cuopt/linear_programming/solve.hpp @@ -59,7 +59,8 @@ 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 * @@ -73,6 +74,7 @@ optimization_problem_solution_t solve_lp( * @param[in] problem_checking If true, the problem is checked for consistency. * @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 @@ -84,7 +86,8 @@ optimization_problem_solution_t solve_lp( bool inside_mip = false); /** - * @brief Linear programming solve function. + * @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/src/linear_programming/pdlp.cu b/cpp/src/linear_programming/pdlp.cu index 57e70f2b2..737905894 100644 --- a/cpp/src/linear_programming/pdlp.cu +++ b/cpp/src/linear_programming/pdlp.cu @@ -292,7 +292,7 @@ std::optional> pdlp_solver_t pdhg_solver_, pdhg_solver_.get_primal_solution(), pdhg_solver_.get_dual_solution(), - get_filled_warmed_start_data(true), + get_filled_warmed_start_data(), pdlp_termination_status_t::TimeLimit); } @@ -316,7 +316,7 @@ std::optional> pdlp_solver_t pdhg_solver_, pdhg_solver_.get_primal_solution(), pdhg_solver_.get_dual_solution(), - get_filled_warmed_start_data(true), + get_filled_warmed_start_data(), pdlp_termination_status_t::IterationLimit); } @@ -616,7 +616,7 @@ std::optional> pdlp_solver_t pdhg_solver_, pdhg_solver_.get_primal_solution(), pdhg_solver_.get_dual_solution(), - get_filled_warmed_start_data(true), + get_filled_warmed_start_data(), termination_current); } else // Average has better overall residual { @@ -625,7 +625,7 @@ std::optional> pdlp_solver_t pdhg_solver_, unscaled_primal_avg_solution_, unscaled_dual_avg_solution_, - get_filled_warmed_start_data(true), + get_filled_warmed_start_data(), termination_average); } } else if (termination_current == pdlp_termination_status_t::PrimalFeasible) { @@ -634,7 +634,7 @@ std::optional> pdlp_solver_t pdhg_solver_, pdhg_solver_.get_primal_solution(), pdhg_solver_.get_dual_solution(), - get_filled_warmed_start_data(true), + get_filled_warmed_start_data(), termination_current); } else if (termination_average == pdlp_termination_status_t::PrimalFeasible) { return average_termination_strategy_.fill_return_problem_solution( @@ -642,7 +642,7 @@ std::optional> pdlp_solver_t pdhg_solver_, unscaled_primal_avg_solution_, unscaled_dual_avg_solution_, - get_filled_warmed_start_data(true), + get_filled_warmed_start_data(), termination_average); } // else neither of the two is primal feasible @@ -676,7 +676,7 @@ std::optional> pdlp_solver_t pdhg_solver_, pdhg_solver_.get_primal_solution(), pdhg_solver_.get_dual_solution(), - get_filled_warmed_start_data(true), + get_filled_warmed_start_data(), termination_current); } else { #ifdef PDLP_VERBOSE_MODE @@ -692,7 +692,7 @@ std::optional> pdlp_solver_t pdhg_solver_, unscaled_primal_avg_solution_, unscaled_dual_avg_solution_, - get_filled_warmed_start_data(true), + get_filled_warmed_start_data(), termination_average); } } @@ -712,7 +712,7 @@ std::optional> pdlp_solver_t pdhg_solver_, unscaled_primal_avg_solution_, unscaled_dual_avg_solution_, - get_filled_warmed_start_data(true), + get_filled_warmed_start_data(), termination_average); } if (termination_current == pdlp_termination_status_t::Optimal) { @@ -727,7 +727,7 @@ std::optional> pdlp_solver_t pdhg_solver_, pdhg_solver_.get_primal_solution(), pdhg_solver_.get_dual_solution(), - get_filled_warmed_start_data(true), + get_filled_warmed_start_data(), termination_current); } diff --git a/cpp/src/linear_programming/pdlp.cuh b/cpp/src/linear_programming/pdlp.cuh index a3de4976f..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(bool solved_by_pdlp); + 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/solve.cu b/cpp/src/linear_programming/solve.cu index fc8699bfe..1c9f42f49 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); + 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,6 +363,7 @@ 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); } @@ -368,7 +374,7 @@ optimization_problem_solution_t run_pdlp(detail::problem_t& { 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); @@ -456,12 +462,13 @@ 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 @@ -492,7 +499,8 @@ 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, inside_mip); @@ -543,7 +551,7 @@ optimization_problem_solution_t solve_lp_with_method( 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(problem, settings, inside_mip); } else { diff --git a/cpp/src/linear_programming/solver_settings.cu b/cpp/src/linear_programming/solver_settings.cu index 5e042eac2..a82639fc1 100644 --- a/cpp/src/linear_programming/solver_settings.cu +++ b/cpp/src/linear_programming/solver_settings.cu @@ -79,18 +79,6 @@ void pdlp_solver_settings_t::set_initial_primal_solution( raft::copy(initial_primal_solution_.get()->data(), initial_primal_solution, size, stream); } -template -void pdlp_solver_settings_t::set_initial_primal_solution( - const rmm::device_uvector& initial_primal_solution) -{ - initial_primal_solution_ = std::make_shared>( - initial_primal_solution.size(), initial_primal_solution.stream()); - raft::copy(initial_primal_solution_.get()->data(), - initial_primal_solution.data(), - initial_primal_solution.size(), - initial_primal_solution.stream()); -} - template void pdlp_solver_settings_t::set_initial_dual_solution(const f_t* initial_dual_solution, i_t size, @@ -104,19 +92,6 @@ void pdlp_solver_settings_t::set_initial_dual_solution(const f_t* init raft::copy(initial_dual_solution_.get()->data(), initial_dual_solution, size, stream); } -// TODO could use a move constructor here -template -void pdlp_solver_settings_t::set_initial_dual_solution( - const rmm::device_uvector& initial_dual_solution) -{ - initial_dual_solution_ = std::make_shared>( - initial_dual_solution.size(), initial_dual_solution.stream()); - raft::copy(initial_dual_solution_.get()->data(), - initial_dual_solution.data(), - initial_dual_solution.size(), - initial_dual_solution.stream()); -} - template void pdlp_solver_settings_t::set_pdlp_warm_start_data( pdlp_warm_start_data_t& pdlp_warm_start_data_view, diff --git a/cpp/src/mip/relaxed_lp/relaxed_lp.cu b/cpp/src/mip/relaxed_lp/relaxed_lp.cu index de2f92ee1..9b7bbeedd 100644 --- a/cpp/src/mip/relaxed_lp/relaxed_lp.cu +++ b/cpp/src/mip/relaxed_lp/relaxed_lp.cu @@ -95,8 +95,11 @@ optimization_problem_solution_t get_relaxed_lp_solution( if (!isfinite(x) || i >= prev_size) { return 0.0; } return x; }); - settings.set_initial_primal_solution(lp_state.prev_primal); - settings.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); From 3cbfd9114520324568fe981fc2b445f28c2102b6 Mon Sep 17 00:00:00 2001 From: Nicolas Blin Date: Fri, 4 Jul 2025 11:47:29 +0200 Subject: [PATCH 4/8] fix solve lp original problem issues --- cpp/src/linear_programming/solve.cu | 19 +++++++--- cpp/src/linear_programming/translate.hpp | 7 ++-- .../utilities/problem_checking.cu | 37 +++++++++++++------ .../utilities/problem_checking.cuh | 7 +++- 4 files changed, 48 insertions(+), 22 deletions(-) diff --git a/cpp/src/linear_programming/solve.cu b/cpp/src/linear_programming/solve.cu index 1c9f42f49..1168601e3 100644 --- a/cpp/src/linear_programming/solve.cu +++ b/cpp/src/linear_programming/solve.cu @@ -340,7 +340,7 @@ optimization_problem_solution_t run_dual_simplex( { // Convert data structures to dual simplex format and back dual_simplex::user_problem_t dual_simplex_problem = - cuopt_problem_to_simplex_problem(problem); + 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), @@ -491,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>> @@ -579,9 +579,18 @@ optimization_problem_solution_t solve_lp(detail::problem_t& if (problem_checking) { raft::common::nvtx::range fun_scope("Check problem representation"); // This is required as user might forget to set some fields - problem_checking_t::check_problem_representation(*problem.original_problem_ptr); - problem_checking_t::check_initial_solution_representation( - *problem.original_problem_ptr, settings); + if (!inside_mip) + { + problem_checking_t::check_problem_representation(*problem.original_problem_ptr); + problem_checking_t::check_initial_solution_representation( + *problem.original_problem_ptr, settings); + } + else + { + problem.check_problem_representation(true, true); + 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", diff --git a/cpp/src/linear_programming/translate.hpp b/cpp/src/linear_programming/translate.hpp index 6731be6ec..95a66c973 100644 --- a/cpp/src/linear_programming/translate.hpp +++ b/cpp/src/linear_programming/translate.hpp @@ -28,7 +28,8 @@ 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 +85,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..7cde78123 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,26 @@ 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 +127,7 @@ 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..c615f3b2a 100644 --- a/cpp/src/linear_programming/utilities/problem_checking.cuh +++ b/cpp/src/linear_programming/utilities/problem_checking.cuh @@ -36,14 +36,17 @@ 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); From f8b1c91246df72db6f33fb57d3275f7b5b257b8a Mon Sep 17 00:00:00 2001 From: Nicolas Blin Date: Fri, 4 Jul 2025 12:57:37 +0000 Subject: [PATCH 5/8] fix style --- cpp/src/linear_programming/solve.cu | 10 +++------ cpp/src/linear_programming/translate.hpp | 3 +-- .../utilities/problem_checking.cu | 21 +++++++++++-------- .../utilities/problem_checking.cuh | 3 +-- 4 files changed, 17 insertions(+), 20 deletions(-) diff --git a/cpp/src/linear_programming/solve.cu b/cpp/src/linear_programming/solve.cu index 1168601e3..cc12e12e7 100644 --- a/cpp/src/linear_programming/solve.cu +++ b/cpp/src/linear_programming/solve.cu @@ -579,17 +579,13 @@ optimization_problem_solution_t solve_lp(detail::problem_t& if (problem_checking) { raft::common::nvtx::range fun_scope("Check problem representation"); // This is required as user might forget to set some fields - if (!inside_mip) - { + if (!inside_mip) { problem_checking_t::check_problem_representation(*problem.original_problem_ptr); problem_checking_t::check_initial_solution_representation( *problem.original_problem_ptr, settings); - } - else - { + } else { problem.check_problem_representation(true, true); - problem_checking_t::check_initial_solution_representation( - problem, settings); + problem_checking_t::check_initial_solution_representation(problem, settings); } } CUOPT_LOG_INFO( diff --git a/cpp/src/linear_programming/translate.hpp b/cpp/src/linear_programming/translate.hpp index 95a66c973..59750623f 100644 --- a/cpp/src/linear_programming/translate.hpp +++ b/cpp/src/linear_programming/translate.hpp @@ -28,8 +28,7 @@ namespace cuopt::linear_programming { template static dual_simplex::user_problem_t cuopt_problem_to_simplex_problem( - detail::problem_t& model, - bool inside_mip) + detail::problem_t& model, bool inside_mip) { dual_simplex::user_problem_t user_problem; diff --git a/cpp/src/linear_programming/utilities/problem_checking.cu b/cpp/src/linear_programming/utilities/problem_checking.cu index 7cde78123..8b2c7d0d5 100644 --- a/cpp/src/linear_programming/utilities/problem_checking.cu +++ b/cpp/src/linear_programming/utilities/problem_checking.cu @@ -98,26 +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.get_objective_coefficients(), 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(); + 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) + 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()); + check_initial_primal_representation(problem.objective_coefficients, + settings.get_initial_primal_solution()); } if (settings.initial_dual_solution_.get() != nullptr) { - check_initial_dual_representation(problem.constraint_lower_bounds, settings.get_initial_dual_solution()); + check_initial_dual_representation(problem.constraint_lower_bounds, + settings.get_initial_dual_solution()); } } @@ -127,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.get_objective_coefficients(), 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 c615f3b2a..8aa265afd 100644 --- a/cpp/src/linear_programming/utilities/problem_checking.cuh +++ b/cpp/src/linear_programming/utilities/problem_checking.cuh @@ -45,8 +45,7 @@ class problem_checking_t { 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); + 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); From 364a4d05013e326cc79647e95d6237f54827b998 Mon Sep 17 00:00:00 2001 From: Nicolas Blin Date: Fri, 4 Jul 2025 13:23:53 +0000 Subject: [PATCH 6/8] address PR comments feedback --- cpp/src/linear_programming/pdlp.cu | 4 ++-- cpp/src/linear_programming/solve.cu | 1 - .../cuopt_server/utils/linear_programming/solver.py | 1 + 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cpp/src/linear_programming/pdlp.cu b/cpp/src/linear_programming/pdlp.cu index 737905894..bbc57b04e 100644 --- a/cpp/src/linear_programming/pdlp.cu +++ b/cpp/src/linear_programming/pdlp.cu @@ -131,12 +131,12 @@ pdlp_solver_t::pdlp_solver_t(problem_t& op_problem, 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_INFO( + 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_INFO( + CUOPT_LOG_DEBUG( "Trying to use warm start with trust region restart (neither Stable nor Fast1), skipping " "warm start"); } else { diff --git a/cpp/src/linear_programming/solve.cu b/cpp/src/linear_programming/solve.cu index cc12e12e7..41ea2fcdf 100644 --- a/cpp/src/linear_programming/solve.cu +++ b/cpp/src/linear_programming/solve.cu @@ -523,7 +523,6 @@ 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"); - // TODO confirm with Akif that using the problem handle ptr is not a problem 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", 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 From 85d23623639fbff41490779e1c04fe7422118d88 Mon Sep 17 00:00:00 2001 From: Nicolas Blin Date: Fri, 4 Jul 2025 13:36:51 +0000 Subject: [PATCH 7/8] clarify bool parameter when calling relaxed_lp and added problem checking only in assert and debug --- cpp/src/mip/relaxed_lp/relaxed_lp.cu | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/cpp/src/mip/relaxed_lp/relaxed_lp.cu b/cpp/src/mip/relaxed_lp/relaxed_lp.cu index 9b7bbeedd..a8b0c1a97 100644 --- a/cpp/src/mip/relaxed_lp/relaxed_lp.cu +++ b/cpp/src/mip/relaxed_lp/relaxed_lp.cu @@ -105,8 +105,17 @@ optimization_problem_solution_t get_relaxed_lp_solution( "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(); - // TODO check that we do want to do problem checking here - auto solver_response = solve_lp(op_problem, settings, true, true, true); + // Check the problem only when in assert or debug mode +#ifdef ASSERT_MODE + const bool problem_checking = true; +#else + const bool problem_checking = false; +#endif + const bool use_pdlp_solver_mode = true; + const bool inside_mip = true; + + auto solver_response = + solve_lp(op_problem, settings, problem_checking, use_pdlp_solver_mode, inside_mip); if (solver_response.get_primal_solution().size() != 0 && solver_response.get_dual_solution().size() != 0 && save_state) { From 1a206a06d61a70d019f461089e26f19dfbbeb3b8 Mon Sep 17 00:00:00 2001 From: Nicolas Blin Date: Fri, 11 Jul 2025 12:56:13 +0000 Subject: [PATCH 8/8] fix problem checking logic --- .../cuopt/linear_programming/solve.hpp | 2 - cpp/src/linear_programming/solve.cu | 38 +++++++++++-------- cpp/src/mip/relaxed_lp/relaxed_lp.cu | 8 +--- .../linear_programming/test_lp_solver.py | 14 +++---- 4 files changed, 30 insertions(+), 32 deletions(-) diff --git a/cpp/include/cuopt/linear_programming/solve.hpp b/cpp/include/cuopt/linear_programming/solve.hpp index bc72c5eb2..899d867da 100644 --- a/cpp/include/cuopt/linear_programming/solve.hpp +++ b/cpp/include/cuopt/linear_programming/solve.hpp @@ -71,7 +71,6 @@ optimization_problem_solution_t solve_lp( * representation of a linear program * @param[in] settings A pdlp_solver_settings_t object with the settings for the PDLP * solver. - * @param[in] problem_checking If true, the problem is checked for consistency. * @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. @@ -81,7 +80,6 @@ template optimization_problem_solution_t solve_lp( detail::problem_t& problem, pdlp_solver_settings_t const& settings = pdlp_solver_settings_t{}, - bool problem_checking = true, bool use_pdlp_solver_mode = true, bool inside_mip = false); diff --git a/cpp/src/linear_programming/solve.cu b/cpp/src/linear_programming/solve.cu index 41ea2fcdf..0f90665d8 100644 --- a/cpp/src/linear_programming/solve.cu +++ b/cpp/src/linear_programming/solve.cu @@ -561,7 +561,6 @@ optimization_problem_solution_t solve_lp_with_method( template 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 inside_mip) { @@ -575,18 +574,10 @@ optimization_problem_solution_t solve_lp(detail::problem_t& raft::common::nvtx::range fun_scope("Running solver"); - if (problem_checking) { - raft::common::nvtx::range fun_scope("Check problem representation"); - // This is required as user might forget to set some fields - if (!inside_mip) { - problem_checking_t::check_problem_representation(*problem.original_problem_ptr); - problem_checking_t::check_initial_solution_representation( - *problem.original_problem_ptr, settings); - } else { - problem.check_problem_representation(true, true); - problem_checking_t::check_initial_solution_representation(problem, settings); - } - } + 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, @@ -632,8 +623,24 @@ optimization_problem_solution_t solve_lp(optimization_problem_t problem(op_problem); - return solve_lp(problem, settings, problem_checking, 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()}; + } 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), + op_problem.get_handle_ptr()->get_stream()}; + } } template @@ -727,7 +734,6 @@ optimization_problem_solution_t solve_lp( template 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 inside_mip); \ template optimization_problem_solution_t solve_lp( \ diff --git a/cpp/src/mip/relaxed_lp/relaxed_lp.cu b/cpp/src/mip/relaxed_lp/relaxed_lp.cu index a8b0c1a97..35f0db798 100644 --- a/cpp/src/mip/relaxed_lp/relaxed_lp.cu +++ b/cpp/src/mip/relaxed_lp/relaxed_lp.cu @@ -106,16 +106,10 @@ optimization_problem_solution_t get_relaxed_lp_solution( // before LP flush the logs as it takes quite some time cuopt::default_logger().flush(); // Check the problem only when in assert or debug mode -#ifdef ASSERT_MODE - const bool problem_checking = true; -#else - const bool problem_checking = false; -#endif const bool use_pdlp_solver_mode = true; const bool inside_mip = true; - auto solver_response = - solve_lp(op_problem, settings, problem_checking, use_pdlp_solver_mode, inside_mip); + 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/python/cuopt/cuopt/tests/linear_programming/test_lp_solver.py b/python/cuopt/cuopt/tests/linear_programming/test_lp_solver.py index 2194b7454..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)