From 3cc5a5ce7100c548e5fbed794a2c2d402d98ce5a Mon Sep 17 00:00:00 2001 From: Rajesh Gandham Date: Thu, 17 Jul 2025 19:48:39 -0700 Subject: [PATCH 001/108] Implement papilo presolve interface --- cpp/CMakeLists.txt | 4 + .../mip/solver_solution.hpp | 3 + cpp/src/mip/CMakeLists.txt | 1 + cpp/src/mip/presolve/third_party_presolve.cu | 213 ++++++++++++++++++ cpp/src/mip/presolve/third_party_presolve.cuh | 41 ++++ cpp/src/mip/solution/solution.cu | 35 +-- cpp/src/mip/solution/solution.cuh | 4 +- cpp/src/mip/solve.cu | 61 +++-- cpp/src/mip/solver_solution.cu | 20 ++ 9 files changed, 349 insertions(+), 33 deletions(-) create mode 100644 cpp/src/mip/presolve/third_party_presolve.cu create mode 100644 cpp/src/mip/presolve/third_party_presolve.cuh diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 2286bd6dcc..a4326efafb 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -153,6 +153,10 @@ else() find_package(RMM REQUIRED) find_package(RAFT REQUIRED) endif() + +include_directories("/raid/rgandham/opt/papilo/include") + + include(${rapids-cmake-dir}/cpm/rapids_logger.cmake) # generate logging macros rapids_cpm_rapids_logger(BUILD_EXPORT_SET cuopt-exports INSTALL_EXPORT_SET cuopt-exports) diff --git a/cpp/include/cuopt/linear_programming/mip/solver_solution.hpp b/cpp/include/cuopt/linear_programming/mip/solver_solution.hpp index b29db43db6..fc98e9afff 100644 --- a/cpp/include/cuopt/linear_programming/mip/solver_solution.hpp +++ b/cpp/include/cuopt/linear_programming/mip/solver_solution.hpp @@ -64,6 +64,7 @@ class mip_solution_t : public base_solution_t { bool is_mip() const override { return true; } const rmm::device_uvector& get_solution() const; rmm::device_uvector& get_solution(); + f_t get_objective_value() const; f_t get_mip_gap() const; f_t get_solution_bound() const; @@ -76,11 +77,13 @@ class mip_solution_t : public base_solution_t { f_t get_max_constraint_violation() const; f_t get_max_int_violation() const; f_t get_max_variable_bound_violation() const; + solver_stats_t get_stats() const; i_t get_num_nodes() const; i_t get_num_simplex_iterations() const; const std::vector& get_variable_names() const; const std::vector>& get_solution_pool() const; void write_to_sol_file(std::string_view filename, rmm::cuda_stream_view stream_view) const; + void log_summary() const; private: rmm::device_uvector solution_; diff --git a/cpp/src/mip/CMakeLists.txt b/cpp/src/mip/CMakeLists.txt index d3bdf3cf89..6739a63af4 100644 --- a/cpp/src/mip/CMakeLists.txt +++ b/cpp/src/mip/CMakeLists.txt @@ -40,6 +40,7 @@ list(PREPEND ${CMAKE_CURRENT_SOURCE_DIR}/presolve/load_balanced_bounds_presolve.cu ${CMAKE_CURRENT_SOURCE_DIR}/presolve/multi_probe.cu ${CMAKE_CURRENT_SOURCE_DIR}/presolve/probing_cache.cu + ${CMAKE_CURRENT_SOURCE_DIR}/presolve/third_party_presolve.cu ${CMAKE_CURRENT_SOURCE_DIR}/presolve/trivial_presolve.cu ${CMAKE_CURRENT_SOURCE_DIR}/problem/load_balanced_problem.cu ${CMAKE_CURRENT_SOURCE_DIR}/feasibility_jump/feasibility_jump.cu diff --git a/cpp/src/mip/presolve/third_party_presolve.cu b/cpp/src/mip/presolve/third_party_presolve.cu new file mode 100644 index 0000000000..eac119cd22 --- /dev/null +++ b/cpp/src/mip/presolve/third_party_presolve.cu @@ -0,0 +1,213 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +namespace cuopt::linear_programming::detail { + +template +papilo::Problem build_papilo_problem(const optimization_problem_t& op_problem) +{ + // Build papilo problem from optimization problem + papilo::ProblemBuilder builder; + + // Get problem dimensions + const i_t num_cols = op_problem.get_n_variables(); + const i_t num_rows = op_problem.get_n_constraints(); + const i_t nnz = op_problem.get_nnz(); + + // Set problem dimensions in builder + builder.reserve(nnz, num_rows, num_cols); + + // Get problem data from optimization problem + const auto& coefficients = op_problem.get_constraint_matrix_values(); + const auto& offsets = op_problem.get_constraint_matrix_offsets(); + const auto& variables = op_problem.get_constraint_matrix_indices(); + const auto& obj_coeffs = op_problem.get_objective_coefficients(); + const auto& var_lb = op_problem.get_variable_lower_bounds(); + const auto& var_ub = op_problem.get_variable_upper_bounds(); + const auto& constr_lb = op_problem.get_constraint_lower_bounds(); + const auto& constr_ub = op_problem.get_constraint_upper_bounds(); + const auto& var_types = op_problem.get_variable_types(); + + // Copy data to host + std::vector h_coefficients = cuopt::host_copy(coefficients); + std::vector h_offsets = cuopt::host_copy(offsets); + std::vector h_variables = cuopt::host_copy(variables); + std::vector h_obj_coeffs = cuopt::host_copy(obj_coeffs); + std::vector h_var_lb = cuopt::host_copy(var_lb); + std::vector h_var_ub = cuopt::host_copy(var_ub); + std::vector h_constr_lb = cuopt::host_copy(constr_lb); + std::vector h_constr_ub = cuopt::host_copy(constr_ub); + std::vector h_var_types = cuopt::host_copy(var_types); + + builder.setNumCols(num_cols); + builder.setNumRows(num_rows); + + builder.setObjAll(h_obj_coeffs); + builder.setObjOffset(op_problem.get_objective_offset()); + + builder.setColLbAll(h_var_lb); + builder.setColUbAll(h_var_ub); + + for (i_t i = 0; i < num_cols; i++) { + builder.setColIntegral(i, h_var_types[i] == var_t::INTEGER); + } + + builder.setRowLhsAll(h_constr_lb); + builder.setRowRhsAll(h_constr_ub); + + // Add constraints row by row + for (i_t i = 0; i < num_rows; i++) { + // Get row entries + i_t row_start = h_offsets[i]; + i_t row_end = h_offsets[i + 1]; + i_t num_entries = row_end - row_start; + builder.addRowEntries( + i, num_entries, h_variables.data() + row_start, h_coefficients.data() + row_start); + } + + return builder.build(); +} + +template +optimization_problem_t build_optimization_problem( + const papilo::Problem& papilo_problem, raft::handle_t const* handle_ptr) +{ + optimization_problem_t op_problem(handle_ptr); + + auto obj = papilo_problem.getObjective(); + op_problem.set_objective_coefficients(obj.coefficients.data(), obj.coefficients.size()); + op_problem.set_objective_offset(obj.offset); + + auto col_lower = papilo_problem.getLowerBounds(); + auto col_upper = papilo_problem.getUpperBounds(); + op_problem.set_variable_lower_bounds(col_lower.data(), col_lower.size()); + op_problem.set_variable_upper_bounds(col_upper.data(), col_upper.size()); + + auto& constraint_matrix = papilo_problem.getConstraintMatrix(); + auto row_lower = constraint_matrix.getLeftHandSides(); + auto row_upper = constraint_matrix.getRightHandSides(); + op_problem.set_constraint_lower_bounds(row_lower.data(), row_lower.size()); + op_problem.set_constraint_upper_bounds(row_upper.data(), row_upper.size()); + + auto [index_range, nrows] = constraint_matrix.getRangeInfo(); + + std::vector offsets(nrows + 1); + // papilo indices do not start from 0 after presolve + size_t start = index_range[0].start; + for (i_t i = 0; i < nrows; i++) { + offsets[i] = index_range[i].start - start; + } + offsets[nrows] = index_range[nrows - 1].end - start; + + i_t nnz = constraint_matrix.getNnz(); + assert(offsets[nrows] == nnz); + + const int* cols = constraint_matrix.getConstraintMatrix().getColumns(); + const f_t* coeffs = constraint_matrix.getConstraintMatrix().getValues(); + op_problem.set_csr_constraint_matrix( + &(coeffs[start]), nnz, &(cols[start]), nnz, offsets.data(), nrows + 1); + + auto col_flags = papilo_problem.getColFlags(); + std::vector var_types(col_flags.size()); + for (size_t i = 0; i < col_flags.size(); i++) { + var_types[i] = + col_flags[i].test(papilo::ColFlag::kIntegral) ? var_t::INTEGER : var_t::CONTINUOUS; + } + op_problem.set_variable_types(var_types.data(), var_types.size()); + + return op_problem; +} + +#define USE_PAPILOS_PRESOLVER 1 + +template +optimization_problem_t third_party_presolve_t::apply( + optimization_problem_t& op_problem, double time_limit) +{ +#if USE_PAPILOS_PRESOLVER + papilo::Problem papilo_problem = build_papilo_problem(op_problem); + + CUOPT_LOG_INFO("Unpresolved problem:: Num variables: %d Num constraints: %d, NNZ: %d", + papilo_problem.getNCols(), + papilo_problem.getNRows(), + papilo_problem.getConstraintMatrix().getNnz()); + + papilo::Presolve presolver; + presolver.addDefaultPresolvers(); + presolver.getPresolveOptions().tlim = time_limit; + + auto result = presolver.apply(papilo_problem); + + post_solve_storage_ = result.postsolve; + + CUOPT_LOG_INFO("Presolved problem:: Num variables: %d Num constraints: %d, NNZ: %d", + papilo_problem.getNCols(), + papilo_problem.getNRows(), + papilo_problem.getConstraintMatrix().getNnz()); + + return build_optimization_problem(papilo_problem, op_problem.get_handle_ptr()); +#else + return op_problem; +#endif +} + +template +rmm::device_uvector third_party_presolve_t::undo( + rmm::device_uvector& reduced_sol_vec) +{ +#if USE_PAPILOS_PRESOLVER + auto reduced_sol_vec_h = cuopt::host_copy(reduced_sol_vec); + + papilo::Solution reduced_sol(reduced_sol_vec_h); + papilo::Solution full_sol; + + papilo::Message Msg{}; + Msg.setVerbosityLevel(papilo::VerbosityLevel::kQuiet); + papilo::Postsolve post_solver{Msg, post_solve_storage_.getNum()}; + + bool is_optimal = false; + auto status = post_solver.undo(reduced_sol, full_sol, post_solve_storage_, is_optimal); + if (status != papilo::PostsolveStatus::kOk) { CUOPT_LOG_INFO("\n Post-solve failed"); } + + std::cout << "primal solution after post solve:" << std::endl; + + // FIXME: recompute objective value, mip gap, max constraint violation, etc. + auto full_sol_vec = cuopt::device_copy(full_sol.primal, reduced_sol_vec.stream()); + return full_sol_vec; +#else + rmm::device_uvector full_sol_vec(reduced_sol_vec.size(), reduced_sol_vec.stream()); + raft::copy( + full_sol_vec.data(), reduced_sol_vec.data(), reduced_sol_vec.size(), reduced_sol_vec.stream()); + return full_sol_vec; +#endif +} + +#if MIP_INSTANTIATE_FLOAT +template class third_party_presolve_t; +#endif + +#if MIP_INSTANTIATE_DOUBLE +template class third_party_presolve_t; +#endif + +} // namespace cuopt::linear_programming::detail diff --git a/cpp/src/mip/presolve/third_party_presolve.cuh b/cpp/src/mip/presolve/third_party_presolve.cuh new file mode 100644 index 0000000000..0f59d49268 --- /dev/null +++ b/cpp/src/mip/presolve/third_party_presolve.cuh @@ -0,0 +1,41 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +#include +namespace cuopt::linear_programming::detail { + +template +class third_party_presolve_t { + public: + third_party_presolve_t() = default; + + optimization_problem_t apply(optimization_problem_t& op_problem, + double time_limit); + + rmm::device_uvector undo(rmm::device_uvector& reduced_sol_vec); + + private: + papilo::PostsolveStorage post_solve_storage_; +}; + +} // namespace cuopt::linear_programming::detail diff --git a/cpp/src/mip/solution/solution.cu b/cpp/src/mip/solution/solution.cu index 74ec4c41cf..f4131e02d0 100644 --- a/cpp/src/mip/solution/solution.cu +++ b/cpp/src/mip/solution/solution.cu @@ -557,7 +557,8 @@ f_t solution_t::compute_max_variable_violation() // returns the solution after applying the conversions template mip_solution_t solution_t::get_solution(bool output_feasible, - solver_stats_t stats) + solver_stats_t stats, + bool log_stats) { cuopt::default_logger().flush(); cuopt_expects( @@ -576,21 +577,23 @@ mip_solution_t solution_t::get_solution(bool output_feasible i_t num_nodes = stats.num_nodes; i_t num_simplex_iterations = stats.num_simplex_iterations; handle_ptr->sync_stream(); - CUOPT_LOG_INFO( - "Solution objective: %f , relative_mip_gap %f solution_bound %f presolve_time %f " - "total_solve_time %f " - "max constraint violation %f max int violation %f max var bounds violation %f " - "nodes %d simplex_iterations %d", - h_user_obj, - rel_mip_gap, - solution_bound, - presolve_time, - total_solve_time, - max_constraint_violation, - max_int_violation, - max_variable_bound_violation, - num_nodes, - num_simplex_iterations); + if (log_stats) { + CUOPT_LOG_INFO( + "Solution objective: %f , relative_mip_gap %f solution_bound %f presolve_time %f " + "total_solve_time %f " + "max constraint violation %f max int violation %f max var bounds violation %f " + "nodes %d simplex_iterations %d", + h_user_obj, + rel_mip_gap, + solution_bound, + presolve_time, + total_solve_time, + max_constraint_violation, + max_int_violation, + max_variable_bound_violation, + num_nodes, + num_simplex_iterations); + } const bool not_optimal = rel_mip_gap > problem_ptr->tolerances.relative_mip_gap && abs_mip_gap > problem_ptr->tolerances.absolute_mip_gap; diff --git a/cpp/src/mip/solution/solution.cuh b/cpp/src/mip/solution/solution.cuh index 729a5c0e55..0774bdef2b 100644 --- a/cpp/src/mip/solution/solution.cuh +++ b/cpp/src/mip/solution/solution.cuh @@ -100,7 +100,9 @@ class solution_t { f_t get_total_excess(); // brings all vars within bounds void clamp_within_bounds(); - mip_solution_t get_solution(bool output_feasible, solver_stats_t stats); + mip_solution_t get_solution(bool output_feasible, + solver_stats_t stats, + bool log_stats = true); f_t compute_max_constraint_violation(); f_t compute_max_int_violation(); f_t compute_max_variable_violation(); diff --git a/cpp/src/mip/solve.cu b/cpp/src/mip/solve.cu index dcfcdd0b1e..7c1daadadd 100644 --- a/cpp/src/mip/solve.cu +++ b/cpp/src/mip/solve.cu @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -65,17 +66,9 @@ static void setup_device_symbols(rmm::cuda_stream_view stream_view) template mip_solution_t run_mip(detail::problem_t& problem, - mip_solver_settings_t const& settings) + mip_solver_settings_t const& settings, + cuopt::timer_t& timer) { - const f_t time_limit = - settings.time_limit == 0 ? std::numeric_limits::max() : settings.time_limit; - if (settings.heuristics_only && time_limit == std::numeric_limits::max()) { - CUOPT_LOG_ERROR("Time limit cannot be infinity when heuristics only is set"); - cuopt_expects(false, - error_type_t::RuntimeError, - "Time limit cannot be infinity when heuristics only is set"); - } - auto timer = cuopt::timer_t(time_limit); auto constexpr const running_mip = true; pdlp_hyper_params::update_primal_weight_on_initial_solution = false; @@ -96,7 +89,7 @@ mip_solution_t run_mip(detail::problem_t& problem, solution.compute_objective(); // just to ensure h_user_obj is set auto stats = solver_stats_t{}; stats.solution_bound = solution.get_user_objective(); - return solution.get_solution(true, stats); + return solution.get_solution(true, stats, false); } // problem contains unpreprocessed data detail::problem_t scaled_problem(problem); @@ -149,8 +142,8 @@ mip_solution_t run_mip(detail::problem_t& problem, "please provide a more numerically stable problem."); } - auto sol = scaled_sol.get_solution(is_feasible_before_scaling || is_feasible_after_unscaling, - solver.get_solver_stats()); + auto sol = scaled_sol.get_solution( + is_feasible_before_scaling || is_feasible_after_unscaling, solver.get_solver_stats(), false); detail::print_solution(scaled_problem.handle_ptr, sol.get_solution()); return sol; } @@ -160,6 +153,15 @@ mip_solution_t solve_mip(optimization_problem_t& op_problem, mip_solver_settings_t const& settings) { try { + const f_t time_limit = + settings.time_limit == 0 ? std::numeric_limits::max() : settings.time_limit; + if (settings.heuristics_only && time_limit == std::numeric_limits::max()) { + CUOPT_LOG_ERROR("Time limit cannot be infinity when heuristics only is set"); + cuopt_expects(false, + error_type_t::RuntimeError, + "Time limit cannot be infinity when heuristics only is set"); + } + // Create log stream for file logging and add it to default logger init_logger_t log(settings.log_file, settings.log_to_console); // Init libraies before to not include it in solve time @@ -172,8 +174,18 @@ mip_solution_t solve_mip(optimization_problem_t& op_problem, problem_checking_t::check_problem_representation(op_problem); problem_checking_t::check_initial_solution_representation(op_problem, settings); + auto timer = cuopt::timer_t(time_limit); + // allocate note more than 10% of the time limit to presolve. + // Note that this is not the presolve time, but the time limit for presolve. + const double presolve_time_limit = 0.1 * time_limit; + detail::third_party_presolve_t presolver; + auto presolved_problem = presolver.apply(op_problem, presolve_time_limit); + const double presolve_time = timer.elapsed_time(); + + CUOPT_LOG_INFO("Third party presolve time: %f", presolve_time); + // have solve, problem, solution, utils etc. in common dir - detail::problem_t problem(op_problem, settings.get_tolerances()); + detail::problem_t problem(presolved_problem, settings.get_tolerances()); if (settings.user_problem_file != "") { CUOPT_LOG_INFO("Writing user problem to file: %s", settings.user_problem_file.c_str()); problem.write_as_mps(settings.user_problem_file); @@ -182,13 +194,30 @@ mip_solution_t solve_mip(optimization_problem_t& op_problem, // this is for PDLP, i think this should be part of pdlp solver setup_device_symbols(op_problem.get_handle_ptr()->get_stream()); - auto sol = run_mip(problem, settings); + auto reduced_solution = run_mip(problem, settings, timer); + auto full_sol_vec = presolver.undo(reduced_solution.get_solution()); + detail::problem_t full_problem(op_problem); + detail::solution_t full_sol(full_problem); + full_sol.copy_new_assignment(cuopt::host_copy(full_sol_vec)); + full_sol.compute_feasibility(); + if (!full_sol.get_feasible()) { + CUOPT_LOG_WARN("The solution is not feasible after post solve"); + } + + auto full_stats = reduced_solution.get_stats(); + // add third party presolve time to cuopt presolve time + full_stats.presolve_time += presolve_time; + + // FIXME:: reduced_solution.get_stats() is not correct, we need to compute the stats for the + // full problem + full_sol.post_process_completed = true; // hack + auto sol = full_sol.get_solution(true, full_stats, true); 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.log_summary(); return sol; } catch (const cuopt::logic_error& e) { CUOPT_LOG_ERROR("Error in solve_mip: %s", e.what()); diff --git a/cpp/src/mip/solver_solution.cu b/cpp/src/mip/solver_solution.cu index a6761aaff9..1e579d600c 100644 --- a/cpp/src/mip/solver_solution.cu +++ b/cpp/src/mip/solver_solution.cu @@ -177,6 +177,12 @@ f_t mip_solution_t::get_max_variable_bound_violation() const return max_variable_bound_violation_; } +template +solver_stats_t mip_solution_t::get_stats() const +{ + return stats_; +} + template i_t mip_solution_t::get_num_nodes() const { @@ -223,6 +229,20 @@ void mip_solution_t::write_to_sol_file(std::string_view filename, std::string(filename), status, objective_value, var_names, solution); } +template +void mip_solution_t::log_summary() const +{ + // CUOPT_LOG_INFO("Termination Status: {}", get_termination_status_string()); + CUOPT_LOG_INFO("Objective Value: %f", get_objective_value()); + CUOPT_LOG_INFO("Max constraint violation: %f", get_max_constraint_violation()); + CUOPT_LOG_INFO("Max integer violation: %f", get_max_int_violation()); + CUOPT_LOG_INFO("Max variable bound violation: %f", get_max_variable_bound_violation()); + CUOPT_LOG_INFO("MIP Gap: %f", get_mip_gap()); + CUOPT_LOG_INFO("Solution Bound: %f", get_solution_bound()); + CUOPT_LOG_INFO("Total Solve Time: %f", get_total_solve_time()); + CUOPT_LOG_INFO("Presolve Time: %f", get_presolve_time()); +} + #if MIP_INSTANTIATE_FLOAT template class mip_solution_t; #endif From 5338bab8cfc8f0203dbc8b2942872d6cf9420b6c Mon Sep 17 00:00:00 2001 From: Rajesh Gandham Date: Fri, 18 Jul 2025 10:12:00 -0700 Subject: [PATCH 002/108] Fix handling of constraint bounds --- cpp/src/mip/presolve/third_party_presolve.cu | 47 ++++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/cpp/src/mip/presolve/third_party_presolve.cu b/cpp/src/mip/presolve/third_party_presolve.cu index eac119cd22..0f9ffd3178 100644 --- a/cpp/src/mip/presolve/third_party_presolve.cu +++ b/cpp/src/mip/presolve/third_party_presolve.cu @@ -106,11 +106,23 @@ optimization_problem_t build_optimization_problem( auto& constraint_matrix = papilo_problem.getConstraintMatrix(); auto row_lower = constraint_matrix.getLeftHandSides(); auto row_upper = constraint_matrix.getRightHandSides(); - op_problem.set_constraint_lower_bounds(row_lower.data(), row_lower.size()); - op_problem.set_constraint_upper_bounds(row_upper.data(), row_upper.size()); + + auto row_flags = constraint_matrix.getRowFlags(); + for (size_t i = 0; i < row_flags.size(); i++) { + // Looks like the bounds are not updated correctly in papilo + if (row_flags[i].test(papilo::RowFlag::kLhsInf)) { + row_lower[i] = -std::numeric_limits::infinity(); + } + if (row_flags[i].test(papilo::RowFlag::kRhsInf)) { + row_upper[i] = std::numeric_limits::infinity(); + } + } auto [index_range, nrows] = constraint_matrix.getRangeInfo(); + op_problem.set_constraint_lower_bounds(row_lower.data(), row_lower.size()); + op_problem.set_constraint_upper_bounds(row_upper.data(), row_upper.size()); + std::vector offsets(nrows + 1); // papilo indices do not start from 0 after presolve size_t start = index_range[0].start; @@ -138,6 +150,33 @@ optimization_problem_t build_optimization_problem( return op_problem; } +void check_presolve_status(const papilo::PresolveStatus& status) +{ + switch (status) { + case papilo::PresolveStatus::kUnchanged: + CUOPT_LOG_INFO("Presolve did not result in any changes"); + break; + case papilo::PresolveStatus::kReduced: CUOPT_LOG_INFO("Presolve reduced the problem"); break; + case papilo::PresolveStatus::kUnbndOrInfeas: + CUOPT_LOG_INFO("Presolve found an unbounded or infeasible problem"); + break; + case papilo::PresolveStatus::kInfeasible: + CUOPT_LOG_INFO("Presolve found an infeasible problem"); + break; + case papilo::PresolveStatus::kUnbounded: + CUOPT_LOG_INFO("Presolve found an unbounded problem"); + break; + } +} + +void check_postsolve_status(const papilo::PostsolveStatus& status) +{ + switch (status) { + case papilo::PostsolveStatus::kOk: CUOPT_LOG_INFO("Post-solve succeeded"); break; + case papilo::PostsolveStatus::kFailed: CUOPT_LOG_INFO("Post-solve failed"); break; + } +} + #define USE_PAPILOS_PRESOLVER 1 template @@ -160,6 +199,7 @@ optimization_problem_t third_party_presolve_t::apply( post_solve_storage_ = result.postsolve; + check_presolve_status(result.status); CUOPT_LOG_INFO("Presolved problem:: Num variables: %d Num constraints: %d, NNZ: %d", papilo_problem.getNCols(), papilo_problem.getNRows(), @@ -187,7 +227,8 @@ rmm::device_uvector third_party_presolve_t::undo( bool is_optimal = false; auto status = post_solver.undo(reduced_sol, full_sol, post_solve_storage_, is_optimal); - if (status != papilo::PostsolveStatus::kOk) { CUOPT_LOG_INFO("\n Post-solve failed"); } + check_postsolve_status(status); + // if (status != papilo::PostsolveStatus::kOk) { CUOPT_LOG_INFO("\n Post-solve failed"); } std::cout << "primal solution after post solve:" << std::endl; From c2115495e1d318641741702f9270fb0fed7db406 Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Thu, 24 Jul 2025 12:59:33 -0700 Subject: [PATCH 003/108] Add Papilo to cmake --- cpp/CMakeLists.txt | 17 ++++++++++++++++- cpp/tests/CMakeLists.txt | 2 ++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index a4326efafb..c2ae0f0acc 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -123,6 +123,11 @@ if(NOT DISABLE_OPENMP) endif() endif() +# find_package(TBB REQUIRED) +# if(TBB_FOUND) +# message(VERBOSE "cuOpt: TBB found in ${TBB_INCLUDE_DIRS}") +# endif() + # Debug options if(CMAKE_BUILD_TYPE MATCHES Debug) message(STATUS "Building with debugging flags") @@ -154,8 +159,15 @@ else() find_package(RAFT REQUIRED) endif() -include_directories("/raid/rgandham/opt/papilo/include") +# include_directories("/home/hugo/rapids/papilo/include") + +FetchContent_Declare( + papilo + GIT_REPOSITORY "git@github.com:scipopt/papilo.git" + GIT_TAG "main" +) +FetchContent_MakeAvailable(papilo) include(${rapids-cmake-dir}/cpm/rapids_logger.cmake) # generate logging macros @@ -328,6 +340,7 @@ target_link_libraries(cuopt_cli PUBLIC cuopt OpenMP::OpenMP_CXX + # TBB::tbb ) set_property(TARGET cuopt_cli PROPERTY INSTALL_RPATH "$ORIGIN/../${lib_dir}") @@ -347,5 +360,7 @@ if(BUILD_BENCHMARKS) PUBLIC cuopt OpenMP::OpenMP_CXX + # TBB::tbb + papilo-core ) endif() diff --git a/cpp/tests/CMakeLists.txt b/cpp/tests/CMakeLists.txt index 50323dc7e8..cd847a18a4 100644 --- a/cpp/tests/CMakeLists.txt +++ b/cpp/tests/CMakeLists.txt @@ -57,6 +57,8 @@ function(ConfigureTest CMAKE_TEST_NAME) GTest::gmock_main GTest::gtest GTest::gtest_main + # TBB::tbb + papilo-core ${CUOPT_PRIVATE_CUDA_LIBS} ) From 733afad2630a607d4691ff67ee42abbae53850dd Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Thu, 24 Jul 2025 15:07:45 -0700 Subject: [PATCH 004/108] Fix compile errors --- benchmarks/linear_programming/cuopt/run_mip.cpp | 7 +++---- cpp/CMakeLists.txt | 1 + cpp/cuopt_cli.cpp | 3 --- cpp/src/dual_simplex/sparse_matrix.hpp | 2 +- cpp/tests/CMakeLists.txt | 1 + cpp/tests/distance_engine/waypoint_matrix_test.cpp | 10 +++++----- cpp/tests/distance_engine/waypoint_matrix_test.hpp | 2 +- cpp/tests/dual_simplex/unit_tests/solve.cpp | 1 - 8 files changed, 12 insertions(+), 15 deletions(-) diff --git a/benchmarks/linear_programming/cuopt/run_mip.cpp b/benchmarks/linear_programming/cuopt/run_mip.cpp index b20ae26ea6..b458b07402 100644 --- a/benchmarks/linear_programming/cuopt/run_mip.cpp +++ b/benchmarks/linear_programming/cuopt/run_mip.cpp @@ -392,8 +392,7 @@ int main(int argc, char* argv[]) for (int i = 0; i < n_gpus; ++i) { gpu_queue.push(i); } - int tests_ran = 0; - int n_instances_solved = 0; + int tests_ran = 0; std::vector paths; if (run_selected) { for (const auto& instance : instances) { @@ -421,11 +420,11 @@ int main(int argc, char* argv[]) bool static_dispatch = false; if (static_dispatch) { - for (int i = 0; i < paths.size(); ++i) { + for (size_t i = 0; i < paths.size(); ++i) { // TODO implement } } else { - for (int i = 0; i < paths.size(); ++i) { + for (size_t i = 0; i < paths.size(); ++i) { task_queue.push(paths[i]); } while (!task_queue.empty()) { diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index c2ae0f0acc..34230d55e9 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -340,6 +340,7 @@ target_link_libraries(cuopt_cli PUBLIC cuopt OpenMP::OpenMP_CXX + papilo-core # TBB::tbb ) set_property(TARGET cuopt_cli PROPERTY INSTALL_RPATH "$ORIGIN/../${lib_dir}") diff --git a/cpp/cuopt_cli.cpp b/cpp/cuopt_cli.cpp index 7394dc7e7b..634c1cb777 100644 --- a/cpp/cuopt_cli.cpp +++ b/cpp/cuopt_cli.cpp @@ -122,9 +122,6 @@ int run_single_file(const std::string& file_path, (op_problem.get_problem_category() == cuopt::linear_programming::problem_category_t::MIP || op_problem.get_problem_category() == cuopt::linear_programming::problem_category_t::IP); - bool sol_found = false; - double obj_val = std::numeric_limits::infinity(); - auto initial_solution = initial_solution_file.empty() ? std::vector() diff --git a/cpp/src/dual_simplex/sparse_matrix.hpp b/cpp/src/dual_simplex/sparse_matrix.hpp index 29e6a0cf42..400aa891fb 100644 --- a/cpp/src/dual_simplex/sparse_matrix.hpp +++ b/cpp/src/dual_simplex/sparse_matrix.hpp @@ -77,9 +77,9 @@ class csc_matrix_t { // Compute || A ||_1 = max_j (sum {i = 1 to m} | A(i, j) | ) f_t norm1() const; - i_t nz_max; // maximum number of entries i_t m; // number of rows i_t n; // number of columns + i_t nz_max; // maximum number of entries std::vector col_start; // column pointers (size n + 1) std::vector i; // row indices, size nz_max std::vector x; // numerical values, size nz_max diff --git a/cpp/tests/CMakeLists.txt b/cpp/tests/CMakeLists.txt index cd847a18a4..a6fa76fbde 100644 --- a/cpp/tests/CMakeLists.txt +++ b/cpp/tests/CMakeLists.txt @@ -33,6 +33,7 @@ target_link_libraries(cuopttestutils cuopt GTest::gmock GTest::gtest + papilo-core ) set(CUOPT_TEST_DIR ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/cpp/tests/distance_engine/waypoint_matrix_test.cpp b/cpp/tests/distance_engine/waypoint_matrix_test.cpp index d574b7c0e0..402b608a4c 100644 --- a/cpp/tests/distance_engine/waypoint_matrix_test.cpp +++ b/cpp/tests/distance_engine/waypoint_matrix_test.cpp @@ -71,7 +71,7 @@ class waypoint_matrix_waypoints_sequence_test_t raft::copy(h_cost_matrix.data(), d_cost_matrix.data(), h_cost_matrix.size(), stream); RAFT_CUDA_TRY(cudaStreamSynchronize(stream)); - for (i_t i = 0; i != h_cost_matrix.size(); ++i) + for (size_t i = 0; i != h_cost_matrix.size(); ++i) EXPECT_EQ(h_cost_matrix[i], expected_cost_matrix[i]); auto [d_sequence_offsets, d_full_path] = @@ -90,9 +90,9 @@ class waypoint_matrix_waypoints_sequence_test_t raft::copy(h_full_path.data(), (i_t*)d_full_path.get()->data(), h_full_path.size(), stream); RAFT_CUDA_TRY(cudaStreamSynchronize(stream)); - for (i_t i = 0; i != h_sequence_offsets.size(); ++i) + for (size_t i = 0; i != h_sequence_offsets.size(); ++i) EXPECT_EQ(h_sequence_offsets[i], expected_sequence_offsets[i]); - for (i_t i = 0; i != h_full_path.size(); ++i) + for (size_t i = 0; i != h_full_path.size(); ++i) EXPECT_EQ(h_full_path[i], expected_full_path[i]); } @@ -166,7 +166,7 @@ class waypoint_matrix_shortest_path_cost_t raft::copy(h_custom_matrix.data(), d_custom_matrix.data(), h_custom_matrix.size(), stream); RAFT_CUDA_TRY(cudaStreamSynchronize(stream)); - for (i_t i = 0; i != h_custom_matrix.size(); ++i) + for (size_t i = 0; i != h_custom_matrix.size(); ++i) EXPECT_EQ(h_custom_matrix[i], ref_custom_matrix[i]); } @@ -219,7 +219,7 @@ class waypoint_matrix_cost_matrix_test_t raft::copy(h_cost_matrix.data(), d_cost_matrix.data(), h_cost_matrix.size(), stream); RAFT_CUDA_TRY(cudaStreamSynchronize(stream)); - for (i_t i = 0; i != h_cost_matrix.size(); ++i) + for (size_t i = 0; i != h_cost_matrix.size(); ++i) EXPECT_NEAR(h_cost_matrix[i], this->ref_cost_matrix[i], 0.001f); } diff --git a/cpp/tests/distance_engine/waypoint_matrix_test.hpp b/cpp/tests/distance_engine/waypoint_matrix_test.hpp index 239039ae2f..59db9554fd 100644 --- a/cpp/tests/distance_engine/waypoint_matrix_test.hpp +++ b/cpp/tests/distance_engine/waypoint_matrix_test.hpp @@ -38,7 +38,7 @@ std::vector parse_vector(std::vector const& vect) { std::vector out(vect.size()); - for (auto i = 0; i != vect.size(); ++i) { + for (size_t i = 0; i != vect.size(); ++i) { if constexpr (std::is_same_v) out[i] = std::stoi(vect[i]); else if constexpr (std::is_same_v) diff --git a/cpp/tests/dual_simplex/unit_tests/solve.cpp b/cpp/tests/dual_simplex/unit_tests/solve.cpp index 0743c08d78..923ad8c90a 100644 --- a/cpp/tests/dual_simplex/unit_tests/solve.cpp +++ b/cpp/tests/dual_simplex/unit_tests/solve.cpp @@ -87,7 +87,6 @@ TEST(dual_simplex, chess_set) user_problem.var_types[0] = dual_simplex::variable_type_t::CONTINUOUS; user_problem.var_types[1] = dual_simplex::variable_type_t::CONTINUOUS; - double start_time = dual_simplex::tic(); dual_simplex::simplex_solver_settings_t settings; dual_simplex::lp_solution_t solution(user_problem.num_rows, user_problem.num_cols); EXPECT_EQ((dual_simplex::solve_linear_program(user_problem, settings, solution)), From ca13c64cf37aa97a46532d859cbec3aa724c8e75 Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Thu, 24 Jul 2025 16:12:21 -0700 Subject: [PATCH 005/108] Fix link error --- cpp/CMakeLists.txt | 12 +++++------- cpp/src/linear_programming/cuopt_c.cpp | 2 +- cpp/tests/CMakeLists.txt | 7 ++++--- cpp/tests/examples/routing/CMakeLists.txt | 4 ++++ 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 34230d55e9..52bc3358ab 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -123,11 +123,6 @@ if(NOT DISABLE_OPENMP) endif() endif() -# find_package(TBB REQUIRED) -# if(TBB_FOUND) -# message(VERBOSE "cuOpt: TBB found in ${TBB_INCLUDE_DIRS}") -# endif() - # Debug options if(CMAKE_BUILD_TYPE MATCHES Debug) message(STATUS "Building with debugging flags") @@ -252,6 +247,7 @@ target_link_libraries(cuopt CCCL::CCCL raft::raft cuopt::mps_parser + clusol PRIVATE ${CUOPT_PRIVATE_CUDA_LIBS} ) @@ -341,7 +337,8 @@ target_link_libraries(cuopt_cli cuopt OpenMP::OpenMP_CXX papilo-core - # TBB::tbb + gfortran + lapack ) set_property(TARGET cuopt_cli PROPERTY INSTALL_RPATH "$ORIGIN/../${lib_dir}") @@ -361,7 +358,8 @@ if(BUILD_BENCHMARKS) PUBLIC cuopt OpenMP::OpenMP_CXX - # TBB::tbb papilo-core + gfortran + lapack ) endif() diff --git a/cpp/src/linear_programming/cuopt_c.cpp b/cpp/src/linear_programming/cuopt_c.cpp index ce8b70d392..fc77e2323e 100644 --- a/cpp/src/linear_programming/cuopt_c.cpp +++ b/cpp/src/linear_programming/cuopt_c.cpp @@ -437,7 +437,7 @@ cuopt_int_t cuOptGetVariableTypes(cuOptOptimizationProblem problem, char* variab variable_types.size(), problem_and_stream_view->stream_view); problem_and_stream_view->stream_view.synchronize(); - for (int j = 0; j < variable_types_host.size(); j++) { + for (size_t j = 0; j < variable_types_host.size(); j++) { variable_types_ptr[j] = variable_types_host[j] == var_t::INTEGER ? CUOPT_INTEGER : CUOPT_CONTINUOUS; } diff --git a/cpp/tests/CMakeLists.txt b/cpp/tests/CMakeLists.txt index a6fa76fbde..323d79c930 100644 --- a/cpp/tests/CMakeLists.txt +++ b/cpp/tests/CMakeLists.txt @@ -34,6 +34,9 @@ target_link_libraries(cuopttestutils GTest::gmock GTest::gtest papilo-core + clusol + gfortran + lapack ) set(CUOPT_TEST_DIR ${CMAKE_CURRENT_SOURCE_DIR}) @@ -50,7 +53,7 @@ function(ConfigureTest CMAKE_TEST_NAME) ) target_link_libraries(${CMAKE_TEST_NAME} - PRIVATE + PRIVATE mps_parser cuopt cuopttestutils @@ -58,8 +61,6 @@ function(ConfigureTest CMAKE_TEST_NAME) GTest::gmock_main GTest::gtest GTest::gtest_main - # TBB::tbb - papilo-core ${CUOPT_PRIVATE_CUDA_LIBS} ) diff --git a/cpp/tests/examples/routing/CMakeLists.txt b/cpp/tests/examples/routing/CMakeLists.txt index 52bc3e6828..7671d5c081 100644 --- a/cpp/tests/examples/routing/CMakeLists.txt +++ b/cpp/tests/examples/routing/CMakeLists.txt @@ -48,5 +48,9 @@ foreach(target cuopt cuopttestutils OpenMP::OpenMP_CXX + papilo-core + clusol + gfortran + lapack ) endforeach() From 3c5f96303d79740534981853708135a491f04bc7 Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Thu, 24 Jul 2025 17:54:12 -0700 Subject: [PATCH 006/108] Add presolve to LP --- cpp/src/linear_programming/solve.cu | 41 ++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/cpp/src/linear_programming/solve.cu b/cpp/src/linear_programming/solve.cu index df3d3d1e1b..fbfb82402f 100644 --- a/cpp/src/linear_programming/solve.cu +++ b/cpp/src/linear_programming/solve.cu @@ -25,6 +25,7 @@ #include #include +#include #include #include @@ -39,6 +40,7 @@ #include #include #include +#include #include #include @@ -562,6 +564,9 @@ optimization_problem_solution_t solve_lp(optimization_problem_t::max() : settings.time_limit; + // Create log stream for file logging and add it to default logger init_logger_t log(settings.log_file, settings.log_to_console); @@ -577,7 +582,20 @@ optimization_problem_solution_t solve_lp(optimization_problem_t::check_problem_representation(op_problem); problem_checking_t::check_initial_solution_representation(op_problem, settings); } - detail::problem_t problem(op_problem); + + auto timer = cuopt::timer_t(time_limit); + detail::problem_t full_problem(op_problem); + + // allocate no more than 10% of the time limit to presolve. + // Note that this is not the presolve time, but the time limit for presolve. + const double presolve_time_limit = 0.1 * time_limit; + detail::third_party_presolve_t presolver; + auto presolved_problem = presolver.apply(op_problem, presolve_time_limit); + const double presolve_time = timer.elapsed_time(); + + CUOPT_LOG_INFO("Third party presolve time: %f", presolve_time); + + detail::problem_t problem(presolved_problem); CUOPT_LOG_INFO( "Solving a problem with %d constraints %d variables (%d integers) and %d nonzeros", problem.n_constraints, @@ -590,7 +608,7 @@ optimization_problem_solution_t solve_lp(optimization_problem_t solve_lp(optimization_problem_tget_stream()); - auto sol = solve_lp_with_method(op_problem, problem, settings, is_batch_mode); + auto reduced_solution = solve_lp_with_method(op_problem, problem, settings, is_batch_mode); + auto full_sol_vec = presolver.undo(reduced_solution.get_primal_solution()); + + auto full_stats = reduced_solution.get_additional_termination_information(); + // add third party presolve time to cuopt presolve time + full_stats.solve_time += presolve_time; + + // Create a new solution with the full problem solution + optimization_problem_solution_t sol(full_sol_vec, + reduced_solution.get_dual_solution(), + reduced_solution.get_reduced_cost(), + full_problem.objective_name, + full_problem.var_names, + full_problem.row_names, + full_stats, + reduced_solution.get_termination_status(), + op_problem.get_handle_ptr(), + true); if (settings.sol_file != "") { CUOPT_LOG_INFO("Writing solution to file %s", settings.sol_file.c_str()); From 61bf43620a701bfcb9265b81ee4c9ffb778c0188 Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Thu, 24 Jul 2025 17:59:45 -0700 Subject: [PATCH 007/108] Add presolve setting --- cpp/cuopt_cli.cpp | 5 ++ .../cuopt/linear_programming/constants.h | 2 + .../mip/solver_settings.hpp | 1 + .../pdlp/solver_settings.hpp | 1 + cpp/src/linear_programming/solve.cu | 63 ++++++++++++------- cpp/src/math_optimization/solver_settings.cu | 4 +- cpp/src/mip/solve.cu | 33 ++++++---- .../solver/solver_parameters.pyx | 2 + .../linear_programming/data_definition.py | 5 ++ .../utils/linear_programming/solver.py | 5 ++ 10 files changed, 85 insertions(+), 36 deletions(-) diff --git a/cpp/cuopt_cli.cpp b/cpp/cuopt_cli.cpp index 634c1cb777..c717210a4d 100644 --- a/cpp/cuopt_cli.cpp +++ b/cpp/cuopt_cli.cpp @@ -194,6 +194,11 @@ int main(int argc, char* argv[]) .default_value(false) .implicit_value(true); + program.add_argument("--presolve") + .help("enable/disable presolve (default: true)") + .default_value(true) + .implicit_value(true); + std::map arg_name_to_param_name; { // Add all solver settings as arguments diff --git a/cpp/include/cuopt/linear_programming/constants.h b/cpp/include/cuopt/linear_programming/constants.h index ca4377de97..72d05a6adb 100644 --- a/cpp/include/cuopt/linear_programming/constants.h +++ b/cpp/include/cuopt/linear_programming/constants.h @@ -50,6 +50,7 @@ #define CUOPT_LOG_FILE "log_file" #define CUOPT_LOG_TO_CONSOLE "log_to_console" #define CUOPT_CROSSOVER "crossover" +#define CUOPT_PRESOLVE "presolve" #define CUOPT_MIP_ABSOLUTE_TOLERANCE "mip_absolute_tolerance" #define CUOPT_MIP_RELATIVE_TOLERANCE "mip_relative_tolerance" #define CUOPT_MIP_INTEGRALITY_TOLERANCE "mip_integrality_tolerance" @@ -57,6 +58,7 @@ #define CUOPT_MIP_RELATIVE_GAP "mip_relative_gap" #define CUOPT_MIP_HEURISTICS_ONLY "mip_heuristics_only" #define CUOPT_MIP_SCALING "mip_scaling" +#define CUOPT_MIP_PRESOLVE "mip_presolve" #define CUOPT_SOLUTION_FILE "solution_file" #define CUOPT_NUM_CPU_THREADS "num_cpu_threads" #define CUOPT_USER_PROBLEM_FILE "user_problem_file" diff --git a/cpp/include/cuopt/linear_programming/mip/solver_settings.hpp b/cpp/include/cuopt/linear_programming/mip/solver_settings.hpp index fbb75d80b1..03c14ab544 100644 --- a/cpp/include/cuopt/linear_programming/mip/solver_settings.hpp +++ b/cpp/include/cuopt/linear_programming/mip/solver_settings.hpp @@ -99,6 +99,7 @@ class mip_solver_settings_t { /** Initial primal solutions */ std::vector>> initial_solutions; bool mip_scaling = true; + bool presolve = true; // Enable presolve by default // this is for extracting info from different places of the solver during benchmarks benchmark_info_t* benchmark_info_ptr = nullptr; diff --git a/cpp/include/cuopt/linear_programming/pdlp/solver_settings.hpp b/cpp/include/cuopt/linear_programming/pdlp/solver_settings.hpp index 9dcccf7a7a..5627a478cb 100644 --- a/cpp/include/cuopt/linear_programming/pdlp/solver_settings.hpp +++ b/cpp/include/cuopt/linear_programming/pdlp/solver_settings.hpp @@ -205,6 +205,7 @@ class pdlp_solver_settings_t { bool crossover{false}; bool save_best_primal_so_far{false}; bool first_primal_feasible{false}; + bool presolve{true}; method_t method{method_t::Concurrent}; // For concurrent termination std::atomic* concurrent_halt; diff --git a/cpp/src/linear_programming/solve.cu b/cpp/src/linear_programming/solve.cu index fbfb82402f..41291c2816 100644 --- a/cpp/src/linear_programming/solve.cu +++ b/cpp/src/linear_programming/solve.cu @@ -584,18 +584,26 @@ optimization_problem_solution_t solve_lp(optimization_problem_t full_problem(op_problem); + detail::problem_t problem(op_problem); - // allocate no more than 10% of the time limit to presolve. - // Note that this is not the presolve time, but the time limit for presolve. - const double presolve_time_limit = 0.1 * time_limit; - detail::third_party_presolve_t presolver; - auto presolved_problem = presolver.apply(op_problem, presolve_time_limit); - const double presolve_time = timer.elapsed_time(); + if (settings.user_problem_file != "") { + CUOPT_LOG_INFO("Writing user problem to file: %s", settings.user_problem_file.c_str()); + problem.write_as_mps(settings.user_problem_file); + } - CUOPT_LOG_INFO("Third party presolve time: %f", presolve_time); + double presolve_time = 0.0; + std::unique_ptr> presolver; + + if (settings.presolve) { + // allocate no more than 10% of the time limit to presolve. + // Note that this is not the presolve time, but the time limit for presolve. + const double presolve_time_limit = 0.1 * time_limit; + presolver = std::make_unique>(); + problem = presolver->apply(op_problem, presolve_time_limit); + presolve_time = timer.elapsed_time(); + CUOPT_LOG_INFO("Third party presolve time: %f", presolve_time); + } - detail::problem_t problem(presolved_problem); CUOPT_LOG_INFO( "Solving a problem with %d constraints %d variables (%d integers) and %d nonzeros", problem.n_constraints, @@ -606,32 +614,39 @@ optimization_problem_solution_t solve_lp(optimization_problem_tget_stream()); - auto reduced_solution = solve_lp_with_method(op_problem, problem, settings, is_batch_mode); - auto full_sol_vec = presolver.undo(reduced_solution.get_primal_solution()); + auto solution = solve_lp_with_method(op_problem, problem, settings, is_batch_mode); + + auto const& primal_sol = solution.get_primal_solution(); + rmm::device_uvector primal_sol_tmp(primal_sol.size(), + op_problem.get_handle_ptr()->get_stream()); + raft::copy(primal_sol_tmp.data(), + primal_sol.data(), + primal_sol.size(), + op_problem.get_handle_ptr()->get_stream()); + + if (settings.presolve) { + auto full_sol_vec = presolver->undo(primal_sol_tmp); + primal_sol_tmp = std::move(full_sol_vec); + } - auto full_stats = reduced_solution.get_additional_termination_information(); + auto full_stats = solution.get_additional_termination_information(); // add third party presolve time to cuopt presolve time full_stats.solve_time += presolve_time; // Create a new solution with the full problem solution - optimization_problem_solution_t sol(full_sol_vec, - reduced_solution.get_dual_solution(), - reduced_solution.get_reduced_cost(), - full_problem.objective_name, - full_problem.var_names, - full_problem.row_names, + optimization_problem_solution_t sol(primal_sol_tmp, + solution.get_dual_solution(), + solution.get_reduced_cost(), + op_problem.get_objective_name(), + op_problem.get_variable_names(), + op_problem.get_row_names(), full_stats, - reduced_solution.get_termination_status(), + solution.get_termination_status(), op_problem.get_handle_ptr(), true); diff --git a/cpp/src/math_optimization/solver_settings.cu b/cpp/src/math_optimization/solver_settings.cu index 0e148610cd..9de975bf9b 100644 --- a/cpp/src/math_optimization/solver_settings.cu +++ b/cpp/src/math_optimization/solver_settings.cu @@ -104,7 +104,9 @@ solver_settings_t::solver_settings_t() : pdlp_settings(), mip_settings {CUOPT_MIP_HEURISTICS_ONLY, &mip_settings.heuristics_only, false}, {CUOPT_LOG_TO_CONSOLE, &pdlp_settings.log_to_console, true}, {CUOPT_LOG_TO_CONSOLE, &mip_settings.log_to_console, true}, - {CUOPT_CROSSOVER, &pdlp_settings.crossover, false} + {CUOPT_CROSSOVER, &pdlp_settings.crossover, false}, + {CUOPT_PRESOLVE, &pdlp_settings.presolve, true}, + {CUOPT_MIP_PRESOLVE, &mip_settings.presolve, true} }; // String parameters string_parameters = { diff --git a/cpp/src/mip/solve.cu b/cpp/src/mip/solve.cu index 7c1daadadd..828fda6835 100644 --- a/cpp/src/mip/solve.cu +++ b/cpp/src/mip/solve.cu @@ -175,17 +175,20 @@ mip_solution_t solve_mip(optimization_problem_t& op_problem, problem_checking_t::check_initial_solution_representation(op_problem, settings); auto timer = cuopt::timer_t(time_limit); - // allocate note more than 10% of the time limit to presolve. - // Note that this is not the presolve time, but the time limit for presolve. - const double presolve_time_limit = 0.1 * time_limit; - detail::third_party_presolve_t presolver; - auto presolved_problem = presolver.apply(op_problem, presolve_time_limit); - const double presolve_time = timer.elapsed_time(); - CUOPT_LOG_INFO("Third party presolve time: %f", presolve_time); + double presolve_time = 0.0; + std::unique_ptr> presolver; + detail::problem_t problem(op_problem, settings.get_tolerances()); - // have solve, problem, solution, utils etc. in common dir - detail::problem_t problem(presolved_problem, settings.get_tolerances()); + if (settings.presolve) { + // allocate not more than 10% of the time limit to presolve. + // Note that this is not the presolve time, but the time limit for presolve. + const double presolve_time_limit = 0.1 * time_limit; + presolver = std::make_unique>(); + problem = presolver->apply(op_problem, presolve_time_limit); + presolve_time = timer.elapsed_time(); + CUOPT_LOG_INFO("Third party presolve time: %f", presolve_time); + } if (settings.user_problem_file != "") { CUOPT_LOG_INFO("Writing user problem to file: %s", settings.user_problem_file.c_str()); problem.write_as_mps(settings.user_problem_file); @@ -195,11 +198,19 @@ mip_solution_t solve_mip(optimization_problem_t& op_problem, setup_device_symbols(op_problem.get_handle_ptr()->get_stream()); auto reduced_solution = run_mip(problem, settings, timer); - auto full_sol_vec = presolver.undo(reduced_solution.get_solution()); + + auto const& full_sol_vec = reduced_solution.get_solution(); + rmm::device_uvector full_sol_vec_tmp(full_sol_vec.size(), + op_problem.get_handle_ptr()->get_stream()); + raft::copy(full_sol_vec_tmp.data(), + full_sol_vec.data(), + full_sol_vec.size(), + op_problem.get_handle_ptr()->get_stream()); + if (settings.presolve) { full_sol_vec_tmp = presolver->undo(full_sol_vec_tmp); } detail::problem_t full_problem(op_problem); detail::solution_t full_sol(full_problem); - full_sol.copy_new_assignment(cuopt::host_copy(full_sol_vec)); + full_sol.copy_new_assignment(cuopt::host_copy(full_sol_vec_tmp)); full_sol.compute_feasibility(); if (!full_sol.get_feasible()) { CUOPT_LOG_WARN("The solution is not feasible after post solve"); diff --git a/python/cuopt/cuopt/linear_programming/solver/solver_parameters.pyx b/python/cuopt/cuopt/linear_programming/solver/solver_parameters.pyx index 5783d8f4eb..413ec6e755 100644 --- a/python/cuopt/cuopt/linear_programming/solver/solver_parameters.pyx +++ b/python/cuopt/cuopt/linear_programming/solver/solver_parameters.pyx @@ -59,6 +59,7 @@ cdef extern from "cuopt/linear_programming/constants.h": # noqa cdef const char* c_CUOPT_LOG_FILE "CUOPT_LOG_FILE" # noqa cdef const char* c_CUOPT_LOG_TO_CONSOLE "CUOPT_LOG_TO_CONSOLE" # noqa cdef const char* c_CUOPT_CROSSOVER "CUOPT_CROSSOVER" # noqa + cdef const char* c_CUOPT_PRESOLVE "CUOPT_PRESOLVE" # noqa cdef const char* c_CUOPT_MIP_ABSOLUTE_TOLERANCE "CUOPT_MIP_ABSOLUTE_TOLERANCE" # noqa cdef const char* c_CUOPT_MIP_RELATIVE_TOLERANCE "CUOPT_MIP_RELATIVE_TOLERANCE" # noqa cdef const char* c_CUOPT_MIP_INTEGRALITY_TOLERANCE "CUOPT_MIP_INTEGRALITY_TOLERANCE" # noqa @@ -92,6 +93,7 @@ CUOPT_FIRST_PRIMAL_FEASIBLE = c_CUOPT_FIRST_PRIMAL_FEASIBLE.decode('utf-8') # no CUOPT_LOG_FILE = c_CUOPT_LOG_FILE.decode('utf-8') # noqa CUOPT_LOG_TO_CONSOLE = c_CUOPT_LOG_TO_CONSOLE.decode('utf-8') # noqa CUOPT_CROSSOVER = c_CUOPT_CROSSOVER.decode('utf-8') # noqa +CUOPT_PRESOLVE = c_CUOPT_PRESOLVE.decode('utf-8') # noqa CUOPT_MIP_ABSOLUTE_TOLERANCE = c_CUOPT_MIP_ABSOLUTE_TOLERANCE.decode('utf-8') # noqa CUOPT_MIP_RELATIVE_TOLERANCE = c_CUOPT_MIP_RELATIVE_TOLERANCE.decode('utf-8') # noqa CUOPT_MIP_INTEGRALITY_TOLERANCE = c_CUOPT_MIP_INTEGRALITY_TOLERANCE.decode('utf-8') # noqa diff --git a/python/cuopt_server/cuopt_server/utils/linear_programming/data_definition.py b/python/cuopt_server/cuopt_server/utils/linear_programming/data_definition.py index d66b7c8171..394c00c878 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 @@ -463,6 +463,11 @@ class SolverConfig(StrictModel): default=False, description="Set True to use crossover, False to not use crossover.", ) + presolve: Optional[bool] = Field( + default=True, + description="Set True to enable presolve, False to disable presolve. " + "Presolve can reduce problem size and improve solve time.", + ) log_to_console: Optional[bool] = Field( default=True, description="Set True to write logs to console, False to " diff --git a/python/cuopt_server/cuopt_server/utils/linear_programming/solver.py b/python/cuopt_server/cuopt_server/utils/linear_programming/solver.py index fcb9d07649..197a8bc297 100644 --- a/python/cuopt_server/cuopt_server/utils/linear_programming/solver.py +++ b/python/cuopt_server/cuopt_server/utils/linear_programming/solver.py @@ -38,6 +38,7 @@ CUOPT_MIP_SCALING, CUOPT_NUM_CPU_THREADS, CUOPT_PDLP_SOLVER_MODE, + CUOPT_PRESOLVE, CUOPT_PRIMAL_INFEASIBLE_TOLERANCE, CUOPT_RELATIVE_DUAL_TOLERANCE, CUOPT_RELATIVE_GAP_TOLERANCE, @@ -276,6 +277,10 @@ def create_solver(LP_data, warmstart_data): solver_settings.set_parameter( CUOPT_CROSSOVER, solver_config.crossover ) + if solver_config.presolve is not None: + solver_settings.set_parameter( + CUOPT_PRESOLVE, solver_config.presolve + ) if solver_config.log_to_console is not None: solver_settings.set_parameter( CUOPT_LOG_TO_CONSOLE, solver_config.log_to_console From e1eb26421fd9397abbaeea7cbe9a0b13564e08ac Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Thu, 24 Jul 2025 18:53:31 -0700 Subject: [PATCH 008/108] Remove macro --- cpp/src/linear_programming/solve.cu | 1 + cpp/src/mip/presolve/third_party_presolve.cu | 16 ---------------- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/cpp/src/linear_programming/solve.cu b/cpp/src/linear_programming/solve.cu index 41291c2816..123d2ad909 100644 --- a/cpp/src/linear_programming/solve.cu +++ b/cpp/src/linear_programming/solve.cu @@ -630,6 +630,7 @@ optimization_problem_solution_t solve_lp(optimization_problem_tget_stream()); if (settings.presolve) { + // Dual postsolve is not supported yet in Papilo. auto full_sol_vec = presolver->undo(primal_sol_tmp); primal_sol_tmp = std::move(full_sol_vec); } diff --git a/cpp/src/mip/presolve/third_party_presolve.cu b/cpp/src/mip/presolve/third_party_presolve.cu index 0f9ffd3178..44ea6deb5c 100644 --- a/cpp/src/mip/presolve/third_party_presolve.cu +++ b/cpp/src/mip/presolve/third_party_presolve.cu @@ -177,13 +177,10 @@ void check_postsolve_status(const papilo::PostsolveStatus& status) } } -#define USE_PAPILOS_PRESOLVER 1 - template optimization_problem_t third_party_presolve_t::apply( optimization_problem_t& op_problem, double time_limit) { -#if USE_PAPILOS_PRESOLVER papilo::Problem papilo_problem = build_papilo_problem(op_problem); CUOPT_LOG_INFO("Unpresolved problem:: Num variables: %d Num constraints: %d, NNZ: %d", @@ -206,16 +203,12 @@ optimization_problem_t third_party_presolve_t::apply( papilo_problem.getConstraintMatrix().getNnz()); return build_optimization_problem(papilo_problem, op_problem.get_handle_ptr()); -#else - return op_problem; -#endif } template rmm::device_uvector third_party_presolve_t::undo( rmm::device_uvector& reduced_sol_vec) { -#if USE_PAPILOS_PRESOLVER auto reduced_sol_vec_h = cuopt::host_copy(reduced_sol_vec); papilo::Solution reduced_sol(reduced_sol_vec_h); @@ -228,19 +221,10 @@ rmm::device_uvector third_party_presolve_t::undo( bool is_optimal = false; auto status = post_solver.undo(reduced_sol, full_sol, post_solve_storage_, is_optimal); check_postsolve_status(status); - // if (status != papilo::PostsolveStatus::kOk) { CUOPT_LOG_INFO("\n Post-solve failed"); } - - std::cout << "primal solution after post solve:" << std::endl; // FIXME: recompute objective value, mip gap, max constraint violation, etc. auto full_sol_vec = cuopt::device_copy(full_sol.primal, reduced_sol_vec.stream()); return full_sol_vec; -#else - rmm::device_uvector full_sol_vec(reduced_sol_vec.size(), reduced_sol_vec.stream()); - raft::copy( - full_sol_vec.data(), reduced_sol_vec.data(), reduced_sol_vec.size(), reduced_sol_vec.stream()); - return full_sol_vec; -#endif } #if MIP_INSTANTIATE_FLOAT From 194318badca940ce36f4cd1ade74c7b3fded120a Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Thu, 24 Jul 2025 19:07:16 -0700 Subject: [PATCH 009/108] Fix option --- cpp/CMakeLists.txt | 2 -- cpp/cuopt_cli.cpp | 2 ++ cpp/src/math_optimization/solver_settings.cu | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 52bc3358ab..579ad35783 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -154,8 +154,6 @@ else() find_package(RAFT REQUIRED) endif() -# include_directories("/home/hugo/rapids/papilo/include") - FetchContent_Declare( papilo GIT_REPOSITORY "git@github.com:scipopt/papilo.git" diff --git a/cpp/cuopt_cli.cpp b/cpp/cuopt_cli.cpp index c717210a4d..a247f16db5 100644 --- a/cpp/cuopt_cli.cpp +++ b/cpp/cuopt_cli.cpp @@ -131,12 +131,14 @@ int run_single_file(const std::string& file_path, try { if (is_mip && !solve_relaxation) { auto& mip_settings = settings.get_mip_settings(); + std::cout << "MIP presolve: " << mip_settings.presolve << std::endl; if (initial_solution.size() > 0) { mip_settings.add_initial_solution(initial_solution.data(), initial_solution.size()); } auto solution = cuopt::linear_programming::solve_mip(op_problem, mip_settings); } else { auto& lp_settings = settings.get_pdlp_settings(); + std::cout << "LP presolve: " << lp_settings.presolve << std::endl; if (initial_solution.size() > 0) { lp_settings.set_initial_primal_solution(initial_solution.data(), initial_solution.size()); } diff --git a/cpp/src/math_optimization/solver_settings.cu b/cpp/src/math_optimization/solver_settings.cu index 9de975bf9b..91d173a3e6 100644 --- a/cpp/src/math_optimization/solver_settings.cu +++ b/cpp/src/math_optimization/solver_settings.cu @@ -106,7 +106,7 @@ solver_settings_t::solver_settings_t() : pdlp_settings(), mip_settings {CUOPT_LOG_TO_CONSOLE, &mip_settings.log_to_console, true}, {CUOPT_CROSSOVER, &pdlp_settings.crossover, false}, {CUOPT_PRESOLVE, &pdlp_settings.presolve, true}, - {CUOPT_MIP_PRESOLVE, &mip_settings.presolve, true} + {CUOPT_PRESOLVE, &mip_settings.presolve, true} }; // String parameters string_parameters = { From a58f65938012d9d30bd651b8dade4379ffa2cc4d Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Thu, 24 Jul 2025 19:14:02 -0700 Subject: [PATCH 010/108] Add papilo to conda env --- conda/environments/all_cuda-128_arch-aarch64.yaml | 1 + conda/environments/all_cuda-128_arch-x86_64.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/conda/environments/all_cuda-128_arch-aarch64.yaml b/conda/environments/all_cuda-128_arch-aarch64.yaml index 55d283e0b1..68fcf411b3 100644 --- a/conda/environments/all_cuda-128_arch-aarch64.yaml +++ b/conda/environments/all_cuda-128_arch-aarch64.yaml @@ -53,6 +53,7 @@ dependencies: - numpy>=1.23.5,<3.0a0 - numpydoc - pandas>=2.0 +- papilo - pexpect - pip - polyline diff --git a/conda/environments/all_cuda-128_arch-x86_64.yaml b/conda/environments/all_cuda-128_arch-x86_64.yaml index 30b6c1718e..b580004a19 100644 --- a/conda/environments/all_cuda-128_arch-x86_64.yaml +++ b/conda/environments/all_cuda-128_arch-x86_64.yaml @@ -53,6 +53,7 @@ dependencies: - numpy>=1.23.5,<3.0a0 - numpydoc - pandas>=2.0 +- papilo - pexpect - pip - polyline From 1de199bfe280d51ce9d04a119adf161689dd947c Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Mon, 28 Jul 2025 17:16:00 -0700 Subject: [PATCH 011/108] Remove papilo from conda --- cpp/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 579ad35783..ed5da7f7bb 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -154,6 +154,8 @@ else() find_package(RAFT REQUIRED) endif() +find_package(TBB REQUIRED) + FetchContent_Declare( papilo GIT_REPOSITORY "git@github.com:scipopt/papilo.git" @@ -218,6 +220,8 @@ target_include_directories(cuopt "$" "$" "$" + "$" + "$" ) # ################################################################################################## From b62dda749eeb0a7967cc570504cf3f02a26dd694 Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Mon, 28 Jul 2025 21:12:50 -0700 Subject: [PATCH 012/108] Fix build --- conda/environments/all_cuda-128_arch-aarch64.yaml | 3 ++- conda/environments/all_cuda-128_arch-x86_64.yaml | 3 ++- cpp/CMakeLists.txt | 5 ----- cpp/tests/CMakeLists.txt | 3 --- cpp/tests/examples/routing/CMakeLists.txt | 3 --- 5 files changed, 4 insertions(+), 13 deletions(-) diff --git a/conda/environments/all_cuda-128_arch-aarch64.yaml b/conda/environments/all_cuda-128_arch-aarch64.yaml index 68fcf411b3..3b81c1ad03 100644 --- a/conda/environments/all_cuda-128_arch-aarch64.yaml +++ b/conda/environments/all_cuda-128_arch-aarch64.yaml @@ -30,6 +30,7 @@ dependencies: - gcc_linux-aarch64=13.* - geopandas - gmock +- gmp - gtest - httpx - ipython @@ -53,7 +54,6 @@ dependencies: - numpy>=1.23.5,<3.0a0 - numpydoc - pandas>=2.0 -- papilo - pexpect - pip - polyline @@ -80,6 +80,7 @@ dependencies: - sphinxcontrib-openapi - sphinxcontrib-websupport - sysroot_linux-aarch64==2.28 +- tbb-devel - uvicorn==0.34.* - pip: - nvidia_sphinx_theme diff --git a/conda/environments/all_cuda-128_arch-x86_64.yaml b/conda/environments/all_cuda-128_arch-x86_64.yaml index b580004a19..e3cfa1d30d 100644 --- a/conda/environments/all_cuda-128_arch-x86_64.yaml +++ b/conda/environments/all_cuda-128_arch-x86_64.yaml @@ -30,6 +30,7 @@ dependencies: - gcc_linux-64=13.* - geopandas - gmock +- gmp - gtest - httpx - ipython @@ -53,7 +54,6 @@ dependencies: - numpy>=1.23.5,<3.0a0 - numpydoc - pandas>=2.0 -- papilo - pexpect - pip - polyline @@ -80,6 +80,7 @@ dependencies: - sphinxcontrib-openapi - sphinxcontrib-websupport - sysroot_linux-64==2.28 +- tbb-devel - uvicorn==0.34.* - pip: - nvidia_sphinx_theme diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index ed5da7f7bb..99ccb10806 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -249,7 +249,6 @@ target_link_libraries(cuopt CCCL::CCCL raft::raft cuopt::mps_parser - clusol PRIVATE ${CUOPT_PRIVATE_CUDA_LIBS} ) @@ -339,8 +338,6 @@ target_link_libraries(cuopt_cli cuopt OpenMP::OpenMP_CXX papilo-core - gfortran - lapack ) set_property(TARGET cuopt_cli PROPERTY INSTALL_RPATH "$ORIGIN/../${lib_dir}") @@ -361,7 +358,5 @@ if(BUILD_BENCHMARKS) cuopt OpenMP::OpenMP_CXX papilo-core - gfortran - lapack ) endif() diff --git a/cpp/tests/CMakeLists.txt b/cpp/tests/CMakeLists.txt index 323d79c930..6e413319b5 100644 --- a/cpp/tests/CMakeLists.txt +++ b/cpp/tests/CMakeLists.txt @@ -34,9 +34,6 @@ target_link_libraries(cuopttestutils GTest::gmock GTest::gtest papilo-core - clusol - gfortran - lapack ) set(CUOPT_TEST_DIR ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/cpp/tests/examples/routing/CMakeLists.txt b/cpp/tests/examples/routing/CMakeLists.txt index 7671d5c081..3b29d259d6 100644 --- a/cpp/tests/examples/routing/CMakeLists.txt +++ b/cpp/tests/examples/routing/CMakeLists.txt @@ -49,8 +49,5 @@ foreach(target cuopttestutils OpenMP::OpenMP_CXX papilo-core - clusol - gfortran - lapack ) endforeach() From daf09e84e463eb9d056e328bb8cdf7d96e86e70d Mon Sep 17 00:00:00 2001 From: Hugo Linsenmaier Date: Mon, 28 Jul 2025 21:47:03 -0700 Subject: [PATCH 013/108] Handle infeasible presolve. Handle dual post solve. --- .../optimization_problem.hpp | 7 +- .../optimization_problem.cu | 6 ++ cpp/src/linear_programming/solve.cu | 37 +++++---- cpp/src/mip/presolve/third_party_presolve.cu | 83 ++++++++++++++++--- cpp/src/mip/presolve/third_party_presolve.cuh | 10 ++- cpp/src/mip/solve.cu | 29 ++++--- cpp/src/utilities/copy_helpers.hpp | 17 ++++ 7 files changed, 150 insertions(+), 39 deletions(-) diff --git a/cpp/include/cuopt/linear_programming/optimization_problem.hpp b/cpp/include/cuopt/linear_programming/optimization_problem.hpp index 77686a337e..db61723676 100644 --- a/cpp/include/cuopt/linear_programming/optimization_problem.hpp +++ b/cpp/include/cuopt/linear_programming/optimization_problem.hpp @@ -42,7 +42,8 @@ enum class problem_category_t : int8_t { LP = 0, MIP = 1, IP = 2 }; * * @tparam f_t Data type of the variables and their weights in the equations * - * This structure stores all the information necessary to represent the following LP: + * This structure stores all the information necessary to represent the + * following LP: * *
  * Minimize:
@@ -61,7 +62,8 @@ enum class problem_category_t : int8_t { LP = 0, MIP = 1, IP = 2 };
  *
  * Objective value can be scaled and offset accordingly:
  * objective_scaling_factor * (dot(c, x) + objective_offset)
- * please refer to the `set_objective_scaling_factor()` and `set_objective_offset()` methods.
+ * please refer to the `set_objective_scaling_factor()` and
+ * `set_objective_offset()` methods.
  */
 template 
 class optimization_problem_t {
@@ -329,6 +331,7 @@ class optimization_problem_t {
   const rmm::device_uvector& get_row_types() const;
   const rmm::device_uvector& get_variable_types() const;
   bool get_sense() const;
+  bool empty() const;
 
   std::string get_objective_name() const;
   std::string get_problem_name() const;
diff --git a/cpp/src/linear_programming/optimization_problem.cu b/cpp/src/linear_programming/optimization_problem.cu
index ad33eb9b4e..7bfc137018 100644
--- a/cpp/src/linear_programming/optimization_problem.cu
+++ b/cpp/src/linear_programming/optimization_problem.cu
@@ -450,6 +450,12 @@ bool optimization_problem_t::get_sense() const
   return maximize_;
 }
 
+template 
+bool optimization_problem_t::empty() const
+{
+  return n_vars_ == 0 || n_constraints_ == 0;
+}
+
 template 
 typename optimization_problem_t::view_t optimization_problem_t::view() const
 {
diff --git a/cpp/src/linear_programming/solve.cu b/cpp/src/linear_programming/solve.cu
index 123d2ad909..613aebd71d 100644
--- a/cpp/src/linear_programming/solve.cu
+++ b/cpp/src/linear_programming/solve.cu
@@ -598,8 +598,14 @@ optimization_problem_solution_t solve_lp(optimization_problem_t>();
-      problem       = presolver->apply(op_problem, presolve_time_limit);
+      presolver            = std::make_unique>();
+      auto reduced_problem = presolver->apply(
+        op_problem, cuopt::linear_programming::problem_category_t::LP, presolve_time_limit);
+      if (reduced_problem.empty()) {
+        return optimization_problem_solution_t(
+          pdlp_termination_status_t::PrimalInfeasible, op_problem.get_handle_ptr()->get_stream());
+      }
+      problem       = detail::problem_t(reduced_problem);
       presolve_time = timer.elapsed_time();
       CUOPT_LOG_INFO("Third party presolve time: %f", presolve_time);
     }
@@ -621,18 +627,19 @@ optimization_problem_solution_t solve_lp(optimization_problem_t primal_sol_tmp(primal_sol.size(),
-                                            op_problem.get_handle_ptr()->get_stream());
-    raft::copy(primal_sol_tmp.data(),
-               primal_sol.data(),
-               primal_sol.size(),
-               op_problem.get_handle_ptr()->get_stream());
-
+    auto primal_solution =
+      cuopt::device_copy(solution.get_primal_solution(), op_problem.get_handle_ptr()->get_stream());
+    auto dual_solution =
+      cuopt::device_copy(solution.get_dual_solution(), op_problem.get_handle_ptr()->get_stream());
+    auto reduced_costs =
+      cuopt::device_copy(solution.get_reduced_cost(), op_problem.get_handle_ptr()->get_stream());
     if (settings.presolve) {
       // Dual postsolve is not supported yet in Papilo.
-      auto full_sol_vec = presolver->undo(primal_sol_tmp);
-      primal_sol_tmp    = std::move(full_sol_vec);
+      presolver->undo(primal_solution,
+                      dual_solution,
+                      reduced_costs,
+                      cuopt::linear_programming::problem_category_t::LP,
+                      op_problem.get_handle_ptr()->get_stream());
     }
 
     auto full_stats = solution.get_additional_termination_information();
@@ -640,9 +647,9 @@ optimization_problem_solution_t solve_lp(optimization_problem_t sol(primal_sol_tmp,
-                                                  solution.get_dual_solution(),
-                                                  solution.get_reduced_cost(),
+    optimization_problem_solution_t sol(primal_solution,
+                                                  dual_solution,
+                                                  reduced_costs,
                                                   op_problem.get_objective_name(),
                                                   op_problem.get_variable_names(),
                                                   op_problem.get_row_names(),
diff --git a/cpp/src/mip/presolve/third_party_presolve.cu b/cpp/src/mip/presolve/third_party_presolve.cu
index 44ea6deb5c..8e6ea004eb 100644
--- a/cpp/src/mip/presolve/third_party_presolve.cu
+++ b/cpp/src/mip/presolve/third_party_presolve.cu
@@ -177,9 +177,55 @@ void check_postsolve_status(const papilo::PostsolveStatus& status)
   }
 }
 
+template 
+void set_presolve_methods(papilo::Presolve& presolver, problem_category_t category)
+{
+  using uptr = std::unique_ptr>;
+
+  // fast presolvers
+  presolver.addPresolveMethod(uptr(new papilo::SingletonCols()));
+  presolver.addPresolveMethod(uptr(new papilo::CoefficientStrengthening()));
+  presolver.addPresolveMethod(uptr(new papilo::ConstraintPropagation()));
+
+  // medium presolvers
+  presolver.addPresolveMethod(uptr(new papilo::FixContinuous()));
+  presolver.addPresolveMethod(uptr(new papilo::SimpleProbing()));
+  presolver.addPresolveMethod(uptr(new papilo::ParallelRowDetection()));
+  presolver.addPresolveMethod(uptr(new papilo::ParallelColDetection()));
+  presolver.addPresolveMethod(uptr(new papilo::SingletonStuffing()));
+  presolver.addPresolveMethod(uptr(new papilo::DualFix()));
+  presolver.addPresolveMethod(uptr(new papilo::SimplifyInequalities()));
+
+  // exhaustive presolvers
+  presolver.addPresolveMethod(uptr(new papilo::ImplIntDetection()));
+  presolver.addPresolveMethod(uptr(new papilo::DominatedCols()));
+  presolver.addPresolveMethod(uptr(new papilo::Probing()));
+
+  if (category == problem_category_t::MIP) {
+    presolver.addPresolveMethod(uptr(new papilo::DualInfer));
+    presolver.addPresolveMethod(uptr(new papilo::SimpleSubstitution()));
+    presolver.addPresolveMethod(uptr(new papilo::Sparsify()));
+    presolver.addPresolveMethod(uptr(new papilo::Substitution()));
+  }
+}
+
+template 
+void set_presolve_options(papilo::Presolve& presolver,
+                          problem_category_t category,
+                          double time_limit)
+{
+  presolver.getPresolveOptions().tlim = time_limit;
+  if (category == problem_category_t::LP) {
+    presolver.getPresolveOptions().componentsmaxint = -1;
+    presolver.getPresolveOptions().detectlindep     = 0;
+  }
+}
+
 template 
 optimization_problem_t third_party_presolve_t::apply(
-  optimization_problem_t& op_problem, double time_limit)
+  optimization_problem_t const& op_problem,
+  problem_category_t category,
+  double time_limit)
 {
   papilo::Problem papilo_problem = build_papilo_problem(op_problem);
 
@@ -189,10 +235,13 @@ optimization_problem_t third_party_presolve_t::apply(
                  papilo_problem.getConstraintMatrix().getNnz());
 
   papilo::Presolve presolver;
-  presolver.addDefaultPresolvers();
-  presolver.getPresolveOptions().tlim = time_limit;
+  set_presolve_methods(presolver, category);
+  set_presolve_options(presolver, category, time_limit);
 
   auto result = presolver.apply(papilo_problem);
+  if (result.status == papilo::PresolveStatus::kInfeasible) {
+    return optimization_problem_t(op_problem.get_handle_ptr());
+  }
 
   post_solve_storage_ = result.postsolve;
 
@@ -206,13 +255,23 @@ optimization_problem_t third_party_presolve_t::apply(
 }
 
 template 
-rmm::device_uvector third_party_presolve_t::undo(
-  rmm::device_uvector& reduced_sol_vec)
+void third_party_presolve_t::undo(rmm::device_uvector& primal_solution,
+                                            rmm::device_uvector& dual_solution,
+                                            rmm::device_uvector& reduced_costs,
+                                            problem_category_t category,
+                                            rmm::cuda_stream_view stream_view)
 {
-  auto reduced_sol_vec_h = cuopt::host_copy(reduced_sol_vec);
+  auto primal_sol_vec_h    = cuopt::host_copy(primal_solution, stream_view);
+  auto dual_sol_vec_h      = cuopt::host_copy(dual_solution, stream_view);
+  auto reduced_costs_vec_h = cuopt::host_copy(reduced_costs, stream_view);
 
-  papilo::Solution reduced_sol(reduced_sol_vec_h);
+  papilo::Solution reduced_sol(primal_sol_vec_h);
   papilo::Solution full_sol;
+  if (category == problem_category_t::LP) {
+    full_sol.type         = papilo::SolutionType::kPrimalDual;
+    full_sol.dual         = dual_sol_vec_h;
+    full_sol.reducedCosts = reduced_costs_vec_h;
+  }
 
   papilo::Message Msg{};
   Msg.setVerbosityLevel(papilo::VerbosityLevel::kQuiet);
@@ -222,9 +281,13 @@ rmm::device_uvector third_party_presolve_t::undo(
   auto status     = post_solver.undo(reduced_sol, full_sol, post_solve_storage_, is_optimal);
   check_postsolve_status(status);
 
-  // FIXME: recompute objective value, mip gap, max constraint violation, etc.
-  auto full_sol_vec = cuopt::device_copy(full_sol.primal, reduced_sol_vec.stream());
-  return full_sol_vec;
+  primal_solution.resize(full_sol.primal.size(), stream_view);
+  dual_solution.resize(full_sol.dual.size(), stream_view);
+  reduced_costs.resize(full_sol.reducedCosts.size(), stream_view);
+  raft::copy(primal_solution.data(), full_sol.primal.data(), full_sol.primal.size(), stream_view);
+  raft::copy(dual_solution.data(), full_sol.dual.data(), full_sol.dual.size(), stream_view);
+  raft::copy(
+    reduced_costs.data(), full_sol.reducedCosts.data(), full_sol.reducedCosts.size(), stream_view);
 }
 
 #if MIP_INSTANTIATE_FLOAT
diff --git a/cpp/src/mip/presolve/third_party_presolve.cuh b/cpp/src/mip/presolve/third_party_presolve.cuh
index 0f59d49268..c33865a476 100644
--- a/cpp/src/mip/presolve/third_party_presolve.cuh
+++ b/cpp/src/mip/presolve/third_party_presolve.cuh
@@ -22,6 +22,7 @@
 #include 
 
 #include 
+
 namespace cuopt::linear_programming::detail {
 
 template 
@@ -29,10 +30,15 @@ class third_party_presolve_t {
  public:
   third_party_presolve_t() = default;
 
-  optimization_problem_t apply(optimization_problem_t& op_problem,
+  optimization_problem_t apply(optimization_problem_t const& op_problem,
+                                         problem_category_t category,
                                          double time_limit);
 
-  rmm::device_uvector undo(rmm::device_uvector& reduced_sol_vec);
+  void undo(rmm::device_uvector& primal_solution,
+            rmm::device_uvector& dual_solution,
+            rmm::device_uvector& reduced_costs,
+            problem_category_t category,
+            rmm::cuda_stream_view stream_view);
 
  private:
   papilo::PostsolveStorage post_solve_storage_;
diff --git a/cpp/src/mip/solve.cu b/cpp/src/mip/solve.cu
index 828fda6835..6018fbd5fd 100644
--- a/cpp/src/mip/solve.cu
+++ b/cpp/src/mip/solve.cu
@@ -184,8 +184,15 @@ mip_solution_t solve_mip(optimization_problem_t& op_problem,
       // allocate not more than 10% of the time limit to presolve.
       // Note that this is not the presolve time, but the time limit for presolve.
       const double presolve_time_limit = 0.1 * time_limit;
-      presolver     = std::make_unique>();
-      problem       = presolver->apply(op_problem, presolve_time_limit);
+      presolver               = std::make_unique>();
+      auto reduced_op_problem = presolver->apply(
+        op_problem, cuopt::linear_programming::problem_category_t::MIP, presolve_time_limit);
+      if (reduced_op_problem.empty()) {
+        return mip_solution_t(mip_termination_status_t::Infeasible,
+                                        solver_stats_t{},
+                                        op_problem.get_handle_ptr()->get_stream());
+      }
+      problem       = detail::problem_t(reduced_op_problem);
       presolve_time = timer.elapsed_time();
       CUOPT_LOG_INFO("Third party presolve time: %f", presolve_time);
     }
@@ -199,18 +206,20 @@ mip_solution_t solve_mip(optimization_problem_t& op_problem,
 
     auto reduced_solution = run_mip(problem, settings, timer);
 
-    auto const& full_sol_vec = reduced_solution.get_solution();
-    rmm::device_uvector full_sol_vec_tmp(full_sol_vec.size(),
+    auto primal_solution = cuopt::device_copy(reduced_solution.get_solution(),
                                               op_problem.get_handle_ptr()->get_stream());
-    raft::copy(full_sol_vec_tmp.data(),
-               full_sol_vec.data(),
-               full_sol_vec.size(),
-               op_problem.get_handle_ptr()->get_stream());
-    if (settings.presolve) { full_sol_vec_tmp = presolver->undo(full_sol_vec_tmp); }
+
+    if (settings.presolve) {
+      presolver->undo(primal_solution,
+                      primal_solution,
+                      primal_solution,
+                      cuopt::linear_programming::problem_category_t::MIP,
+                      op_problem.get_handle_ptr()->get_stream());
+    }
 
     detail::problem_t full_problem(op_problem);
     detail::solution_t full_sol(full_problem);
-    full_sol.copy_new_assignment(cuopt::host_copy(full_sol_vec_tmp));
+    full_sol.copy_new_assignment(cuopt::host_copy(primal_solution));
     full_sol.compute_feasibility();
     if (!full_sol.get_feasible()) {
       CUOPT_LOG_WARN("The solution is not feasible after post solve");
diff --git a/cpp/src/utilities/copy_helpers.hpp b/cpp/src/utilities/copy_helpers.hpp
index 611e7811aa..78593d3e1a 100644
--- a/cpp/src/utilities/copy_helpers.hpp
+++ b/cpp/src/utilities/copy_helpers.hpp
@@ -114,6 +114,23 @@ auto host_copy(rmm::device_uvector const& device_vec, rmm::cuda_stream_view s
   return host_copy(device_vec.data(), device_vec.size(), stream_view);
 }
 
+/**
+ * @brief Simple utility function to copy std::vector to device
+ *
+ * @tparam T
+ * @param[in] device_vec
+ * @param[in] stream_view
+ * @return device_vec
+ */
+template 
+inline rmm::device_uvector device_copy(rmm::device_uvector const& device_vec,
+                                          rmm::cuda_stream_view stream_view)
+{
+  rmm::device_uvector device_vec_copy(device_vec.size(), stream_view);
+  raft::copy(device_vec_copy.data(), device_vec.data(), device_vec.size(), stream_view);
+  return device_vec_copy;
+}
+
 /**
  * @brief Simple utility function to copy std::vector to device
  *

From 00b22a8ae8273856fa15d2e27659c6dc43654fce Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Mon, 28 Jul 2025 22:01:01 -0700
Subject: [PATCH 014/108] Add tolerances from settings

---
 cpp/src/linear_programming/solve.cu           |  6 ++++--
 cpp/src/mip/presolve/third_party_presolve.cu  | 14 +++++++++-----
 cpp/src/mip/presolve/third_party_presolve.cuh |  1 +
 cpp/src/mip/solve.cu                          |  6 ++++--
 4 files changed, 18 insertions(+), 9 deletions(-)

diff --git a/cpp/src/linear_programming/solve.cu b/cpp/src/linear_programming/solve.cu
index 613aebd71d..05d98bb229 100644
--- a/cpp/src/linear_programming/solve.cu
+++ b/cpp/src/linear_programming/solve.cu
@@ -599,8 +599,10 @@ optimization_problem_solution_t solve_lp(optimization_problem_t>();
-      auto reduced_problem = presolver->apply(
-        op_problem, cuopt::linear_programming::problem_category_t::LP, presolve_time_limit);
+      auto reduced_problem = presolver->apply(op_problem,
+                                              cuopt::linear_programming::problem_category_t::LP,
+                                              settings.tolerances.absolute_primal_tolerance,
+                                              presolve_time_limit);
       if (reduced_problem.empty()) {
         return optimization_problem_solution_t(
           pdlp_termination_status_t::PrimalInfeasible, op_problem.get_handle_ptr()->get_stream());
diff --git a/cpp/src/mip/presolve/third_party_presolve.cu b/cpp/src/mip/presolve/third_party_presolve.cu
index 8e6ea004eb..3ef9d7c45f 100644
--- a/cpp/src/mip/presolve/third_party_presolve.cu
+++ b/cpp/src/mip/presolve/third_party_presolve.cu
@@ -212,9 +212,12 @@ void set_presolve_methods(papilo::Presolve& presolver, problem_category_t c
 template 
 void set_presolve_options(papilo::Presolve& presolver,
                           problem_category_t category,
+                          f_t absolute_tolerance,
                           double time_limit)
 {
-  presolver.getPresolveOptions().tlim = time_limit;
+  presolver.getPresolveOptions().tlim    = time_limit;
+  presolver.getPresolveOptions().epsilon = absolute_tolerance;
+  presolver.getPresolveOptions().feastol = absolute_tolerance;
   if (category == problem_category_t::LP) {
     presolver.getPresolveOptions().componentsmaxint = -1;
     presolver.getPresolveOptions().detectlindep     = 0;
@@ -225,6 +228,7 @@ template 
 optimization_problem_t third_party_presolve_t::apply(
   optimization_problem_t const& op_problem,
   problem_category_t category,
+  f_t absolute_tolerance,
   double time_limit)
 {
   papilo::Problem papilo_problem = build_papilo_problem(op_problem);
@@ -236,7 +240,7 @@ optimization_problem_t third_party_presolve_t::apply(
 
   papilo::Presolve presolver;
   set_presolve_methods(presolver, category);
-  set_presolve_options(presolver, category, time_limit);
+  set_presolve_options(presolver, category, absolute_tolerance, time_limit);
 
   auto result = presolver.apply(papilo_problem);
   if (result.status == papilo::PresolveStatus::kInfeasible) {
@@ -268,9 +272,9 @@ void third_party_presolve_t::undo(rmm::device_uvector& primal_sol
   papilo::Solution reduced_sol(primal_sol_vec_h);
   papilo::Solution full_sol;
   if (category == problem_category_t::LP) {
-    full_sol.type         = papilo::SolutionType::kPrimalDual;
-    full_sol.dual         = dual_sol_vec_h;
-    full_sol.reducedCosts = reduced_costs_vec_h;
+    reduced_sol.type         = papilo::SolutionType::kPrimalDual;
+    reduced_sol.dual         = dual_sol_vec_h;
+    reduced_sol.reducedCosts = reduced_costs_vec_h;
   }
 
   papilo::Message Msg{};
diff --git a/cpp/src/mip/presolve/third_party_presolve.cuh b/cpp/src/mip/presolve/third_party_presolve.cuh
index c33865a476..2207075fe4 100644
--- a/cpp/src/mip/presolve/third_party_presolve.cuh
+++ b/cpp/src/mip/presolve/third_party_presolve.cuh
@@ -32,6 +32,7 @@ class third_party_presolve_t {
 
   optimization_problem_t apply(optimization_problem_t const& op_problem,
                                          problem_category_t category,
+                                         f_t absolute_tolerance,
                                          double time_limit);
 
   void undo(rmm::device_uvector& primal_solution,
diff --git a/cpp/src/mip/solve.cu b/cpp/src/mip/solve.cu
index 6018fbd5fd..43facabfcb 100644
--- a/cpp/src/mip/solve.cu
+++ b/cpp/src/mip/solve.cu
@@ -185,8 +185,10 @@ mip_solution_t solve_mip(optimization_problem_t& op_problem,
       // Note that this is not the presolve time, but the time limit for presolve.
       const double presolve_time_limit = 0.1 * time_limit;
       presolver               = std::make_unique>();
-      auto reduced_op_problem = presolver->apply(
-        op_problem, cuopt::linear_programming::problem_category_t::MIP, presolve_time_limit);
+      auto reduced_op_problem = presolver->apply(op_problem,
+                                                 cuopt::linear_programming::problem_category_t::MIP,
+                                                 settings.tolerances.absolute_tolerance,
+                                                 presolve_time_limit);
       if (reduced_op_problem.empty()) {
         return mip_solution_t(mip_termination_status_t::Infeasible,
                                         solver_stats_t{},

From 168e7e950f929b67c2552c35a4866cde0b30b70e Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Tue, 29 Jul 2025 07:19:20 -0700
Subject: [PATCH 015/108] Fix mip after postsolve changes

---
 cpp/src/mip/solve.cu | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/cpp/src/mip/solve.cu b/cpp/src/mip/solve.cu
index 43facabfcb..6b65b71afc 100644
--- a/cpp/src/mip/solve.cu
+++ b/cpp/src/mip/solve.cu
@@ -212,9 +212,11 @@ mip_solution_t solve_mip(optimization_problem_t& op_problem,
                                               op_problem.get_handle_ptr()->get_stream());
 
     if (settings.presolve) {
+      rmm::device_uvector dual_solution(0, op_problem.get_handle_ptr()->get_stream());
+      rmm::device_uvector reduced_costs(0, op_problem.get_handle_ptr()->get_stream());
       presolver->undo(primal_solution,
-                      primal_solution,
-                      primal_solution,
+                      dual_solution,
+                      reduced_costs,
                       cuopt::linear_programming::problem_category_t::MIP,
                       op_problem.get_handle_ptr()->get_stream());
     }

From 72d9ed13c7f43f7134714a18d1e41a6ab27d1c6d Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Tue, 29 Jul 2025 08:16:43 -0700
Subject: [PATCH 016/108] Fix style

---
 conda/environments/all_cuda-128_arch-aarch64.yaml | 2 --
 conda/environments/all_cuda-128_arch-x86_64.yaml  | 2 --
 cpp/CMakeLists.txt                                | 1 -
 3 files changed, 5 deletions(-)

diff --git a/conda/environments/all_cuda-128_arch-aarch64.yaml b/conda/environments/all_cuda-128_arch-aarch64.yaml
index 3b81c1ad03..55d283e0b1 100644
--- a/conda/environments/all_cuda-128_arch-aarch64.yaml
+++ b/conda/environments/all_cuda-128_arch-aarch64.yaml
@@ -30,7 +30,6 @@ dependencies:
 - gcc_linux-aarch64=13.*
 - geopandas
 - gmock
-- gmp
 - gtest
 - httpx
 - ipython
@@ -80,7 +79,6 @@ dependencies:
 - sphinxcontrib-openapi
 - sphinxcontrib-websupport
 - sysroot_linux-aarch64==2.28
-- tbb-devel
 - uvicorn==0.34.*
 - pip:
   - nvidia_sphinx_theme
diff --git a/conda/environments/all_cuda-128_arch-x86_64.yaml b/conda/environments/all_cuda-128_arch-x86_64.yaml
index e3cfa1d30d..30b6c1718e 100644
--- a/conda/environments/all_cuda-128_arch-x86_64.yaml
+++ b/conda/environments/all_cuda-128_arch-x86_64.yaml
@@ -30,7 +30,6 @@ dependencies:
 - gcc_linux-64=13.*
 - geopandas
 - gmock
-- gmp
 - gtest
 - httpx
 - ipython
@@ -80,7 +79,6 @@ dependencies:
 - sphinxcontrib-openapi
 - sphinxcontrib-websupport
 - sysroot_linux-64==2.28
-- tbb-devel
 - uvicorn==0.34.*
 - pip:
   - nvidia_sphinx_theme
diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt
index 99ccb10806..fc8c4bf503 100644
--- a/cpp/CMakeLists.txt
+++ b/cpp/CMakeLists.txt
@@ -157,7 +157,6 @@ endif()
 find_package(TBB REQUIRED)
 
 FetchContent_Declare(
-  papilo
   GIT_REPOSITORY "git@github.com:scipopt/papilo.git"
   GIT_TAG "main"
 )

From f34cb0f5c63e45c69b5fa986decef4d00e4541b2 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Tue, 29 Jul 2025 08:23:43 -0700
Subject: [PATCH 017/108] Add boost dependency

---
 cpp/CMakeLists.txt | 1 +
 1 file changed, 1 insertion(+)

diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt
index cf9cb0a6c0..451702924a 100644
--- a/cpp/CMakeLists.txt
+++ b/cpp/CMakeLists.txt
@@ -155,6 +155,7 @@ endif()
 find_package(TBB REQUIRED)
 
 FetchContent_Declare(
+  papilo
   GIT_REPOSITORY "git@github.com:scipopt/papilo.git"
   GIT_TAG "main"
 )

From 7425f0a18e0bbea90ce5a4e58e5e034654254d41 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Tue, 29 Jul 2025 08:27:27 -0700
Subject: [PATCH 018/108] Re add dependencies

---
 conda/environments/all_cuda-128_arch-aarch64.yaml | 3 +++
 conda/environments/all_cuda-128_arch-x86_64.yaml  | 3 +++
 2 files changed, 6 insertions(+)

diff --git a/conda/environments/all_cuda-128_arch-aarch64.yaml b/conda/environments/all_cuda-128_arch-aarch64.yaml
index 55d283e0b1..c528312205 100644
--- a/conda/environments/all_cuda-128_arch-aarch64.yaml
+++ b/conda/environments/all_cuda-128_arch-aarch64.yaml
@@ -5,6 +5,7 @@ channels:
 - rapidsai-nightly
 - conda-forge
 dependencies:
+- boost
 - breathe
 - c-compiler
 - ccache
@@ -30,6 +31,7 @@ dependencies:
 - gcc_linux-aarch64=13.*
 - geopandas
 - gmock
+- gmp
 - gtest
 - httpx
 - ipython
@@ -79,6 +81,7 @@ dependencies:
 - sphinxcontrib-openapi
 - sphinxcontrib-websupport
 - sysroot_linux-aarch64==2.28
+- tbb-devel
 - uvicorn==0.34.*
 - pip:
   - nvidia_sphinx_theme
diff --git a/conda/environments/all_cuda-128_arch-x86_64.yaml b/conda/environments/all_cuda-128_arch-x86_64.yaml
index 30b6c1718e..4f06e232b0 100644
--- a/conda/environments/all_cuda-128_arch-x86_64.yaml
+++ b/conda/environments/all_cuda-128_arch-x86_64.yaml
@@ -5,6 +5,7 @@ channels:
 - rapidsai-nightly
 - conda-forge
 dependencies:
+- boost
 - breathe
 - c-compiler
 - ccache
@@ -30,6 +31,7 @@ dependencies:
 - gcc_linux-64=13.*
 - geopandas
 - gmock
+- gmp
 - gtest
 - httpx
 - ipython
@@ -79,6 +81,7 @@ dependencies:
 - sphinxcontrib-openapi
 - sphinxcontrib-websupport
 - sysroot_linux-64==2.28
+- tbb-devel
 - uvicorn==0.34.*
 - pip:
   - nvidia_sphinx_theme

From 51fc7f6eda2d02451dff52a9c4ba548985449941 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Tue, 29 Jul 2025 09:04:03 -0700
Subject: [PATCH 019/108] Fix compile error

---
 benchmarks/linear_programming/cuopt/run_mip.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/benchmarks/linear_programming/cuopt/run_mip.cpp b/benchmarks/linear_programming/cuopt/run_mip.cpp
index b458b07402..3163719488 100644
--- a/benchmarks/linear_programming/cuopt/run_mip.cpp
+++ b/benchmarks/linear_programming/cuopt/run_mip.cpp
@@ -360,7 +360,7 @@ int main(int argc, char* argv[])
 
   std::string out_dir;
   std::string result_file;
-  int batch_num;
+  int batch_num = -1;
 
   bool heuristics_only = program.get("--heuristics-only")[0] == 't';
   int num_cpu_threads  = program.get("--num-cpu-threads");

From 4f1164ee12d2baab599a60ab90e44751335d820b Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Tue, 29 Jul 2025 09:14:49 -0700
Subject: [PATCH 020/108] Remove gmp from dependencies

---
 conda/environments/all_cuda-128_arch-aarch64.yaml | 1 -
 conda/environments/all_cuda-128_arch-x86_64.yaml  | 1 -
 conda/recipes/libcuopt/recipe.yaml                | 2 ++
 dependencies.yaml                                 | 2 ++
 4 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/conda/environments/all_cuda-128_arch-aarch64.yaml b/conda/environments/all_cuda-128_arch-aarch64.yaml
index c528312205..ebabb1a59d 100644
--- a/conda/environments/all_cuda-128_arch-aarch64.yaml
+++ b/conda/environments/all_cuda-128_arch-aarch64.yaml
@@ -31,7 +31,6 @@ dependencies:
 - gcc_linux-aarch64=13.*
 - geopandas
 - gmock
-- gmp
 - gtest
 - httpx
 - ipython
diff --git a/conda/environments/all_cuda-128_arch-x86_64.yaml b/conda/environments/all_cuda-128_arch-x86_64.yaml
index 4f06e232b0..4b7e8cb877 100644
--- a/conda/environments/all_cuda-128_arch-x86_64.yaml
+++ b/conda/environments/all_cuda-128_arch-x86_64.yaml
@@ -31,7 +31,6 @@ dependencies:
 - gcc_linux-64=13.*
 - geopandas
 - gmock
-- gmp
 - gtest
 - httpx
 - ipython
diff --git a/conda/recipes/libcuopt/recipe.yaml b/conda/recipes/libcuopt/recipe.yaml
index b507a35fb3..815f6ac75e 100644
--- a/conda/recipes/libcuopt/recipe.yaml
+++ b/conda/recipes/libcuopt/recipe.yaml
@@ -49,6 +49,7 @@ cache:
 
   requirements:
     build:
+      - boost
       - ${{ compiler("c") }}
       - ${{ compiler("cxx") }}
       - ${{ compiler("cuda") }} =${{ cuda_version }}
@@ -56,6 +57,7 @@ cache:
       - cuda-version =${{ cuda_version }}
       - cmake ${{ cmake_version }}
       - ninja
+      - tbb-devel
     host:
       - cpp-argparse
       - cuda-version =${{ cuda_version }}
diff --git a/dependencies.yaml b/dependencies.yaml
index 3aa6c94601..a62433f4d9 100644
--- a/dependencies.yaml
+++ b/dependencies.yaml
@@ -309,9 +309,11 @@ dependencies:
     common:
       - output_types: conda
         packages:
+          - boost
           - cpp-argparse
           - librmm==25.8.*
           - libraft-headers==25.8.*
+          - tbb-devel
   test_cpp:
     common:
       - output_types: [conda]

From 234694cdf923eefd4b48cb606b38a23b13f8e79c Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Tue, 29 Jul 2025 09:44:26 -0700
Subject: [PATCH 021/108] Use https link

---
 cpp/CMakeLists.txt | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt
index 451702924a..27ece243ca 100644
--- a/cpp/CMakeLists.txt
+++ b/cpp/CMakeLists.txt
@@ -156,7 +156,8 @@ find_package(TBB REQUIRED)
 
 FetchContent_Declare(
   papilo
-  GIT_REPOSITORY "git@github.com:scipopt/papilo.git"
+  # SOURCE_DIR /home/hugo/rapids/papilo
+  GIT_REPOSITORY "https://github.com/scipopt/papilo.git"
   GIT_TAG "main"
 )
 

From f86b6b7df30ab0049e40430dedd4cbec485c19b0 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Tue, 29 Jul 2025 09:59:03 -0700
Subject: [PATCH 022/108] Update third party license

---
 thirdparty/THIRD_PARTY_LICENSES | 869 +++++++++++++++++++++++++++++++-
 1 file changed, 861 insertions(+), 8 deletions(-)

diff --git a/thirdparty/THIRD_PARTY_LICENSES b/thirdparty/THIRD_PARTY_LICENSES
index 665324e106..e6cb70a114 100644
--- a/thirdparty/THIRD_PARTY_LICENSES
+++ b/thirdparty/THIRD_PARTY_LICENSES
@@ -1,11 +1,11 @@
-==================================================
-Third Party Licenses
-==================================================
-
------------------------------------------------------------------------------------------
-== cxxopts
-
-Files: cpp/tests/utilities/cxxopts.hpp
+==================================================
+Third Party Licenses
+==================================================
+
+-----------------------------------------------------------------------------------------
+== cxxopts
+
+Files: cpp/tests/utilities/cxxopts.hpp
 Copyright (c) 2014 Jarryd Beck
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -25,3 +25,856 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
+
+
+-----------------------------------------------------------------------------------------
+== papilo LGPL-3.0
+
+Files: cpp/build/_deps/papilo-src
+
+                   GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. 
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+  This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+  0. Additional Definitions.
+
+  As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+  "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+  An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+  A "Combined Work" is a work produced by combining or linking an
+Application with the Library.  The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+  The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+  The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+  1. Exception to Section 3 of the GNU GPL.
+
+  You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+  2. Conveying Modified Versions.
+
+  If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+   a) under this License, provided that you make a good faith effort to
+   ensure that, in the event an Application does not supply the
+   function or data, the facility still operates, and performs
+   whatever part of its purpose remains meaningful, or
+
+   b) under the GNU GPL, with none of the additional permissions of
+   this License applicable to that copy.
+
+  3. Object Code Incorporating Material from Library Header Files.
+
+  The object code form of an Application may incorporate material from
+a header file that is part of the Library.  You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+   a) Give prominent notice with each copy of the object code that the
+   Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the object code with a copy of the GNU GPL and this license
+   document.
+
+  4. Combined Works.
+
+  You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+   a) Give prominent notice with each copy of the Combined Work that
+   the Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the Combined Work with a copy of the GNU GPL and this license
+   document.
+
+   c) For a Combined Work that displays copyright notices during
+   execution, include the copyright notice for the Library among
+   these notices, as well as a reference directing the user to the
+   copies of the GNU GPL and this license document.
+
+   d) Do one of the following:
+
+       0) Convey the Minimal Corresponding Source under the terms of this
+       License, and the Corresponding Application Code in a form
+       suitable for, and under terms that permit, the user to
+       recombine or relink the Application with a modified version of
+       the Linked Version to produce a modified Combined Work, in the
+       manner specified by section 6 of the GNU GPL for conveying
+       Corresponding Source.
+
+       1) Use a suitable shared library mechanism for linking with the
+       Library.  A suitable mechanism is one that (a) uses at run time
+       a copy of the Library already present on the user's computer
+       system, and (b) will operate properly with a modified version
+       of the Library that is interface-compatible with the Linked
+       Version.
+
+   e) Provide Installation Information, but only if you would otherwise
+   be required to provide such information under section 6 of the
+   GNU GPL, and only to the extent that such information is
+   necessary to install and execute a modified version of the
+   Combined Work produced by recombining or relinking the
+   Application with a modified version of the Linked Version. (If
+   you use option 4d0, the Installation Information must accompany
+   the Minimal Corresponding Source and Corresponding Application
+   Code. If you use option 4d1, you must provide the Installation
+   Information in the manner specified by section 6 of the GNU GPL
+   for conveying Corresponding Source.)
+
+  5. Combined Libraries.
+
+  You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+   a) Accompany the combined library with a copy of the same work based
+   on the Library, uncombined with any other library facilities,
+   conveyed under the terms of this License.
+
+   b) Give prominent notice with the combined library that part of it
+   is a work based on the Library, and explaining where to find the
+   accompanying uncombined form of the same work.
+
+  6. Revised Versions of the GNU Lesser General Public License.
+
+  The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+  Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+  If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
+
+
+-----------------------------------------------------------------------------------------
+== papilo GPL-3.0
+
+Files: cpp/build/_deps/papilo-src
+
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. 
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    
+    Copyright (C)   
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+      Copyright (C)   
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+.

From 1276d69b5cec4796ebd5399fb485be5716ff363e Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Tue, 29 Jul 2025 10:36:35 -0700
Subject: [PATCH 023/108] Add boost and tbb to host env

---
 conda/recipes/libcuopt/recipe.yaml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/conda/recipes/libcuopt/recipe.yaml b/conda/recipes/libcuopt/recipe.yaml
index 815f6ac75e..b726c16ca7 100644
--- a/conda/recipes/libcuopt/recipe.yaml
+++ b/conda/recipes/libcuopt/recipe.yaml
@@ -70,6 +70,8 @@ cache:
       - libcurand-dev
       - libcusparse-dev
       - cuda-cudart-dev
+      - boost
+      - tbb-devel
 
 outputs:
   - package:

From cfdfa562a3b14204c03474b7ad07c33daaf8746d Mon Sep 17 00:00:00 2001
From: Ramakrishnap <42624703+rgsl888prabhu@users.noreply.github.com>
Date: Tue, 29 Jul 2025 14:14:57 -0500
Subject: [PATCH 024/108] Update recipe.yaml

---
 conda/recipes/libcuopt/recipe.yaml | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/conda/recipes/libcuopt/recipe.yaml b/conda/recipes/libcuopt/recipe.yaml
index b726c16ca7..388d4aa87c 100644
--- a/conda/recipes/libcuopt/recipe.yaml
+++ b/conda/recipes/libcuopt/recipe.yaml
@@ -130,17 +130,21 @@ outputs:
         - ${{ stdlib("c") }}
       host:
         - ${{ pin_subpackage("libmps-parser", exact=True) }}
+        - boost
         - cuda-version =${{ cuda_version }}
         - rapids-logger =0.1
         - librmm =${{ dep_minor_version }}
         - cuda-cudart-dev
         - libcublas
         - libcusparse-dev
+        - tbb-devel
       run:
         - ${{ pin_compatible("cuda-version", upper_bound="x", lower_bound="x") }}
         - ${{ pin_subpackage("libmps-parser", exact=True) }}
+        - boost
         - librmm =${{ dep_minor_version }}
         - cuda-nvrtc
+        - tbb-devel
       ignore_run_exports:
         by_name:
           - cuda-cudart
@@ -150,6 +154,8 @@ outputs:
           - libcurand
           - libcusparse
           - librmm
+          - libboost
+          - libboost_iostreams
     tests:
     - package_contents:
         files:
@@ -177,11 +183,13 @@ outputs:
       host:
         - ${{ pin_subpackage("libcuopt", exact=True) }}
         - ${{ pin_subpackage("libmps-parser", exact=True) }}
+        - boost
         - gmock ${{ gtest_version }}
         - gtest ${{ gtest_version }}
         - cuda-cudart-dev
         - libcublas
         - libcusparse-dev
+        - tbb-devel
       run:
         - gmock ${{ gtest_version }}
         - gtest ${{ gtest_version }}
@@ -196,6 +204,8 @@ outputs:
           - libcurand
           - libcusparse
           - librmm
+          - libboost
+          - libboost_iostreams
     about:
       homepage: ${{ load_from_file("python/cuopt/pyproject.toml").project.urls.Homepage }}
       license: ${{ load_from_file("python/cuopt/pyproject.toml").project.license.text }}

From f1e637c8ffe88ae295e0468dacf44166278bcd21 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Tue, 29 Jul 2025 11:48:20 -0700
Subject: [PATCH 025/108] Set row and col flags

---
 cpp/CMakeLists.txt                           |  6 +++---
 cpp/src/mip/presolve/third_party_presolve.cu | 16 +++++++++++++---
 2 files changed, 16 insertions(+), 6 deletions(-)

diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt
index 27ece243ca..ed4dcc82d3 100644
--- a/cpp/CMakeLists.txt
+++ b/cpp/CMakeLists.txt
@@ -156,9 +156,9 @@ find_package(TBB REQUIRED)
 
 FetchContent_Declare(
   papilo
-  # SOURCE_DIR /home/hugo/rapids/papilo
-  GIT_REPOSITORY "https://github.com/scipopt/papilo.git"
-  GIT_TAG "main"
+  SOURCE_DIR /home/hugo/rapids/papilo
+  # GIT_REPOSITORY "https://github.com/scipopt/papilo.git"
+  # GIT_TAG "main"
 )
 
 FetchContent_MakeAvailable(papilo)
diff --git a/cpp/src/mip/presolve/third_party_presolve.cu b/cpp/src/mip/presolve/third_party_presolve.cu
index 3ef9d7c45f..3f30fc81b3 100644
--- a/cpp/src/mip/presolve/third_party_presolve.cu
+++ b/cpp/src/mip/presolve/third_party_presolve.cu
@@ -68,7 +68,7 @@ papilo::Problem build_papilo_problem(const optimization_problem_t
   builder.setColLbAll(h_var_lb);
   builder.setColUbAll(h_var_ub);
 
-  for (i_t i = 0; i < num_cols; i++) {
+  for (i_t i = 0; i < num_cols; ++i) {
     builder.setColIntegral(i, h_var_types[i] == var_t::INTEGER);
   }
 
@@ -76,13 +76,20 @@ papilo::Problem build_papilo_problem(const optimization_problem_t
   builder.setRowRhsAll(h_constr_ub);
 
   // Add constraints row by row
-  for (i_t i = 0; i < num_rows; i++) {
+  for (i_t i = 0; i < num_rows; ++i) {
     // Get row entries
     i_t row_start   = h_offsets[i];
     i_t row_end     = h_offsets[i + 1];
     i_t num_entries = row_end - row_start;
     builder.addRowEntries(
       i, num_entries, h_variables.data() + row_start, h_coefficients.data() + row_start);
+    builder.setRowLhsInf(i, h_constr_lb[i] == -std::numeric_limits::infinity());
+    builder.setRowRhsInf(i, h_constr_ub[i] == std::numeric_limits::infinity());
+  }
+
+  for (i_t i = 0; i < num_cols; ++i) {
+    builder.setColLbInf(i, h_var_lb[i] == -std::numeric_limits::infinity());
+    builder.setColUbInf(i, h_var_ub[i] == std::numeric_limits::infinity());
   }
 
   return builder.build();
@@ -172,7 +179,10 @@ void check_presolve_status(const papilo::PresolveStatus& status)
 void check_postsolve_status(const papilo::PostsolveStatus& status)
 {
   switch (status) {
-    case papilo::PostsolveStatus::kOk: CUOPT_LOG_INFO("Post-solve succeeded"); break;
+    case papilo::PostsolveStatus::kOk:
+      CUOPT_LOG_INFO("Post-solve succeeded");
+      break;
+      // This occurs when the solution is not feasible
     case papilo::PostsolveStatus::kFailed: CUOPT_LOG_INFO("Post-solve failed"); break;
   }
 }

From 202ac9f505f116f1ce8911613274940794094ab9 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Tue, 29 Jul 2025 12:24:44 -0700
Subject: [PATCH 026/108] Disable test build for papilo

---
 cpp/CMakeLists.txt | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt
index ed4dcc82d3..99e9ce1a5a 100644
--- a/cpp/CMakeLists.txt
+++ b/cpp/CMakeLists.txt
@@ -156,11 +156,13 @@ find_package(TBB REQUIRED)
 
 FetchContent_Declare(
   papilo
-  SOURCE_DIR /home/hugo/rapids/papilo
-  # GIT_REPOSITORY "https://github.com/scipopt/papilo.git"
-  # GIT_TAG "main"
+  # SOURCE_DIR /home/hugo/rapids/papilo
+  GIT_REPOSITORY "https://github.com/scipopt/papilo.git"
+  GIT_TAG "main"
 )
 
+set(BUILD_TESTING OFF CACHE BOOL "Disable test build for papilo")
+
 FetchContent_MakeAvailable(papilo)
 
 include(${rapids-cmake-dir}/cpm/rapids_logger.cmake)

From d3cfccc00c139526f277801bab15eb7b51cd5478 Mon Sep 17 00:00:00 2001
From: Trevor McKay 
Date: Tue, 29 Jul 2025 15:48:20 -0400
Subject: [PATCH 027/108] dd doc for CUOPT_PRESOLVE, add missing params to
 lp-milp-c-api.rst

---
 docs/cuopt/source/cuopt-c/lp-milp/lp-milp-c-api.rst | 8 +++++++-
 docs/cuopt/source/lp-milp-settings.rst              | 3 +++
 2 files changed, 10 insertions(+), 1 deletion(-)

diff --git a/docs/cuopt/source/cuopt-c/lp-milp/lp-milp-c-api.rst b/docs/cuopt/source/cuopt-c/lp-milp/lp-milp-c-api.rst
index 11e5763004..d9cbe1806d 100644
--- a/docs/cuopt/source/cuopt-c/lp-milp/lp-milp-c-api.rst
+++ b/docs/cuopt/source/cuopt-c/lp-milp/lp-milp-c-api.rst
@@ -153,15 +153,21 @@ These constants are used as parameter names in the :c:func:`cuOptSetParameter`,
 .. doxygendefine:: CUOPT_SAVE_BEST_PRIMAL_SO_FAR
 .. doxygendefine:: CUOPT_FIRST_PRIMAL_FEASIBLE
 .. doxygendefine:: CUOPT_LOG_FILE
+.. doxygendefine:: CUOPT_LOG_TO_CONSOLE
+.. doxygendefine:: CUOPT_CROSSOVER
+.. doxygendefine:: CUOPT_PRESOLVE
 .. doxygendefine:: CUOPT_MIP_ABSOLUTE_TOLERANCE
 .. doxygendefine:: CUOPT_MIP_RELATIVE_TOLERANCE
 .. doxygendefine:: CUOPT_MIP_INTEGRALITY_TOLERANCE
-.. doxygendefine:: CUOPT_MIP_SCALING
+.. doxygendefine:: CUOPT_MIP_ABSOLUTE_GAP
+.. doxygendefine:: CUOPT_MIP_RELATIVE_GAP
 .. doxygendefine:: CUOPT_MIP_HEURISTICS_ONLY
+.. doxygendefine:: CUOPT_MIP_SCALING
 .. doxygendefine:: CUOPT_SOLUTION_FILE
 .. doxygendefine:: CUOPT_NUM_CPU_THREADS
 .. doxygendefine:: CUOPT_USER_PROBLEM_FILE
 
+
 .. _pdlp-solver-mode-constants:
 
 PDLP Solver Mode Constants
diff --git a/docs/cuopt/source/lp-milp-settings.rst b/docs/cuopt/source/lp-milp-settings.rst
index 28e5105d09..9dbcbb64a4 100644
--- a/docs/cuopt/source/lp-milp-settings.rst
+++ b/docs/cuopt/source/lp-milp-settings.rst
@@ -61,6 +61,9 @@ parallel parts of the solvers.
 
 Note: by default the number of CPU threads is automatically determined based on the number of CPU cores.
 
+Presolve
+^^^^^^^^
+``CUOPT_PRESOLVE`` controls whether presolve is enabled. Presolve can reduce problem size and improve solve time. Default is enabled.
 
 Linear Programming
 ------------------

From 34413ffa9a045197b210629da5a8ed8afcde5ea4 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Tue, 29 Jul 2025 16:39:13 -0700
Subject: [PATCH 028/108] Set to zero inf bounds for papilo

---
 cpp/CMakeLists.txt                           |  1 -
 cpp/src/mip/presolve/third_party_presolve.cu | 27 +++++++++++++-------
 python/cuopt/CMakeLists.txt                  |  1 +
 3 files changed, 19 insertions(+), 10 deletions(-)

diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt
index 99e9ce1a5a..80a7f28dc5 100644
--- a/cpp/CMakeLists.txt
+++ b/cpp/CMakeLists.txt
@@ -156,7 +156,6 @@ find_package(TBB REQUIRED)
 
 FetchContent_Declare(
   papilo
-  # SOURCE_DIR /home/hugo/rapids/papilo
   GIT_REPOSITORY "https://github.com/scipopt/papilo.git"
   GIT_TAG "main"
 )
diff --git a/cpp/src/mip/presolve/third_party_presolve.cu b/cpp/src/mip/presolve/third_party_presolve.cu
index 3f30fc81b3..c399e2d440 100644
--- a/cpp/src/mip/presolve/third_party_presolve.cu
+++ b/cpp/src/mip/presolve/third_party_presolve.cu
@@ -85,11 +85,15 @@ papilo::Problem build_papilo_problem(const optimization_problem_t
       i, num_entries, h_variables.data() + row_start, h_coefficients.data() + row_start);
     builder.setRowLhsInf(i, h_constr_lb[i] == -std::numeric_limits::infinity());
     builder.setRowRhsInf(i, h_constr_ub[i] == std::numeric_limits::infinity());
+    if (h_constr_lb[i] == -std::numeric_limits::infinity()) { builder.setRowLhs(i, 0); }
+    if (h_constr_ub[i] == std::numeric_limits::infinity()) { builder.setRowRhs(i, 0); }
   }
 
   for (i_t i = 0; i < num_cols; ++i) {
     builder.setColLbInf(i, h_var_lb[i] == -std::numeric_limits::infinity());
     builder.setColUbInf(i, h_var_ub[i] == std::numeric_limits::infinity());
+    if (h_var_lb[i] == -std::numeric_limits::infinity()) { builder.setColLb(i, 0); }
+    if (h_var_ub[i] == std::numeric_limits::infinity()) { builder.setColUb(i, 0); }
   }
 
   return builder.build();
@@ -97,7 +101,7 @@ papilo::Problem build_papilo_problem(const optimization_problem_t
 
 template 
 optimization_problem_t build_optimization_problem(
-  const papilo::Problem& papilo_problem, raft::handle_t const* handle_ptr)
+  papilo::Problem const& papilo_problem, raft::handle_t const* handle_ptr)
 {
   optimization_problem_t op_problem(handle_ptr);
 
@@ -105,18 +109,14 @@ optimization_problem_t build_optimization_problem(
   op_problem.set_objective_coefficients(obj.coefficients.data(), obj.coefficients.size());
   op_problem.set_objective_offset(obj.offset);
 
-  auto col_lower = papilo_problem.getLowerBounds();
-  auto col_upper = papilo_problem.getUpperBounds();
-  op_problem.set_variable_lower_bounds(col_lower.data(), col_lower.size());
-  op_problem.set_variable_upper_bounds(col_upper.data(), col_upper.size());
-
   auto& constraint_matrix = papilo_problem.getConstraintMatrix();
   auto row_lower          = constraint_matrix.getLeftHandSides();
   auto row_upper          = constraint_matrix.getRightHandSides();
+  auto col_lower          = papilo_problem.getLowerBounds();
+  auto col_upper          = papilo_problem.getUpperBounds();
 
   auto row_flags = constraint_matrix.getRowFlags();
   for (size_t i = 0; i < row_flags.size(); i++) {
-    // Looks like the bounds are not updated correctly in papilo
     if (row_flags[i].test(papilo::RowFlag::kLhsInf)) {
       row_lower[i] = -std::numeric_limits::infinity();
     }
@@ -125,11 +125,11 @@ optimization_problem_t build_optimization_problem(
     }
   }
 
-  auto [index_range, nrows] = constraint_matrix.getRangeInfo();
-
   op_problem.set_constraint_lower_bounds(row_lower.data(), row_lower.size());
   op_problem.set_constraint_upper_bounds(row_upper.data(), row_upper.size());
 
+  auto [index_range, nrows] = constraint_matrix.getRangeInfo();
+
   std::vector offsets(nrows + 1);
   // papilo indices do not start from 0 after presolve
   size_t start = index_range[0].start;
@@ -151,7 +151,16 @@ optimization_problem_t build_optimization_problem(
   for (size_t i = 0; i < col_flags.size(); i++) {
     var_types[i] =
       col_flags[i].test(papilo::ColFlag::kIntegral) ? var_t::INTEGER : var_t::CONTINUOUS;
+    if (col_flags[i].test(papilo::ColFlag::kLbInf)) {
+      col_lower[i] = -std::numeric_limits::infinity();
+    }
+    if (col_flags[i].test(papilo::ColFlag::kUbInf)) {
+      col_upper[i] = std::numeric_limits::infinity();
+    }
   }
+
+  op_problem.set_variable_lower_bounds(col_lower.data(), col_lower.size());
+  op_problem.set_variable_upper_bounds(col_upper.data(), col_upper.size());
   op_problem.set_variable_types(var_types.data(), var_types.size());
 
   return op_problem;
diff --git a/python/cuopt/CMakeLists.txt b/python/cuopt/CMakeLists.txt
index 8ea79d784c..84e38129d5 100644
--- a/python/cuopt/CMakeLists.txt
+++ b/python/cuopt/CMakeLists.txt
@@ -33,6 +33,7 @@ project(
 
 find_package(cuopt ${cuopt_version})
 find_package(mps_parser ${cuopt_version})
+find_package(TBB REQUIRED)
 
 include(rapids-cython-core)
 rapids_cython_init()

From 5dc9e95d656e768b0bc3fce6846f6ecb373952f9 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Wed, 30 Jul 2025 09:12:51 -0700
Subject: [PATCH 029/108] Remove tbb find package

---
 cpp/CMakeLists.txt          | 2 --
 python/cuopt/CMakeLists.txt | 1 -
 2 files changed, 3 deletions(-)

diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt
index 80a7f28dc5..c261647bd3 100644
--- a/cpp/CMakeLists.txt
+++ b/cpp/CMakeLists.txt
@@ -152,8 +152,6 @@ else()
   find_package(RAFT REQUIRED)
 endif()
 
-find_package(TBB REQUIRED)
-
 FetchContent_Declare(
   papilo
   GIT_REPOSITORY "https://github.com/scipopt/papilo.git"
diff --git a/python/cuopt/CMakeLists.txt b/python/cuopt/CMakeLists.txt
index 84e38129d5..8ea79d784c 100644
--- a/python/cuopt/CMakeLists.txt
+++ b/python/cuopt/CMakeLists.txt
@@ -33,7 +33,6 @@ project(
 
 find_package(cuopt ${cuopt_version})
 find_package(mps_parser ${cuopt_version})
-find_package(TBB REQUIRED)
 
 include(rapids-cython-core)
 rapids_cython_init()

From d6063b8874a8cf540fd4a22ef5ea4264a95a8320 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Wed, 30 Jul 2025 09:44:18 -0700
Subject: [PATCH 030/108] Find tbb and link it for python layer symbol issue

---
 cpp/CMakeLists.txt          | 3 +++
 python/cuopt/CMakeLists.txt | 1 +
 2 files changed, 4 insertions(+)

diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt
index a35aa5dfc4..08dbe038eb 100644
--- a/cpp/CMakeLists.txt
+++ b/cpp/CMakeLists.txt
@@ -158,6 +158,8 @@ else()
   find_package(RAFT REQUIRED)
 endif()
 
+find_package(TBB REQUIRED)
+
 FetchContent_Declare(
   papilo
   GIT_REPOSITORY "https://github.com/scipopt/papilo.git"
@@ -251,6 +253,7 @@ target_link_libraries(cuopt
   CCCL::CCCL
   raft::raft
   cuopt::mps_parser
+  TBB::tbb
   PRIVATE
   ${CUOPT_PRIVATE_CUDA_LIBS}
   )
diff --git a/python/cuopt/CMakeLists.txt b/python/cuopt/CMakeLists.txt
index 8ea79d784c..84e38129d5 100644
--- a/python/cuopt/CMakeLists.txt
+++ b/python/cuopt/CMakeLists.txt
@@ -33,6 +33,7 @@ project(
 
 find_package(cuopt ${cuopt_version})
 find_package(mps_parser ${cuopt_version})
+find_package(TBB REQUIRED)
 
 include(rapids-cython-core)
 rapids_cython_init()

From d7718c96feaa0ff8a8c1f5c639872e4d2db79ed4 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Wed, 30 Jul 2025 12:38:55 -0700
Subject: [PATCH 031/108] Fix reduction to empty papilo model. Fix assumption
 that variables and constraint bounds are always given.

---
 cpp/src/mip/presolve/third_party_presolve.cu  |   40 +-
 cpp/tests/linear_programming/pdlp_test.cu     | 1737 +++++++++--------
 .../utilities/pdlp_test_utilities.cuh         |    3 +
 .../linear_programming/test_lp_solver.py      |    3 +
 4 files changed, 921 insertions(+), 862 deletions(-)

diff --git a/cpp/src/mip/presolve/third_party_presolve.cu b/cpp/src/mip/presolve/third_party_presolve.cu
index c399e2d440..5738ef9f11 100644
--- a/cpp/src/mip/presolve/third_party_presolve.cu
+++ b/cpp/src/mip/presolve/third_party_presolve.cu
@@ -34,7 +34,6 @@ papilo::Problem build_papilo_problem(const optimization_problem_t
   const i_t num_rows = op_problem.get_n_constraints();
   const i_t nnz      = op_problem.get_nnz();
 
-  // Set problem dimensions in builder
   builder.reserve(nnz, num_rows, num_cols);
 
   // Get problem data from optimization problem
@@ -44,6 +43,8 @@ papilo::Problem build_papilo_problem(const optimization_problem_t
   const auto& obj_coeffs   = op_problem.get_objective_coefficients();
   const auto& var_lb       = op_problem.get_variable_lower_bounds();
   const auto& var_ub       = op_problem.get_variable_upper_bounds();
+  const auto& bounds       = op_problem.get_constraint_bounds();
+  const auto& row_types    = op_problem.get_row_types();
   const auto& constr_lb    = op_problem.get_constraint_lower_bounds();
   const auto& constr_ub    = op_problem.get_constraint_upper_bounds();
   const auto& var_types    = op_problem.get_variable_types();
@@ -55,28 +56,50 @@ papilo::Problem build_papilo_problem(const optimization_problem_t
   std::vector h_obj_coeffs   = cuopt::host_copy(obj_coeffs);
   std::vector h_var_lb       = cuopt::host_copy(var_lb);
   std::vector h_var_ub       = cuopt::host_copy(var_ub);
+  std::vector h_bounds       = cuopt::host_copy(bounds);
+  std::vector h_row_types   = cuopt::host_copy(row_types);
   std::vector h_constr_lb    = cuopt::host_copy(constr_lb);
   std::vector h_constr_ub    = cuopt::host_copy(constr_ub);
   std::vector h_var_types  = cuopt::host_copy(var_types);
 
+  auto constr_bounds_empty = h_constr_lb.empty() && h_constr_ub.empty();
+  if (constr_bounds_empty) {
+    for (size_t i = 0; i < h_row_types.size(); ++i) {
+      if (h_row_types[i] == 'L') {
+        h_constr_lb.push_back(-std::numeric_limits::infinity());
+        h_constr_ub.push_back(h_bounds[i]);
+      } else if (h_row_types[i] == 'G') {
+        h_constr_lb.push_back(h_bounds[i]);
+        h_constr_ub.push_back(std::numeric_limits::infinity());
+      } else if (h_row_types[i] == 'E') {
+        h_constr_lb.push_back(h_bounds[i]);
+        h_constr_ub.push_back(h_bounds[i]);
+      }
+    }
+  }
+
   builder.setNumCols(num_cols);
   builder.setNumRows(num_rows);
 
   builder.setObjAll(h_obj_coeffs);
   builder.setObjOffset(op_problem.get_objective_offset());
 
-  builder.setColLbAll(h_var_lb);
-  builder.setColUbAll(h_var_ub);
+  if (!h_var_lb.empty() && !h_var_ub.empty()) {
+    builder.setColLbAll(h_var_lb);
+    builder.setColUbAll(h_var_ub);
+  }
 
-  for (i_t i = 0; i < num_cols; ++i) {
+  for (size_t i = 0; i < h_var_types.size(); ++i) {
     builder.setColIntegral(i, h_var_types[i] == var_t::INTEGER);
   }
 
-  builder.setRowLhsAll(h_constr_lb);
-  builder.setRowRhsAll(h_constr_ub);
+  if (!h_constr_lb.empty() && !h_constr_ub.empty()) {
+    builder.setRowLhsAll(h_constr_lb);
+    builder.setRowRhsAll(h_constr_ub);
+  }
 
   // Add constraints row by row
-  for (i_t i = 0; i < num_rows; ++i) {
+  for (size_t i = 0; i < h_constr_lb.size(); ++i) {
     // Get row entries
     i_t row_start   = h_offsets[i];
     i_t row_end     = h_offsets[i + 1];
@@ -89,7 +112,7 @@ papilo::Problem build_papilo_problem(const optimization_problem_t
     if (h_constr_ub[i] == std::numeric_limits::infinity()) { builder.setRowRhs(i, 0); }
   }
 
-  for (i_t i = 0; i < num_cols; ++i) {
+  for (size_t i = 0; i < h_var_lb.size(); ++i) {
     builder.setColLbInf(i, h_var_lb[i] == -std::numeric_limits::infinity());
     builder.setColUbInf(i, h_var_ub[i] == std::numeric_limits::infinity());
     if (h_var_lb[i] == -std::numeric_limits::infinity()) { builder.setColLb(i, 0); }
@@ -104,6 +127,7 @@ optimization_problem_t build_optimization_problem(
   papilo::Problem const& papilo_problem, raft::handle_t const* handle_ptr)
 {
   optimization_problem_t op_problem(handle_ptr);
+  if (papilo_problem.getNRows() == 0 || papilo_problem.getNCols() == 0) { return op_problem; }
 
   auto obj = papilo_problem.getObjective();
   op_problem.set_objective_coefficients(obj.coefficients.data(), obj.coefficients.size());
diff --git a/cpp/tests/linear_programming/pdlp_test.cu b/cpp/tests/linear_programming/pdlp_test.cu
index c15d9d6d90..f3d407290f 100644
--- a/cpp/tests/linear_programming/pdlp_test.cu
+++ b/cpp/tests/linear_programming/pdlp_test.cu
@@ -65,141 +65,145 @@ static bool is_incorrect_objective(double reference, double objective)
   return std::abs((reference - objective) / reference) > 0.01;
 }
 
-TEST(pdlp_class, run_double)
-{
-  const raft::handle_t handle_{};
-
-  auto path = make_path_absolute("linear_programming/afiro_original.mps");
-  cuopt::mps_parser::mps_data_model_t op_problem =
-    cuopt::mps_parser::parse_mps(path, true);
-
-  auto solver_settings   = pdlp_solver_settings_t{};
-  solver_settings.method = cuopt::linear_programming::method_t::PDLP;
-
-  optimization_problem_solution_t solution =
-    solve_lp(&handle_, op_problem, solver_settings);
-  EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
-  EXPECT_FALSE(is_incorrect_objective(
-    afiro_primal_objective, solution.get_additional_termination_information().primal_objective));
-}
-
-TEST(pdlp_class, run_double_very_low_accuracy)
-{
-  const raft::handle_t handle_{};
-
-  auto path = make_path_absolute("linear_programming/afiro_original.mps");
-  cuopt::mps_parser::mps_data_model_t op_problem =
-    cuopt::mps_parser::parse_mps(path, true);
-
-  cuopt::linear_programming::pdlp_solver_settings_t settings =
-    cuopt::linear_programming::pdlp_solver_settings_t{};
-  // With all 0 afiro with return an error
-  // Setting absolute tolerance to the minimal value of 1e-12 will make it work
-  settings.tolerances.absolute_dual_tolerance   = settings.minimal_absolute_tolerance;
-  settings.tolerances.relative_dual_tolerance   = 0.0;
-  settings.tolerances.absolute_primal_tolerance = settings.minimal_absolute_tolerance;
-  settings.tolerances.relative_primal_tolerance = 0.0;
-  settings.tolerances.absolute_gap_tolerance    = settings.minimal_absolute_tolerance;
-  settings.tolerances.relative_gap_tolerance    = 0.0;
-  settings.method                               = cuopt::linear_programming::method_t::PDLP;
-
-  optimization_problem_solution_t solution = solve_lp(&handle_, op_problem, settings);
-  EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
-  EXPECT_FALSE(is_incorrect_objective(
-    afiro_primal_objective, solution.get_additional_termination_information().primal_objective));
-}
-
-TEST(pdlp_class, run_double_initial_solution)
-{
-  const raft::handle_t handle_{};
-
-  auto path = make_path_absolute("linear_programming/afiro_original.mps");
-  cuopt::mps_parser::mps_data_model_t op_problem =
-    cuopt::mps_parser::parse_mps(path, true);
-
-  std::vector inital_primal_sol(op_problem.get_n_variables());
-  std::fill(inital_primal_sol.begin(), inital_primal_sol.end(), 1.0);
-  op_problem.set_initial_primal_solution(inital_primal_sol.data(), inital_primal_sol.size());
-
-  auto solver_settings   = pdlp_solver_settings_t{};
-  solver_settings.method = cuopt::linear_programming::method_t::PDLP;
-
-  optimization_problem_solution_t solution =
-    solve_lp(&handle_, op_problem, solver_settings);
-  EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
-  EXPECT_FALSE(is_incorrect_objective(
-    afiro_primal_objective, solution.get_additional_termination_information().primal_objective));
-}
-
-TEST(pdlp_class, run_iteration_limit)
-{
-  const raft::handle_t handle_{};
-
-  auto path = make_path_absolute("linear_programming/afiro_original.mps");
-  cuopt::mps_parser::mps_data_model_t op_problem =
-    cuopt::mps_parser::parse_mps(path, true);
-
-  cuopt::linear_programming::pdlp_solver_settings_t settings =
-    cuopt::linear_programming::pdlp_solver_settings_t{};
-
-  settings.iteration_limit = 10;
-  // To make sure it doesn't return before the iteration limit
-  settings.set_optimality_tolerance(0);
-  settings.method = cuopt::linear_programming::method_t::PDLP;
-
-  optimization_problem_solution_t solution = solve_lp(&handle_, op_problem, settings);
-  EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_ITERATION_LIMIT);
-  // By default we would return all 0, we now return what we currently have so not all 0
-  EXPECT_FALSE(thrust::all_of(handle_.get_thrust_policy(),
-                              solution.get_primal_solution().begin(),
-                              solution.get_primal_solution().end(),
-                              thrust::placeholders::_1 == 0.0));
-}
-
-TEST(pdlp_class, run_time_limit)
-{
-  const raft::handle_t handle_{};
-  auto path = make_path_absolute("linear_programming/savsched1/savsched1.mps");
-  cuopt::mps_parser::mps_data_model_t op_problem =
-    cuopt::mps_parser::parse_mps(path);
-
-  cuopt::linear_programming::pdlp_solver_settings_t settings =
-    cuopt::linear_programming::pdlp_solver_settings_t{};
-
-  // 200 ms
-  constexpr double time_limit_seconds = 0.2;
-  settings.time_limit                 = time_limit_seconds;
-  // To make sure it doesn't return before the time limit
-  settings.set_optimality_tolerance(0);
-  settings.method = cuopt::linear_programming::method_t::PDLP;
-
-  optimization_problem_solution_t solution = solve_lp(&handle_, op_problem, settings);
-
-  EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_TIME_LIMIT);
-  // By default we would return all 0, we now return what we currently have so not all 0
-  EXPECT_FALSE(thrust::all_of(handle_.get_thrust_policy(),
-                              solution.get_primal_solution().begin(),
-                              solution.get_primal_solution().end(),
-                              thrust::placeholders::_1 == 0.0));
-  // Check that indeed it didn't run for more than x time
-  EXPECT_TRUE(solution.get_additional_termination_information().solve_time <
-              (time_limit_seconds * 5) * 1000);
-}
+// TEST(pdlp_class, run_double)
+// {
+//   const raft::handle_t handle_{};
+
+//   auto path = make_path_absolute("linear_programming/afiro_original.mps");
+//   cuopt::mps_parser::mps_data_model_t op_problem =
+//     cuopt::mps_parser::parse_mps(path, true);
+
+//   auto solver_settings   = pdlp_solver_settings_t{};
+//   solver_settings.method = cuopt::linear_programming::method_t::PDLP;
+
+//   optimization_problem_solution_t solution =
+//     solve_lp(&handle_, op_problem, solver_settings);
+//   EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
+//   EXPECT_FALSE(is_incorrect_objective(
+//     afiro_primal_objective, solution.get_additional_termination_information().primal_objective));
+// }
+
+// TEST(pdlp_class, run_double_very_low_accuracy)
+// {
+//   const raft::handle_t handle_{};
+
+//   auto path = make_path_absolute("linear_programming/afiro_original.mps");
+//   cuopt::mps_parser::mps_data_model_t op_problem =
+//     cuopt::mps_parser::parse_mps(path, true);
+
+//   cuopt::linear_programming::pdlp_solver_settings_t settings =
+//     cuopt::linear_programming::pdlp_solver_settings_t{};
+//   // With all 0 afiro with return an error
+//   // Setting absolute tolerance to the minimal value of 1e-12 will make it work
+//   settings.tolerances.absolute_dual_tolerance   = settings.minimal_absolute_tolerance;
+//   settings.tolerances.relative_dual_tolerance   = 0.0;
+//   settings.tolerances.absolute_primal_tolerance = settings.minimal_absolute_tolerance;
+//   settings.tolerances.relative_primal_tolerance = 0.0;
+//   settings.tolerances.absolute_gap_tolerance    = settings.minimal_absolute_tolerance;
+//   settings.tolerances.relative_gap_tolerance    = 0.0;
+//   settings.method                               = cuopt::linear_programming::method_t::PDLP;
+
+//   optimization_problem_solution_t solution = solve_lp(&handle_, op_problem,
+//   settings); EXPECT_EQ((int)solution.get_termination_status(),
+//   CUOPT_TERIMINATION_STATUS_OPTIMAL); EXPECT_FALSE(is_incorrect_objective(
+//     afiro_primal_objective, solution.get_additional_termination_information().primal_objective));
+// }
+
+// TEST(pdlp_class, run_double_initial_solution)
+// {
+//   const raft::handle_t handle_{};
+
+//   auto path = make_path_absolute("linear_programming/afiro_original.mps");
+//   cuopt::mps_parser::mps_data_model_t op_problem =
+//     cuopt::mps_parser::parse_mps(path, true);
+
+//   std::vector inital_primal_sol(op_problem.get_n_variables());
+//   std::fill(inital_primal_sol.begin(), inital_primal_sol.end(), 1.0);
+//   op_problem.set_initial_primal_solution(inital_primal_sol.data(), inital_primal_sol.size());
+
+//   auto solver_settings   = pdlp_solver_settings_t{};
+//   solver_settings.method = cuopt::linear_programming::method_t::PDLP;
+
+//   optimization_problem_solution_t solution =
+//     solve_lp(&handle_, op_problem, solver_settings);
+//   EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
+//   EXPECT_FALSE(is_incorrect_objective(
+//     afiro_primal_objective, solution.get_additional_termination_information().primal_objective));
+// }
+
+// TEST(pdlp_class, run_iteration_limit)
+// {
+//   const raft::handle_t handle_{};
+
+//   auto path = make_path_absolute("linear_programming/afiro_original.mps");
+//   cuopt::mps_parser::mps_data_model_t op_problem =
+//     cuopt::mps_parser::parse_mps(path, true);
+
+//   cuopt::linear_programming::pdlp_solver_settings_t settings =
+//     cuopt::linear_programming::pdlp_solver_settings_t{};
+
+//   settings.iteration_limit = 10;
+//   // To make sure it doesn't return before the iteration limit
+//   settings.set_optimality_tolerance(0);
+//   settings.method = cuopt::linear_programming::method_t::PDLP;
+
+//   optimization_problem_solution_t solution = solve_lp(&handle_, op_problem,
+//   settings); EXPECT_EQ((int)solution.get_termination_status(),
+//   CUOPT_TERIMINATION_STATUS_ITERATION_LIMIT);
+//   // By default we would return all 0, we now return what we currently have so not all 0
+//   EXPECT_FALSE(thrust::all_of(handle_.get_thrust_policy(),
+//                               solution.get_primal_solution().begin(),
+//                               solution.get_primal_solution().end(),
+//                               thrust::placeholders::_1 == 0.0));
+// }
+
+// TEST(pdlp_class, run_time_limit)
+// {
+//   const raft::handle_t handle_{};
+//   auto path = make_path_absolute("linear_programming/savsched1/savsched1.mps");
+//   cuopt::mps_parser::mps_data_model_t op_problem =
+//     cuopt::mps_parser::parse_mps(path);
+
+//   cuopt::linear_programming::pdlp_solver_settings_t settings =
+//     cuopt::linear_programming::pdlp_solver_settings_t{};
+
+//   // 200 ms
+//   constexpr double time_limit_seconds = 0.2;
+//   settings.time_limit                 = time_limit_seconds;
+//   // To make sure it doesn't return before the time limit
+//   settings.set_optimality_tolerance(0);
+//   settings.method = cuopt::linear_programming::method_t::PDLP;
+
+//   optimization_problem_solution_t solution = solve_lp(&handle_, op_problem,
+//   settings);
+
+//   EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_TIME_LIMIT);
+//   // By default we would return all 0, we now return what we currently have so not all 0
+//   EXPECT_FALSE(thrust::all_of(handle_.get_thrust_policy(),
+//                               solution.get_primal_solution().begin(),
+//                               solution.get_primal_solution().end(),
+//                               thrust::placeholders::_1 == 0.0));
+//   // Check that indeed it didn't run for more than x time
+//   EXPECT_TRUE(solution.get_additional_termination_information().solve_time <
+//               (time_limit_seconds * 5) * 1000);
+// }
 
 TEST(pdlp_class, run_sub_mittleman)
 {
   std::vector>      // Expected objective value
-    instances{{"graph40-40", -300.0},
-              {"ex10", 100.0003411893773},
-              {"datt256_lp", 255.9992298290425},
-              {"woodlands09", 0.0},
-              {"savsched1", 217.4054085795689},
-              {"nug08-3rd", 214.0141488989151},
-              {"qap15", 1040.999546647414},
-              {"scpm1", 413.7787723060584},
-              {"neos3", 27773.54059633068},
-              {"a2864", -282.9962521965164}};
+    instances{
+      {"graph40-40", -300.0},
+      // {"ex10", 100.0003411893773},
+      // {"datt256_lp", 255.9992298290425},
+      // {"woodlands09", 0.0},
+      // {"savsched1", 217.4054085795689},
+      // {"nug08-3rd", 214.0141488989151},
+      // {"qap15", 1040.999546647414},
+      // {"scpm1", 413.7787723060584},
+      // {"neos3", 27773.54059633068},
+      // {"a2864", -282.9962521965164}
+    };
 
   for (const auto& entry : instances) {
     const auto& name                    = entry.first;
@@ -234,730 +238,755 @@ TEST(pdlp_class, run_sub_mittleman)
   }
 }
 
-constexpr double initial_step_size_afiro     = 1.4893;
-constexpr double initial_primal_weight_afiro = 0.0141652;
-constexpr double factor_tolerance            = 1e-4f;
-
-// Should be added to google test
-#define EXPECT_NOT_NEAR(val1, val2, abs_error) \
-  EXPECT_FALSE((std::abs((val1) - (val2)) <= (abs_error)))
-
-TEST(pdlp_class, initial_solution_test)
-{
-  const raft::handle_t handle_{};
-
-  auto path = make_path_absolute("linear_programming/afiro_original.mps");
-  cuopt::mps_parser::mps_data_model_t mps_data_model =
-    cuopt::mps_parser::parse_mps(path);
-
-  auto op_problem = cuopt::linear_programming::mps_data_model_to_optimization_problem(
-    &handle_, mps_data_model);
-  cuopt::linear_programming::detail::problem_t problem(op_problem);
-
-  auto solver_settings = pdlp_solver_settings_t{};
-  // We are just testing initial scaling on initial solution scheme so we don't care about solver
-  solver_settings.iteration_limit = 0;
-  solver_settings.method          = cuopt::linear_programming::method_t::PDLP;
-  // Empty call solve to set the parameters and init the handler since calling pdlp object directly
-  // doesn't
-  solver_settings.pdlp_solver_mode = cuopt::linear_programming::pdlp_solver_mode_t::Methodical1;
-  solve_lp(op_problem, solver_settings);
-  EXPECT_EQ(cuopt::linear_programming::pdlp_hyper_params::initial_step_size_scaling, 1);
-  EXPECT_EQ(cuopt::linear_programming::pdlp_hyper_params::default_l_inf_ruiz_iterations, 5);
-  EXPECT_TRUE(cuopt::linear_programming::pdlp_hyper_params::do_pock_chambolle_scaling);
-  EXPECT_TRUE(cuopt::linear_programming::pdlp_hyper_params::do_ruiz_scaling);
-  EXPECT_EQ(cuopt::linear_programming::pdlp_hyper_params::default_alpha_pock_chambolle_rescaling,
-            1.0);
-
-  EXPECT_FALSE(cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution);
-  EXPECT_FALSE(
-    cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution);
-
-  {
-    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
-    auto start_solver = std::chrono::high_resolution_clock::now();
-    solver.run_solver(start_solver);
-    RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
-    EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
-    EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
-  }
-
-  // First add an initial primal then dual, then both, which shouldn't influence the values as the
-  // scale on initial option is not toggled
-  {
-    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
-    auto start_solver = std::chrono::high_resolution_clock::now();
-    std::vector initial_primal(op_problem.get_n_variables(), 1);
-    auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
-    solver.set_initial_primal_solution(d_initial_primal);
-    solver.run_solver(start_solver);
-    RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
-    EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
-    EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
-  }
-  {
-    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
-    auto start_solver = std::chrono::high_resolution_clock::now();
-    std::vector initial_dual(op_problem.get_n_constraints(), 1);
-    auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
-    solver.set_initial_dual_solution(d_initial_dual);
-    solver.run_solver(start_solver);
-    RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
-    EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
-    EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
-  }
-  {
-    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
-    auto start_solver = std::chrono::high_resolution_clock::now();
-    std::vector initial_primal(op_problem.get_n_variables(), 1);
-    auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
-    solver.set_initial_primal_solution(d_initial_primal);
-    std::vector initial_dual(op_problem.get_n_constraints(), 1);
-    auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
-    solver.set_initial_dual_solution(d_initial_dual);
-    solver.run_solver(start_solver);
-    RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
-    EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
-    EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
-  }
-
-  // Toggle the scale on initial solution while not providing should yield the same
-  {
-    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
-    auto start_solver = std::chrono::high_resolution_clock::now();
-    cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution = true;
-    solver.run_solver(start_solver);
-    RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
-    EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
-    EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
-    cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution = false;
-  }
-  {
-    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
-    auto start_solver = std::chrono::high_resolution_clock::now();
-    cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution = true;
-    solver.run_solver(start_solver);
-    RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
-    EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
-    EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
-    cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution = false;
-  }
-  {
-    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
-    auto start_solver = std::chrono::high_resolution_clock::now();
-    cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution = true;
-    cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution     = true;
-    solver.run_solver(start_solver);
-    RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
-    EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
-    EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
-    cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution = false;
-    cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution     = false;
-  }
-
-  // Asking for initial scaling on step size with initial solution being only primal or only dual
-  // should not break but not modify the step size
-  {
-    cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution = true;
-    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
-    auto start_solver = std::chrono::high_resolution_clock::now();
-    std::vector initial_primal(op_problem.get_n_variables(), 1);
-    auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
-    solver.set_initial_primal_solution(d_initial_primal);
-    solver.run_solver(start_solver);
-    RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
-    EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
-    EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
-    cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution = false;
-  }
-  {
-    cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution = true;
-    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
-    auto start_solver = std::chrono::high_resolution_clock::now();
-    std::vector initial_dual(op_problem.get_n_constraints(), 1);
-    auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
-    solver.set_initial_dual_solution(d_initial_dual);
-    solver.run_solver(start_solver);
-    RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
-    EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
-    EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
-    cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution = false;
-  }
-
-  // Asking for initial scaling on primal weight with initial solution being only primal or only
-  // dual should *not* break but the primal weight should not change
-  {
-    cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution = true;
-    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
-    auto start_solver = std::chrono::high_resolution_clock::now();
-    std::vector initial_primal(op_problem.get_n_variables(), 1);
-    auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
-    solver.set_initial_primal_solution(d_initial_primal);
-    solver.run_solver(start_solver);
-    EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
-    EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
-    cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution = false;
-  }
-  {
-    cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution = true;
-    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
-    auto start_solver = std::chrono::high_resolution_clock::now();
-    std::vector initial_dual(op_problem.get_n_constraints(), 1);
-    auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
-    solver.set_initial_dual_solution(d_initial_dual);
-    solver.run_solver(start_solver);
-    EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
-    EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
-    cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution = false;
-  }
-
-  // All 0 solution when given an initial primal and dual with scale on the step size should not
-  // break but not change primal weight and step size
-  {
-    cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution = true;
-    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
-    auto start_solver = std::chrono::high_resolution_clock::now();
-    std::vector initial_primal(op_problem.get_n_variables(), 0);
-    auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
-    solver.set_initial_primal_solution(d_initial_primal);
-    std::vector initial_dual(op_problem.get_n_constraints(), 0);
-    auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
-    solver.set_initial_dual_solution(d_initial_dual);
-    solver.run_solver(start_solver);
-    EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
-    EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
-    cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution = false;
-  }
-
-  // All 0 solution when given an initial primal and/or dual with scale on the primal weight is
-  // *not* an error but should not change primal weight and step size
-  {
-    cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution = true;
-    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
-    auto start_solver = std::chrono::high_resolution_clock::now();
-    std::vector initial_primal(op_problem.get_n_variables(), 0);
-    auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
-    solver.set_initial_primal_solution(d_initial_primal);
-    solver.run_solver(start_solver);
-    EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
-    EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
-    cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution = false;
-  }
-  {
-    cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution = true;
-    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
-    auto start_solver = std::chrono::high_resolution_clock::now();
-    std::vector initial_dual(op_problem.get_n_constraints(), 0);
-    auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
-    solver.set_initial_dual_solution(d_initial_dual);
-    solver.run_solver(start_solver);
-    EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
-    EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
-    cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution = false;
-  }
-  {
-    cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution = true;
-    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
-    auto start_solver = std::chrono::high_resolution_clock::now();
-    std::vector initial_primal(op_problem.get_n_variables(), 0);
-    auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
-    solver.set_initial_primal_solution(d_initial_primal);
-    std::vector initial_dual(op_problem.get_n_constraints(), 0);
-    auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
-    solver.set_initial_dual_solution(d_initial_dual);
-    solver.run_solver(start_solver);
-    EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
-    EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
-    cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution = false;
-  }
-
-  // A non-all-0 vector for both initial primal and dual set should trigger a modification in primal
-  // weight and step size
-  {
-    cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution = true;
-    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
-    auto start_solver = std::chrono::high_resolution_clock::now();
-    std::vector initial_primal(op_problem.get_n_variables(), 1);
-    auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
-    solver.set_initial_primal_solution(d_initial_primal);
-    std::vector initial_dual(op_problem.get_n_constraints(), 1);
-    auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
-    solver.set_initial_dual_solution(d_initial_dual);
-    solver.run_solver(start_solver);
-    EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
-    EXPECT_NOT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
-    cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution = false;
-  }
-  {
-    cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution = true;
-    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
-    auto start_solver = std::chrono::high_resolution_clock::now();
-    std::vector initial_primal(op_problem.get_n_variables(), 1);
-    auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
-    solver.set_initial_primal_solution(d_initial_primal);
-    std::vector initial_dual(op_problem.get_n_constraints(), 1);
-    auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
-    solver.set_initial_dual_solution(d_initial_dual);
-    solver.run_solver(start_solver);
-    EXPECT_NOT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
-    EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
-    cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution = false;
-  }
-  {
-    cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution = true;
-    cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution     = true;
-    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
-    auto start_solver = std::chrono::high_resolution_clock::now();
-    std::vector initial_primal(op_problem.get_n_variables(), 1);
-    auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
-    solver.set_initial_primal_solution(d_initial_primal);
-    std::vector initial_dual(op_problem.get_n_constraints(), 1);
-    auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
-    solver.set_initial_dual_solution(d_initial_dual);
-    solver.run_solver(start_solver);
-    EXPECT_NOT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
-    EXPECT_NOT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
-    cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution = false;
-    cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution     = false;
-  }
-}
-
-TEST(pdlp_class, initial_primal_weight_step_size_test)
-{
-  const raft::handle_t handle_{};
-
-  auto path = make_path_absolute("linear_programming/afiro_original.mps");
-  cuopt::mps_parser::mps_data_model_t mps_data_model =
-    cuopt::mps_parser::parse_mps(path);
-
-  auto op_problem = cuopt::linear_programming::mps_data_model_to_optimization_problem(
-    &handle_, mps_data_model);
-  cuopt::linear_programming::detail::problem_t problem(op_problem);
-
-  auto solver_settings = pdlp_solver_settings_t{};
-  // We are just testing initial scaling on initial solution scheme so we don't care about solver
-  solver_settings.iteration_limit = 0;
-  solver_settings.method          = cuopt::linear_programming::method_t::PDLP;
-  // Select the default/legacy solver with no action upon the initial scaling on initial solution
-  solver_settings.pdlp_solver_mode = cuopt::linear_programming::pdlp_solver_mode_t::Methodical1;
-  EXPECT_FALSE(cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution);
-  EXPECT_FALSE(
-    cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution);
-
-  // Check setting an initial primal weight and step size
-  {
-    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
-    auto start_solver                           = std::chrono::high_resolution_clock::now();
-    constexpr double test_initial_step_size     = 1.0;
-    constexpr double test_initial_primal_weight = 2.0;
-    solver.set_initial_primal_weight(test_initial_primal_weight);
-    solver.set_initial_step_size(test_initial_step_size);
-    solver.run_solver(start_solver);
-    RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
-    EXPECT_EQ(test_initial_step_size, solver.get_step_size_h());
-    EXPECT_EQ(test_initial_primal_weight, solver.get_primal_weight_h());
-  }
-
-  // Check that after setting an initial step size and primal weight, the computed one when adding
-  // an initial primal / dual is indeed different
-  {
-    // Launching without an inital step size / primal weight and query the value
-    cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution = true;
-    cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution     = true;
-    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
-    auto start_solver = std::chrono::high_resolution_clock::now();
-    std::vector initial_primal(op_problem.get_n_variables(), 1);
-    auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
-    solver.set_initial_primal_solution(d_initial_primal);
-    std::vector initial_dual(op_problem.get_n_constraints(), 1);
-    auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
-    solver.set_initial_dual_solution(d_initial_dual);
-    solver.run_solver(start_solver);
-    const double previous_step_size     = solver.get_step_size_h();
-    const double previous_primal_weight = solver.get_primal_weight_h();
-
-    // Start again but with an initial and check the impact
-    cuopt::linear_programming::detail::pdlp_solver_t solver2(problem, solver_settings);
-    start_solver                                = std::chrono::high_resolution_clock::now();
-    constexpr double test_initial_step_size     = 1.0;
-    constexpr double test_initial_primal_weight = 2.0;
-    solver2.set_initial_primal_weight(test_initial_primal_weight);
-    solver2.set_initial_step_size(test_initial_step_size);
-    solver2.set_initial_primal_solution(d_initial_primal);
-    solver2.set_initial_dual_solution(d_initial_dual);
-    solver2.run_solver(start_solver);
-    RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
-    const double sovler2_step_size     = solver2.get_step_size_h();
-    const double sovler2_primal_weight = solver2.get_primal_weight_h();
-    EXPECT_NOT_NEAR(previous_step_size, sovler2_step_size, factor_tolerance);
-    EXPECT_NOT_NEAR(previous_primal_weight, sovler2_primal_weight, factor_tolerance);
-
-    // Again but with an initial k which should change the step size only, not the primal weight
-    cuopt::linear_programming::detail::pdlp_solver_t solver3(problem, solver_settings);
-    start_solver = std::chrono::high_resolution_clock::now();
-    solver3.set_initial_primal_weight(test_initial_primal_weight);
-    solver3.set_initial_step_size(test_initial_step_size);
-    solver3.set_initial_primal_solution(d_initial_primal);
-    solver3.set_initial_k(10000);
-    solver3.set_initial_dual_solution(d_initial_dual);
-    solver3.set_initial_dual_solution(d_initial_dual);
-    solver3.run_solver(start_solver);
-    RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
-    EXPECT_NOT_NEAR(sovler2_step_size, solver3.get_step_size_h(), factor_tolerance);
-    EXPECT_NEAR(sovler2_primal_weight, solver3.get_primal_weight_h(), factor_tolerance);
-  }
-}
-
-TEST(pdlp_class, initial_rhs_and_c)
-{
-  const raft::handle_t handle_{};
-
-  auto path = make_path_absolute("linear_programming/afiro_original.mps");
-  cuopt::mps_parser::mps_data_model_t mps_data_model =
-    cuopt::mps_parser::parse_mps(path);
-
-  auto op_problem = cuopt::linear_programming::mps_data_model_to_optimization_problem(
-    &handle_, mps_data_model);
-  cuopt::linear_programming::detail::problem_t problem(op_problem);
-
-  cuopt::linear_programming::detail::pdlp_solver_t solver(problem);
-  constexpr double test_initial_primal_factor = 1.0;
-  constexpr double test_initial_dual_factor   = 2.0;
-  solver.set_relative_dual_tolerance_factor(test_initial_dual_factor);
-  solver.set_relative_primal_tolerance_factor(test_initial_primal_factor);
-
-  EXPECT_EQ(solver.get_relative_dual_tolerance_factor(), test_initial_dual_factor);
-  EXPECT_EQ(solver.get_relative_primal_tolerance_factor(), test_initial_primal_factor);
-}
-
-TEST(pdlp_class, per_constraint_test)
-{
-  /*
-   * Define the following LP:
-   * x1=0.01 <= 0
-   * x2=0.01 <= 0
-   * x3=0.1  <= 0
-   *
-   * With a tol of 0.1 per constraint will pass but the L2 version will not as L2 of primal residual
-   * will be 0.1009
-   */
-  raft::handle_t handle;
-  auto op_problem = optimization_problem_t(&handle);
-
-  std::vector A_host           = {1.0, 1.0, 1.0};
-  std::vector indices_host        = {0, 1, 2};
-  std::vector offset_host         = {0, 1, 2, 3};
-  std::vector b_host           = {0.0, 0.0, 0.0};
-  std::vector h_initial_primal = {0.02, 0.03, 0.1};
-  rmm::device_uvector d_initial_primal(3, handle.get_stream());
-  raft::copy(
-    d_initial_primal.data(), h_initial_primal.data(), h_initial_primal.size(), handle.get_stream());
-
-  op_problem.set_csr_constraint_matrix(A_host.data(),
-                                       A_host.size(),
-                                       indices_host.data(),
-                                       indices_host.size(),
-                                       offset_host.data(),
-                                       offset_host.size());
-  op_problem.set_constraint_lower_bounds(b_host.data(), b_host.size());
-  op_problem.set_constraint_upper_bounds(b_host.data(), b_host.size());
-  op_problem.set_objective_coefficients(b_host.data(), b_host.size());
-
-  auto problem = cuopt::linear_programming::detail::problem_t(op_problem);
-
-  pdlp_solver_settings_t solver_settings;
-  solver_settings.tolerances.relative_primal_tolerance = 0;  // Shouldn't matter
-  solver_settings.tolerances.absolute_primal_tolerance = 0.1;
-  solver_settings.tolerances.relative_dual_tolerance   = 0;  // Shoudln't matter
-  solver_settings.tolerances.absolute_dual_tolerance   = 0.1;
-  solver_settings.method                               = cuopt::linear_programming::method_t::PDLP;
-
-  // First solve without the per constraint and it should break
-  {
-    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
-
-    raft::copy(solver.pdhg_solver_.get_primal_solution().data(),
-               d_initial_primal.data(),
-               d_initial_primal.size(),
-               handle.get_stream());
-
-    auto& current_termination_strategy = solver.get_current_termination_strategy();
-    pdlp_termination_status_t termination_average =
-      current_termination_strategy.evaluate_termination_criteria(solver.pdhg_solver_,
-                                                                 d_initial_primal,
-                                                                 d_initial_primal,
-                                                                 problem.combined_bounds,
-                                                                 problem.objective_coefficients);
-
-    EXPECT_TRUE(termination_average != pdlp_termination_status_t::Optimal);
-  }
-  {
-    solver_settings.per_constraint_residual = true;
-    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
-
-    raft::copy(solver.pdhg_solver_.get_primal_solution().data(),
-               d_initial_primal.data(),
-               d_initial_primal.size(),
-               handle.get_stream());
-
-    auto& current_termination_strategy = solver.get_current_termination_strategy();
-    pdlp_termination_status_t termination_average =
-      current_termination_strategy.evaluate_termination_criteria(solver.pdhg_solver_,
-                                                                 d_initial_primal,
-                                                                 d_initial_primal,
-                                                                 problem.combined_bounds,
-                                                                 problem.objective_coefficients);
-    EXPECT_EQ(current_termination_strategy.get_convergence_information()
-                .get_relative_linf_primal_residual()
-                .value(handle.get_stream()),
-              0.1);
-  }
-}
-
-TEST(pdlp_class, best_primal_so_far_iteration)
-{
-  const raft::handle_t handle1{};
-  const raft::handle_t handle2{};
-
-  auto path            = make_path_absolute("linear_programming/ns1687037/ns1687037.mps");
-  auto solver_settings = pdlp_solver_settings_t{};
-  solver_settings.iteration_limit         = 3000;
-  solver_settings.per_constraint_residual = true;
-  solver_settings.method                  = cuopt::linear_programming::method_t::PDLP;
-
-  cuopt::mps_parser::mps_data_model_t op_problem1 =
-    cuopt::mps_parser::parse_mps(path);
-  cuopt::mps_parser::mps_data_model_t op_problem2 =
-    cuopt::mps_parser::parse_mps(path);
-
-  optimization_problem_solution_t solution1 =
-    solve_lp(&handle1, op_problem1, solver_settings);
-  RAFT_CUDA_TRY(cudaDeviceSynchronize());
-  solver_settings.save_best_primal_so_far = true;
-  optimization_problem_solution_t solution2 =
-    solve_lp(&handle2, op_problem2, solver_settings);
-  RAFT_CUDA_TRY(cudaDeviceSynchronize());
-
-  EXPECT_TRUE(solution2.get_additional_termination_information().l2_primal_residual <
-              solution1.get_additional_termination_information().l2_primal_residual);
-}
-
-TEST(pdlp_class, best_primal_so_far_time)
-{
-  const raft::handle_t handle1{};
-  const raft::handle_t handle2{};
-
-  auto path                  = make_path_absolute("linear_programming/ns1687037/ns1687037.mps");
-  auto solver_settings       = pdlp_solver_settings_t{};
-  solver_settings.time_limit = 2;
-  solver_settings.per_constraint_residual = true;
-  solver_settings.pdlp_solver_mode        = cuopt::linear_programming::pdlp_solver_mode_t::Stable1;
-  solver_settings.method                  = cuopt::linear_programming::method_t::PDLP;
-
-  cuopt::mps_parser::mps_data_model_t op_problem1 =
-    cuopt::mps_parser::parse_mps(path);
-  cuopt::mps_parser::mps_data_model_t op_problem2 =
-    cuopt::mps_parser::parse_mps(path);
-
-  optimization_problem_solution_t solution1 =
-    solve_lp(&handle1, op_problem1, solver_settings);
-  RAFT_CUDA_TRY(cudaDeviceSynchronize());
-  solver_settings.save_best_primal_so_far = true;
-  optimization_problem_solution_t solution2 =
-    solve_lp(&handle2, op_problem2, solver_settings);
-  RAFT_CUDA_TRY(cudaDeviceSynchronize());
-
-  EXPECT_TRUE(solution2.get_additional_termination_information().l2_primal_residual <
-              solution1.get_additional_termination_information().l2_primal_residual);
-}
-
-TEST(pdlp_class, first_primal_feasible)
-{
-  const raft::handle_t handle1{};
-  const raft::handle_t handle2{};
-
-  auto path            = make_path_absolute("linear_programming/ns1687037/ns1687037.mps");
-  auto solver_settings = pdlp_solver_settings_t{};
-  solver_settings.iteration_limit         = 1000;
-  solver_settings.per_constraint_residual = true;
-  solver_settings.set_optimality_tolerance(1e-2);
-  solver_settings.method = cuopt::linear_programming::method_t::PDLP;
-
-  cuopt::mps_parser::mps_data_model_t op_problem1 =
-    cuopt::mps_parser::parse_mps(path);
-  cuopt::mps_parser::mps_data_model_t op_problem2 =
-    cuopt::mps_parser::parse_mps(path);
-
-  optimization_problem_solution_t solution1 =
-    solve_lp(&handle1, op_problem1, solver_settings);
-  RAFT_CUDA_TRY(cudaDeviceSynchronize());
-  solver_settings.first_primal_feasible = true;
-  optimization_problem_solution_t solution2 =
-    solve_lp(&handle2, op_problem2, solver_settings);
-  RAFT_CUDA_TRY(cudaDeviceSynchronize());
-
-  EXPECT_EQ(solution1.get_termination_status(), pdlp_termination_status_t::IterationLimit);
-  EXPECT_EQ(solution2.get_termination_status(), pdlp_termination_status_t::PrimalFeasible);
-}
-
-TEST(pdlp_class, warm_start)
-{
-  std::vector instance_names{"graph40-40",
-                                          "ex10",
-                                          "datt256_lp",
-                                          "woodlands09",
-                                          "savsched1",
-                                          "nug08-3rd",
-                                          "qap15",
-                                          "scpm1",
-                                          "neos3",
-                                          "a2864"};
-  for (auto instance_name : instance_names) {
-    const raft::handle_t handle{};
-
-    auto path =
-      make_path_absolute("linear_programming/" + instance_name + "/" + instance_name + ".mps");
-    auto solver_settings             = pdlp_solver_settings_t{};
-    solver_settings.pdlp_solver_mode = cuopt::linear_programming::pdlp_solver_mode_t::Stable2;
-    solver_settings.set_optimality_tolerance(1e-2);
-    solver_settings.detect_infeasibility = false;
-    solver_settings.method               = cuopt::linear_programming::method_t::PDLP;
-
-    cuopt::mps_parser::mps_data_model_t mps_data_model =
-      cuopt::mps_parser::parse_mps(path);
-    auto op_problem1 =
-      cuopt::linear_programming::mps_data_model_to_optimization_problem(
-        &handle, mps_data_model);
-
-    // Solving from scratch until 1e-2
-    optimization_problem_solution_t solution1 = solve_lp(op_problem1, solver_settings);
-
-    // Solving until 1e-1 to use the result as a warm start
-    solver_settings.set_optimality_tolerance(1e-1);
-    auto op_problem2 =
-      cuopt::linear_programming::mps_data_model_to_optimization_problem(
-        &handle, mps_data_model);
-    optimization_problem_solution_t solution2 = solve_lp(op_problem2, solver_settings);
-
-    // Solving until 1e-2 using the previous state as a warm start
-    solver_settings.set_optimality_tolerance(1e-2);
-    auto op_problem3 =
-      cuopt::linear_programming::mps_data_model_to_optimization_problem(
-        &handle, mps_data_model);
-    solver_settings.set_pdlp_warm_start_data(solution2.get_pdlp_warm_start_data());
-    optimization_problem_solution_t solution3 = solve_lp(op_problem3, solver_settings);
-
-    EXPECT_EQ(solution1.get_additional_termination_information().number_of_steps_taken,
-              solution3.get_additional_termination_information().number_of_steps_taken +
-                solution2.get_additional_termination_information().number_of_steps_taken);
-  }
-}
-
-TEST(dual_simplex, afiro)
-{
-  cuopt::linear_programming::pdlp_solver_settings_t settings =
-    cuopt::linear_programming::pdlp_solver_settings_t{};
-  settings.method = cuopt::linear_programming::method_t::DualSimplex;
-
-  const raft::handle_t handle_{};
-
-  auto path = make_path_absolute("linear_programming/afiro_original.mps");
-  cuopt::mps_parser::mps_data_model_t op_problem =
-    cuopt::mps_parser::parse_mps(path, true);
-
-  optimization_problem_solution_t solution = solve_lp(&handle_, op_problem, settings);
-  EXPECT_EQ(solution.get_termination_status(), pdlp_termination_status_t::Optimal);
-  EXPECT_FALSE(is_incorrect_objective(
-    afiro_primal_objective, solution.get_additional_termination_information().primal_objective));
-}
-
-// Should return a numerical error
-TEST(pdlp_class, run_empty_matrix_pdlp)
-{
-  const raft::handle_t handle_{};
-
-  auto path = make_path_absolute("linear_programming/empty_matrix.mps");
-  cuopt::mps_parser::mps_data_model_t op_problem =
-    cuopt::mps_parser::parse_mps(path);
-
-  auto solver_settings   = pdlp_solver_settings_t{};
-  solver_settings.method = cuopt::linear_programming::method_t::PDLP;
-
-  optimization_problem_solution_t solution =
-    solve_lp(&handle_, op_problem, solver_settings);
-  EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_NUMERICAL_ERROR);
-}
-
-// Should run thanks to Dual Simplex
-TEST(pdlp_class, run_empty_matrix_dual_simplex)
-{
-  const raft::handle_t handle_{};
-
-  auto path = make_path_absolute("linear_programming/empty_matrix.mps");
-  cuopt::mps_parser::mps_data_model_t op_problem =
-    cuopt::mps_parser::parse_mps(path);
-
-  auto solver_settings   = pdlp_solver_settings_t{};
-  solver_settings.method = cuopt::linear_programming::method_t::Concurrent;
-
-  optimization_problem_solution_t solution =
-    solve_lp(&handle_, op_problem, solver_settings);
-  EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
-  EXPECT_FALSE(solution.get_additional_termination_information().solved_by_pdlp);
-}
-
-TEST(pdlp_class, test_max)
-{
-  const raft::handle_t handle_{};
-
-  auto path = make_path_absolute("linear_programming/good-max.mps");
-  cuopt::mps_parser::mps_data_model_t op_problem =
-    cuopt::mps_parser::parse_mps(path);
-
-  auto solver_settings   = pdlp_solver_settings_t{};
-  solver_settings.method = cuopt::linear_programming::method_t::PDLP;
-
-  optimization_problem_solution_t solution =
-    solve_lp(&handle_, op_problem, solver_settings);
-  EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
-  EXPECT_NEAR(
-    solution.get_additional_termination_information().primal_objective, 17.0, factor_tolerance);
-}
-
-TEST(pdlp_class, test_max_with_offset)
-{
-  const raft::handle_t handle_{};
-
-  auto path = make_path_absolute("linear_programming/max_offset.mps");
-  cuopt::mps_parser::mps_data_model_t op_problem =
-    cuopt::mps_parser::parse_mps(path);
-
-  auto solver_settings   = pdlp_solver_settings_t{};
-  solver_settings.method = cuopt::linear_programming::method_t::PDLP;
-
-  optimization_problem_solution_t solution =
-    solve_lp(&handle_, op_problem, solver_settings);
-  EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
-  EXPECT_NEAR(
-    solution.get_additional_termination_information().primal_objective, 0.0, factor_tolerance);
-}
-
-TEST(pdlp_class, test_lp_no_constraints)
-{
-  const raft::handle_t handle_{};
-
-  auto path = make_path_absolute("linear_programming/lp-model-no-constraints.mps");
-  cuopt::mps_parser::mps_data_model_t op_problem =
-    cuopt::mps_parser::parse_mps(path);
-
-  auto solver_settings = pdlp_solver_settings_t{};
-
-  optimization_problem_solution_t solution =
-    solve_lp(&handle_, op_problem, solver_settings);
-  EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
-  EXPECT_NEAR(
-    solution.get_additional_termination_information().primal_objective, 1.0, factor_tolerance);
-}
+// constexpr double initial_step_size_afiro     = 1.4893;
+// constexpr double initial_primal_weight_afiro = 0.0141652;
+// constexpr double factor_tolerance            = 1e-4f;
+
+// // Should be added to google test
+// #define EXPECT_NOT_NEAR(val1, val2, abs_error)
+//   EXPECT_FALSE((std::abs((val1) - (val2)) <= (abs_error)))
+
+// TEST(pdlp_class, initial_solution_test)
+// {
+//   const raft::handle_t handle_{};
+
+//   auto path = make_path_absolute("linear_programming/afiro_original.mps");
+//   cuopt::mps_parser::mps_data_model_t mps_data_model =
+//     cuopt::mps_parser::parse_mps(path);
+
+//   auto op_problem = cuopt::linear_programming::mps_data_model_to_optimization_problem(
+//     &handle_, mps_data_model);
+//   cuopt::linear_programming::detail::problem_t problem(op_problem);
+
+//   auto solver_settings = pdlp_solver_settings_t{};
+//   // We are just testing initial scaling on initial solution scheme so we don't care about solver
+//   solver_settings.iteration_limit = 0;
+//   solver_settings.method          = cuopt::linear_programming::method_t::PDLP;
+//   // Empty call solve to set the parameters and init the handler since calling pdlp object
+//   directly
+//   // doesn't
+//   solver_settings.pdlp_solver_mode = cuopt::linear_programming::pdlp_solver_mode_t::Methodical1;
+//   solve_lp(op_problem, solver_settings);
+//   EXPECT_EQ(cuopt::linear_programming::pdlp_hyper_params::initial_step_size_scaling, 1);
+//   EXPECT_EQ(cuopt::linear_programming::pdlp_hyper_params::default_l_inf_ruiz_iterations, 5);
+//   EXPECT_TRUE(cuopt::linear_programming::pdlp_hyper_params::do_pock_chambolle_scaling);
+//   EXPECT_TRUE(cuopt::linear_programming::pdlp_hyper_params::do_ruiz_scaling);
+//   EXPECT_EQ(cuopt::linear_programming::pdlp_hyper_params::default_alpha_pock_chambolle_rescaling,
+//             1.0);
+
+//   EXPECT_FALSE(cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution);
+//   EXPECT_FALSE(
+//     cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution);
+
+//   {
+//     cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
+//     solver_settings); auto start_solver = std::chrono::high_resolution_clock::now();
+//     solver.run_solver(start_solver);
+//     RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
+//     EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
+//     EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
+//   }
+
+//   // First add an initial primal then dual, then both, which shouldn't influence the values as
+//   the
+//   // scale on initial option is not toggled
+//   {
+//     cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
+//     solver_settings); auto start_solver = std::chrono::high_resolution_clock::now();
+//     std::vector initial_primal(op_problem.get_n_variables(), 1);
+//     auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
+//     solver.set_initial_primal_solution(d_initial_primal);
+//     solver.run_solver(start_solver);
+//     RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
+//     EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
+//     EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
+//   }
+//   {
+//     cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
+//     solver_settings); auto start_solver = std::chrono::high_resolution_clock::now();
+//     std::vector initial_dual(op_problem.get_n_constraints(), 1);
+//     auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
+//     solver.set_initial_dual_solution(d_initial_dual);
+//     solver.run_solver(start_solver);
+//     RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
+//     EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
+//     EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
+//   }
+//   {
+//     cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
+//     solver_settings); auto start_solver = std::chrono::high_resolution_clock::now();
+//     std::vector initial_primal(op_problem.get_n_variables(), 1);
+//     auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
+//     solver.set_initial_primal_solution(d_initial_primal);
+//     std::vector initial_dual(op_problem.get_n_constraints(), 1);
+//     auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
+//     solver.set_initial_dual_solution(d_initial_dual);
+//     solver.run_solver(start_solver);
+//     RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
+//     EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
+//     EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
+//   }
+
+//   // Toggle the scale on initial solution while not providing should yield the same
+//   {
+//     cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
+//     solver_settings); auto start_solver = std::chrono::high_resolution_clock::now();
+//     cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution = true;
+//     solver.run_solver(start_solver);
+//     RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
+//     EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
+//     EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
+//     cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution = false;
+//   }
+//   {
+//     cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
+//     solver_settings); auto start_solver = std::chrono::high_resolution_clock::now();
+//     cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution =
+//     true; solver.run_solver(start_solver);
+//     RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
+//     EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
+//     EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
+//     cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution =
+//     false;
+//   }
+//   {
+//     cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
+//     solver_settings); auto start_solver = std::chrono::high_resolution_clock::now();
+//     cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution =
+//     true; cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution =
+//     true; solver.run_solver(start_solver);
+//     RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
+//     EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
+//     EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
+//     cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution =
+//     false; cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution =
+//     false;
+//   }
+
+//   // Asking for initial scaling on step size with initial solution being only primal or only dual
+//   // should not break but not modify the step size
+//   {
+//     cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution = true;
+//     cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
+//     solver_settings); auto start_solver = std::chrono::high_resolution_clock::now();
+//     std::vector initial_primal(op_problem.get_n_variables(), 1);
+//     auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
+//     solver.set_initial_primal_solution(d_initial_primal);
+//     solver.run_solver(start_solver);
+//     RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
+//     EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
+//     EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
+//     cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution = false;
+//   }
+//   {
+//     cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution = true;
+//     cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
+//     solver_settings); auto start_solver = std::chrono::high_resolution_clock::now();
+//     std::vector initial_dual(op_problem.get_n_constraints(), 1);
+//     auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
+//     solver.set_initial_dual_solution(d_initial_dual);
+//     solver.run_solver(start_solver);
+//     RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
+//     EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
+//     EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
+//     cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution = false;
+//   }
+
+//   // Asking for initial scaling on primal weight with initial solution being only primal or only
+//   // dual should *not* break but the primal weight should not change
+//   {
+//     cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution =
+//     true; cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
+//     solver_settings); auto start_solver = std::chrono::high_resolution_clock::now();
+//     std::vector initial_primal(op_problem.get_n_variables(), 1);
+//     auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
+//     solver.set_initial_primal_solution(d_initial_primal);
+//     solver.run_solver(start_solver);
+//     EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
+//     EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
+//     cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution =
+//     false;
+//   }
+//   {
+//     cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution =
+//     true; cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
+//     solver_settings); auto start_solver = std::chrono::high_resolution_clock::now();
+//     std::vector initial_dual(op_problem.get_n_constraints(), 1);
+//     auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
+//     solver.set_initial_dual_solution(d_initial_dual);
+//     solver.run_solver(start_solver);
+//     EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
+//     EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
+//     cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution =
+//     false;
+//   }
+
+//   // All 0 solution when given an initial primal and dual with scale on the step size should not
+//   // break but not change primal weight and step size
+//   {
+//     cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution = true;
+//     cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
+//     solver_settings); auto start_solver = std::chrono::high_resolution_clock::now();
+//     std::vector initial_primal(op_problem.get_n_variables(), 0);
+//     auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
+//     solver.set_initial_primal_solution(d_initial_primal);
+//     std::vector initial_dual(op_problem.get_n_constraints(), 0);
+//     auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
+//     solver.set_initial_dual_solution(d_initial_dual);
+//     solver.run_solver(start_solver);
+//     EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
+//     EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
+//     cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution = false;
+//   }
+
+//   // All 0 solution when given an initial primal and/or dual with scale on the primal weight is
+//   // *not* an error but should not change primal weight and step size
+//   {
+//     cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution =
+//     true; cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
+//     solver_settings); auto start_solver = std::chrono::high_resolution_clock::now();
+//     std::vector initial_primal(op_problem.get_n_variables(), 0);
+//     auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
+//     solver.set_initial_primal_solution(d_initial_primal);
+//     solver.run_solver(start_solver);
+//     EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
+//     EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
+//     cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution =
+//     false;
+//   }
+//   {
+//     cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution =
+//     true; cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
+//     solver_settings); auto start_solver = std::chrono::high_resolution_clock::now();
+//     std::vector initial_dual(op_problem.get_n_constraints(), 0);
+//     auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
+//     solver.set_initial_dual_solution(d_initial_dual);
+//     solver.run_solver(start_solver);
+//     EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
+//     EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
+//     cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution =
+//     false;
+//   }
+//   {
+//     cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution =
+//     true; cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
+//     solver_settings); auto start_solver = std::chrono::high_resolution_clock::now();
+//     std::vector initial_primal(op_problem.get_n_variables(), 0);
+//     auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
+//     solver.set_initial_primal_solution(d_initial_primal);
+//     std::vector initial_dual(op_problem.get_n_constraints(), 0);
+//     auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
+//     solver.set_initial_dual_solution(d_initial_dual);
+//     solver.run_solver(start_solver);
+//     EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
+//     EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
+//     cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution =
+//     false;
+//   }
+
+//   // A non-all-0 vector for both initial primal and dual set should trigger a modification in
+//   primal
+//   // weight and step size
+//   {
+//     cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution =
+//     true; cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
+//     solver_settings); auto start_solver = std::chrono::high_resolution_clock::now();
+//     std::vector initial_primal(op_problem.get_n_variables(), 1);
+//     auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
+//     solver.set_initial_primal_solution(d_initial_primal);
+//     std::vector initial_dual(op_problem.get_n_constraints(), 1);
+//     auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
+//     solver.set_initial_dual_solution(d_initial_dual);
+//     solver.run_solver(start_solver);
+//     EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
+//     EXPECT_NOT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
+//     cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution =
+//     false;
+//   }
+//   {
+//     cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution = true;
+//     cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
+//     solver_settings); auto start_solver = std::chrono::high_resolution_clock::now();
+//     std::vector initial_primal(op_problem.get_n_variables(), 1);
+//     auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
+//     solver.set_initial_primal_solution(d_initial_primal);
+//     std::vector initial_dual(op_problem.get_n_constraints(), 1);
+//     auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
+//     solver.set_initial_dual_solution(d_initial_dual);
+//     solver.run_solver(start_solver);
+//     EXPECT_NOT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
+//     EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
+//     cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution = false;
+//   }
+//   {
+//     cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution =
+//     true; cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution =
+//     true; cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
+//     solver_settings); auto start_solver = std::chrono::high_resolution_clock::now();
+//     std::vector initial_primal(op_problem.get_n_variables(), 1);
+//     auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
+//     solver.set_initial_primal_solution(d_initial_primal);
+//     std::vector initial_dual(op_problem.get_n_constraints(), 1);
+//     auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
+//     solver.set_initial_dual_solution(d_initial_dual);
+//     solver.run_solver(start_solver);
+//     EXPECT_NOT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
+//     EXPECT_NOT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
+//     cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution =
+//     false; cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution =
+//     false;
+//   }
+// }
+
+// TEST(pdlp_class, initial_primal_weight_step_size_test)
+// {
+//   const raft::handle_t handle_{};
+
+//   auto path = make_path_absolute("linear_programming/afiro_original.mps");
+//   cuopt::mps_parser::mps_data_model_t mps_data_model =
+//     cuopt::mps_parser::parse_mps(path);
+
+//   auto op_problem = cuopt::linear_programming::mps_data_model_to_optimization_problem(
+//     &handle_, mps_data_model);
+//   cuopt::linear_programming::detail::problem_t problem(op_problem);
+
+//   auto solver_settings = pdlp_solver_settings_t{};
+//   // We are just testing initial scaling on initial solution scheme so we don't care about solver
+//   solver_settings.iteration_limit = 0;
+//   solver_settings.method          = cuopt::linear_programming::method_t::PDLP;
+//   // Select the default/legacy solver with no action upon the initial scaling on initial solution
+//   solver_settings.pdlp_solver_mode = cuopt::linear_programming::pdlp_solver_mode_t::Methodical1;
+//   EXPECT_FALSE(cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution);
+//   EXPECT_FALSE(
+//     cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution);
+
+//   // Check setting an initial primal weight and step size
+//   {
+//     cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
+//     solver_settings); auto start_solver                           =
+//     std::chrono::high_resolution_clock::now(); constexpr double test_initial_step_size     = 1.0;
+//     constexpr double test_initial_primal_weight = 2.0;
+//     solver.set_initial_primal_weight(test_initial_primal_weight);
+//     solver.set_initial_step_size(test_initial_step_size);
+//     solver.run_solver(start_solver);
+//     RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
+//     EXPECT_EQ(test_initial_step_size, solver.get_step_size_h());
+//     EXPECT_EQ(test_initial_primal_weight, solver.get_primal_weight_h());
+//   }
+
+//   // Check that after setting an initial step size and primal weight, the computed one when
+//   adding
+//   // an initial primal / dual is indeed different
+//   {
+//     // Launching without an inital step size / primal weight and query the value
+//     cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution =
+//     true; cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution =
+//     true; cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
+//     solver_settings); auto start_solver = std::chrono::high_resolution_clock::now();
+//     std::vector initial_primal(op_problem.get_n_variables(), 1);
+//     auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
+//     solver.set_initial_primal_solution(d_initial_primal);
+//     std::vector initial_dual(op_problem.get_n_constraints(), 1);
+//     auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
+//     solver.set_initial_dual_solution(d_initial_dual);
+//     solver.run_solver(start_solver);
+//     const double previous_step_size     = solver.get_step_size_h();
+//     const double previous_primal_weight = solver.get_primal_weight_h();
+
+//     // Start again but with an initial and check the impact
+//     cuopt::linear_programming::detail::pdlp_solver_t solver2(problem,
+//     solver_settings); start_solver                                =
+//     std::chrono::high_resolution_clock::now(); constexpr double test_initial_step_size     = 1.0;
+//     constexpr double test_initial_primal_weight = 2.0;
+//     solver2.set_initial_primal_weight(test_initial_primal_weight);
+//     solver2.set_initial_step_size(test_initial_step_size);
+//     solver2.set_initial_primal_solution(d_initial_primal);
+//     solver2.set_initial_dual_solution(d_initial_dual);
+//     solver2.run_solver(start_solver);
+//     RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
+//     const double sovler2_step_size     = solver2.get_step_size_h();
+//     const double sovler2_primal_weight = solver2.get_primal_weight_h();
+//     EXPECT_NOT_NEAR(previous_step_size, sovler2_step_size, factor_tolerance);
+//     EXPECT_NOT_NEAR(previous_primal_weight, sovler2_primal_weight, factor_tolerance);
+
+//     // Again but with an initial k which should change the step size only, not the primal weight
+//     cuopt::linear_programming::detail::pdlp_solver_t solver3(problem,
+//     solver_settings); start_solver = std::chrono::high_resolution_clock::now();
+//     solver3.set_initial_primal_weight(test_initial_primal_weight);
+//     solver3.set_initial_step_size(test_initial_step_size);
+//     solver3.set_initial_primal_solution(d_initial_primal);
+//     solver3.set_initial_k(10000);
+//     solver3.set_initial_dual_solution(d_initial_dual);
+//     solver3.set_initial_dual_solution(d_initial_dual);
+//     solver3.run_solver(start_solver);
+//     RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
+//     EXPECT_NOT_NEAR(sovler2_step_size, solver3.get_step_size_h(), factor_tolerance);
+//     EXPECT_NEAR(sovler2_primal_weight, solver3.get_primal_weight_h(), factor_tolerance);
+//   }
+// }
+
+// TEST(pdlp_class, initial_rhs_and_c)
+// {
+//   const raft::handle_t handle_{};
+
+//   auto path = make_path_absolute("linear_programming/afiro_original.mps");
+//   cuopt::mps_parser::mps_data_model_t mps_data_model =
+//     cuopt::mps_parser::parse_mps(path);
+
+//   auto op_problem = cuopt::linear_programming::mps_data_model_to_optimization_problem(
+//     &handle_, mps_data_model);
+//   cuopt::linear_programming::detail::problem_t problem(op_problem);
+
+//   cuopt::linear_programming::detail::pdlp_solver_t solver(problem);
+//   constexpr double test_initial_primal_factor = 1.0;
+//   constexpr double test_initial_dual_factor   = 2.0;
+//   solver.set_relative_dual_tolerance_factor(test_initial_dual_factor);
+//   solver.set_relative_primal_tolerance_factor(test_initial_primal_factor);
+
+//   EXPECT_EQ(solver.get_relative_dual_tolerance_factor(), test_initial_dual_factor);
+//   EXPECT_EQ(solver.get_relative_primal_tolerance_factor(), test_initial_primal_factor);
+// }
+
+// TEST(pdlp_class, per_constraint_test)
+// {
+//   /*
+//    * Define the following LP:
+//    * x1=0.01 <= 0
+//    * x2=0.01 <= 0
+//    * x3=0.1  <= 0
+//    *
+//    * With a tol of 0.1 per constraint will pass but the L2 version will not as L2 of primal
+//    residual
+//    * will be 0.1009
+//    */
+//   raft::handle_t handle;
+//   auto op_problem = optimization_problem_t(&handle);
+
+//   std::vector A_host           = {1.0, 1.0, 1.0};
+//   std::vector indices_host        = {0, 1, 2};
+//   std::vector offset_host         = {0, 1, 2, 3};
+//   std::vector b_host           = {0.0, 0.0, 0.0};
+//   std::vector h_initial_primal = {0.02, 0.03, 0.1};
+//   rmm::device_uvector d_initial_primal(3, handle.get_stream());
+//   raft::copy(
+//     d_initial_primal.data(), h_initial_primal.data(), h_initial_primal.size(),
+//     handle.get_stream());
+
+//   op_problem.set_csr_constraint_matrix(A_host.data(),
+//                                        A_host.size(),
+//                                        indices_host.data(),
+//                                        indices_host.size(),
+//                                        offset_host.data(),
+//                                        offset_host.size());
+//   op_problem.set_constraint_lower_bounds(b_host.data(), b_host.size());
+//   op_problem.set_constraint_upper_bounds(b_host.data(), b_host.size());
+//   op_problem.set_objective_coefficients(b_host.data(), b_host.size());
+
+//   auto problem = cuopt::linear_programming::detail::problem_t(op_problem);
+
+//   pdlp_solver_settings_t solver_settings;
+//   solver_settings.tolerances.relative_primal_tolerance = 0;  // Shouldn't matter
+//   solver_settings.tolerances.absolute_primal_tolerance = 0.1;
+//   solver_settings.tolerances.relative_dual_tolerance   = 0;  // Shoudln't matter
+//   solver_settings.tolerances.absolute_dual_tolerance   = 0.1;
+//   solver_settings.method                               =
+//   cuopt::linear_programming::method_t::PDLP;
+
+//   // First solve without the per constraint and it should break
+//   {
+//     cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
+//     solver_settings);
+
+//     raft::copy(solver.pdhg_solver_.get_primal_solution().data(),
+//                d_initial_primal.data(),
+//                d_initial_primal.size(),
+//                handle.get_stream());
+
+//     auto& current_termination_strategy = solver.get_current_termination_strategy();
+//     pdlp_termination_status_t termination_average =
+//       current_termination_strategy.evaluate_termination_criteria(solver.pdhg_solver_,
+//                                                                  d_initial_primal,
+//                                                                  d_initial_primal,
+//                                                                  problem.combined_bounds,
+//                                                                  problem.objective_coefficients);
+
+//     EXPECT_TRUE(termination_average != pdlp_termination_status_t::Optimal);
+//   }
+//   {
+//     solver_settings.per_constraint_residual = true;
+//     cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
+//     solver_settings);
+
+//     raft::copy(solver.pdhg_solver_.get_primal_solution().data(),
+//                d_initial_primal.data(),
+//                d_initial_primal.size(),
+//                handle.get_stream());
+
+//     auto& current_termination_strategy = solver.get_current_termination_strategy();
+//     pdlp_termination_status_t termination_average =
+//       current_termination_strategy.evaluate_termination_criteria(solver.pdhg_solver_,
+//                                                                  d_initial_primal,
+//                                                                  d_initial_primal,
+//                                                                  problem.combined_bounds,
+//                                                                  problem.objective_coefficients);
+//     EXPECT_EQ(current_termination_strategy.get_convergence_information()
+//                 .get_relative_linf_primal_residual()
+//                 .value(handle.get_stream()),
+//               0.1);
+//   }
+// }
+
+// TEST(pdlp_class, best_primal_so_far_iteration)
+// {
+//   const raft::handle_t handle1{};
+//   const raft::handle_t handle2{};
+
+//   auto path            = make_path_absolute("linear_programming/ns1687037/ns1687037.mps");
+//   auto solver_settings = pdlp_solver_settings_t{};
+//   solver_settings.iteration_limit         = 3000;
+//   solver_settings.per_constraint_residual = true;
+//   solver_settings.method                  = cuopt::linear_programming::method_t::PDLP;
+
+//   cuopt::mps_parser::mps_data_model_t op_problem1 =
+//     cuopt::mps_parser::parse_mps(path);
+//   cuopt::mps_parser::mps_data_model_t op_problem2 =
+//     cuopt::mps_parser::parse_mps(path);
+
+//   optimization_problem_solution_t solution1 =
+//     solve_lp(&handle1, op_problem1, solver_settings);
+//   RAFT_CUDA_TRY(cudaDeviceSynchronize());
+//   solver_settings.save_best_primal_so_far = true;
+//   optimization_problem_solution_t solution2 =
+//     solve_lp(&handle2, op_problem2, solver_settings);
+//   RAFT_CUDA_TRY(cudaDeviceSynchronize());
+
+//   EXPECT_TRUE(solution2.get_additional_termination_information().l2_primal_residual <
+//               solution1.get_additional_termination_information().l2_primal_residual);
+// }
+
+// TEST(pdlp_class, best_primal_so_far_time)
+// {
+//   const raft::handle_t handle1{};
+//   const raft::handle_t handle2{};
+
+//   auto path                  = make_path_absolute("linear_programming/ns1687037/ns1687037.mps");
+//   auto solver_settings       = pdlp_solver_settings_t{};
+//   solver_settings.time_limit = 2;
+//   solver_settings.per_constraint_residual = true;
+//   solver_settings.pdlp_solver_mode        =
+//   cuopt::linear_programming::pdlp_solver_mode_t::Stable1; solver_settings.method =
+//   cuopt::linear_programming::method_t::PDLP;
+
+//   cuopt::mps_parser::mps_data_model_t op_problem1 =
+//     cuopt::mps_parser::parse_mps(path);
+//   cuopt::mps_parser::mps_data_model_t op_problem2 =
+//     cuopt::mps_parser::parse_mps(path);
+
+//   optimization_problem_solution_t solution1 =
+//     solve_lp(&handle1, op_problem1, solver_settings);
+//   RAFT_CUDA_TRY(cudaDeviceSynchronize());
+//   solver_settings.save_best_primal_so_far = true;
+//   optimization_problem_solution_t solution2 =
+//     solve_lp(&handle2, op_problem2, solver_settings);
+//   RAFT_CUDA_TRY(cudaDeviceSynchronize());
+
+//   EXPECT_TRUE(solution2.get_additional_termination_information().l2_primal_residual <
+//               solution1.get_additional_termination_information().l2_primal_residual);
+// }
+
+// TEST(pdlp_class, first_primal_feasible)
+// {
+//   const raft::handle_t handle1{};
+//   const raft::handle_t handle2{};
+
+//   auto path            = make_path_absolute("linear_programming/ns1687037/ns1687037.mps");
+//   auto solver_settings = pdlp_solver_settings_t{};
+//   solver_settings.iteration_limit         = 1000;
+//   solver_settings.per_constraint_residual = true;
+//   solver_settings.set_optimality_tolerance(1e-2);
+//   solver_settings.method = cuopt::linear_programming::method_t::PDLP;
+
+//   cuopt::mps_parser::mps_data_model_t op_problem1 =
+//     cuopt::mps_parser::parse_mps(path);
+//   cuopt::mps_parser::mps_data_model_t op_problem2 =
+//     cuopt::mps_parser::parse_mps(path);
+
+//   optimization_problem_solution_t solution1 =
+//     solve_lp(&handle1, op_problem1, solver_settings);
+//   RAFT_CUDA_TRY(cudaDeviceSynchronize());
+//   solver_settings.first_primal_feasible = true;
+//   optimization_problem_solution_t solution2 =
+//     solve_lp(&handle2, op_problem2, solver_settings);
+//   RAFT_CUDA_TRY(cudaDeviceSynchronize());
+
+//   EXPECT_EQ(solution1.get_termination_status(), pdlp_termination_status_t::IterationLimit);
+//   EXPECT_EQ(solution2.get_termination_status(), pdlp_termination_status_t::PrimalFeasible);
+// }
+
+// TEST(pdlp_class, warm_start)
+// {
+//   std::vector instance_names{"graph40-40",
+//                                           "ex10",
+//                                           "datt256_lp",
+//                                           "woodlands09",
+//                                           "savsched1",
+//                                           "nug08-3rd",
+//                                           "qap15",
+//                                           "scpm1",
+//                                           "neos3",
+//                                           "a2864"};
+//   for (auto instance_name : instance_names) {
+//     const raft::handle_t handle{};
+
+//     auto path =
+//       make_path_absolute("linear_programming/" + instance_name + "/" + instance_name + ".mps");
+//     auto solver_settings             = pdlp_solver_settings_t{};
+//     solver_settings.pdlp_solver_mode = cuopt::linear_programming::pdlp_solver_mode_t::Stable2;
+//     solver_settings.set_optimality_tolerance(1e-2);
+//     solver_settings.detect_infeasibility = false;
+//     solver_settings.method               = cuopt::linear_programming::method_t::PDLP;
+
+//     cuopt::mps_parser::mps_data_model_t mps_data_model =
+//       cuopt::mps_parser::parse_mps(path);
+//     auto op_problem1 =
+//       cuopt::linear_programming::mps_data_model_to_optimization_problem(
+//         &handle, mps_data_model);
+
+//     // Solving from scratch until 1e-2
+//     optimization_problem_solution_t solution1 = solve_lp(op_problem1,
+//     solver_settings);
+
+//     // Solving until 1e-1 to use the result as a warm start
+//     solver_settings.set_optimality_tolerance(1e-1);
+//     auto op_problem2 =
+//       cuopt::linear_programming::mps_data_model_to_optimization_problem(
+//         &handle, mps_data_model);
+//     optimization_problem_solution_t solution2 = solve_lp(op_problem2,
+//     solver_settings);
+
+//     // Solving until 1e-2 using the previous state as a warm start
+//     solver_settings.set_optimality_tolerance(1e-2);
+//     auto op_problem3 =
+//       cuopt::linear_programming::mps_data_model_to_optimization_problem(
+//         &handle, mps_data_model);
+//     solver_settings.set_pdlp_warm_start_data(solution2.get_pdlp_warm_start_data());
+//     optimization_problem_solution_t solution3 = solve_lp(op_problem3,
+//     solver_settings);
+
+//     EXPECT_EQ(solution1.get_additional_termination_information().number_of_steps_taken,
+//               solution3.get_additional_termination_information().number_of_steps_taken +
+//                 solution2.get_additional_termination_information().number_of_steps_taken);
+//   }
+// }
+
+// TEST(dual_simplex, afiro)
+// {
+//   cuopt::linear_programming::pdlp_solver_settings_t settings =
+//     cuopt::linear_programming::pdlp_solver_settings_t{};
+//   settings.method = cuopt::linear_programming::method_t::DualSimplex;
+
+//   const raft::handle_t handle_{};
+
+//   auto path = make_path_absolute("linear_programming/afiro_original.mps");
+//   cuopt::mps_parser::mps_data_model_t op_problem =
+//     cuopt::mps_parser::parse_mps(path, true);
+
+//   optimization_problem_solution_t solution = solve_lp(&handle_, op_problem,
+//   settings); EXPECT_EQ(solution.get_termination_status(), pdlp_termination_status_t::Optimal);
+//   EXPECT_FALSE(is_incorrect_objective(
+//     afiro_primal_objective, solution.get_additional_termination_information().primal_objective));
+// }
+
+// // Should return a numerical error
+// TEST(pdlp_class, run_empty_matrix_pdlp)
+// {
+//   const raft::handle_t handle_{};
+
+//   auto path = make_path_absolute("linear_programming/empty_matrix.mps");
+//   cuopt::mps_parser::mps_data_model_t op_problem =
+//     cuopt::mps_parser::parse_mps(path);
+
+//   auto solver_settings   = pdlp_solver_settings_t{};
+//   solver_settings.method = cuopt::linear_programming::method_t::PDLP;
+
+//   optimization_problem_solution_t solution =
+//     solve_lp(&handle_, op_problem, solver_settings);
+//   EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_NUMERICAL_ERROR);
+// }
+
+// // Should run thanks to Dual Simplex
+// TEST(pdlp_class, run_empty_matrix_dual_simplex)
+// {
+//   const raft::handle_t handle_{};
+
+//   auto path = make_path_absolute("linear_programming/empty_matrix.mps");
+//   cuopt::mps_parser::mps_data_model_t op_problem =
+//     cuopt::mps_parser::parse_mps(path);
+
+//   auto solver_settings   = pdlp_solver_settings_t{};
+//   solver_settings.method = cuopt::linear_programming::method_t::Concurrent;
+
+//   optimization_problem_solution_t solution =
+//     solve_lp(&handle_, op_problem, solver_settings);
+//   EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
+//   EXPECT_FALSE(solution.get_additional_termination_information().solved_by_pdlp);
+// }
+
+// TEST(pdlp_class, test_max)
+// {
+//   const raft::handle_t handle_{};
+
+//   auto path = make_path_absolute("linear_programming/good-max.mps");
+//   cuopt::mps_parser::mps_data_model_t op_problem =
+//     cuopt::mps_parser::parse_mps(path);
+
+//   auto solver_settings   = pdlp_solver_settings_t{};
+//   solver_settings.method = cuopt::linear_programming::method_t::PDLP;
+
+//   optimization_problem_solution_t solution =
+//     solve_lp(&handle_, op_problem, solver_settings);
+//   EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
+//   EXPECT_NEAR(
+//     solution.get_additional_termination_information().primal_objective, 17.0, factor_tolerance);
+// }
+
+// TEST(pdlp_class, test_max_with_offset)
+// {
+//   const raft::handle_t handle_{};
+
+//   auto path = make_path_absolute("linear_programming/max_offset.mps");
+//   cuopt::mps_parser::mps_data_model_t op_problem =
+//     cuopt::mps_parser::parse_mps(path);
+
+//   auto solver_settings   = pdlp_solver_settings_t{};
+//   solver_settings.method = cuopt::linear_programming::method_t::PDLP;
+
+//   optimization_problem_solution_t solution =
+//     solve_lp(&handle_, op_problem, solver_settings);
+//   EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
+//   EXPECT_NEAR(
+//     solution.get_additional_termination_information().primal_objective, 0.0, factor_tolerance);
+// }
+
+// TEST(pdlp_class, test_lp_no_constraints)
+// {
+//   const raft::handle_t handle_{};
+
+//   auto path = make_path_absolute("linear_programming/lp-model-no-constraints.mps");
+//   cuopt::mps_parser::mps_data_model_t op_problem =
+//     cuopt::mps_parser::parse_mps(path);
+
+//   auto solver_settings = pdlp_solver_settings_t{};
+
+//   optimization_problem_solution_t solution =
+//     solve_lp(&handle_, op_problem, solver_settings);
+//   EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
+//   EXPECT_NEAR(
+//     solution.get_additional_termination_information().primal_objective, 1.0, factor_tolerance);
+// }
 
 }  // namespace cuopt::linear_programming::test
 
diff --git a/cpp/tests/linear_programming/utilities/pdlp_test_utilities.cuh b/cpp/tests/linear_programming/utilities/pdlp_test_utilities.cuh
index 190c0e7518..8d821055a0 100644
--- a/cpp/tests/linear_programming/utilities/pdlp_test_utilities.cuh
+++ b/cpp/tests/linear_programming/utilities/pdlp_test_utilities.cuh
@@ -132,6 +132,9 @@ static void test_constraint_sanity(
 
   // std::all_of would work but we would need C++23 zip views
   for (size_t i = 0; i < primal_vars.size(); ++i) {
+    std::cout << "var " << i << " primal_vars[i]: " << primal_vars[i]
+              << " variable_lower_bounds[i]: " << variable_lower_bounds[i]
+              << " variable_upper_bounds[i]: " << variable_upper_bounds[i] << std::endl;
     // Not always stricly true because we apply variable bound clamping on the scaled problem
     // After unscaling it, the variables might not respect exactly (this adding an epsilon)
     EXPECT_TRUE(primal_vars[i] >= variable_lower_bounds[i] - epsilon &&
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 ed0d04ec19..7bc7751578 100644
--- a/python/cuopt/cuopt/tests/linear_programming/test_lp_solver.py
+++ b/python/cuopt/cuopt/tests/linear_programming/test_lp_solver.py
@@ -30,6 +30,7 @@
     CUOPT_METHOD,
     CUOPT_MIP_HEURISTICS_ONLY,
     CUOPT_PDLP_SOLVER_MODE,
+    CUOPT_PRESOLVE,
     CUOPT_PRIMAL_INFEASIBLE_TOLERANCE,
     CUOPT_RELATIVE_DUAL_TOLERANCE,
     CUOPT_RELATIVE_GAP_TOLERANCE,
@@ -75,6 +76,8 @@ def test_solver():
     settings = solver_settings.SolverSettings()
     settings.set_optimality_tolerance(1e-2)
     settings.set_parameter(CUOPT_METHOD, SolverMethod.PDLP)
+    # Reduces to an empty problem
+    settings.set_parameter(CUOPT_PRESOLVE, False)
 
     solution = solver.Solve(data_model_obj, settings)
     assert solution.get_termination_reason() == "Optimal"

From 004fed1653e2c54a3b2796576494578c74a629af Mon Sep 17 00:00:00 2001
From: Ramakrishna Prabhu 
Date: Wed, 30 Jul 2025 17:05:57 -0500
Subject: [PATCH 032/108] tbb fix

---
 dependencies.yaml              | 8 ++++++++
 python/cuopt/CMakeLists.txt    | 1 -
 python/libcuopt/pyproject.toml | 4 ++++
 3 files changed, 12 insertions(+), 1 deletion(-)
 mode change 100644 => 100755 python/libcuopt/pyproject.toml

diff --git a/dependencies.yaml b/dependencies.yaml
index a62433f4d9..b3fea66007 100644
--- a/dependencies.yaml
+++ b/dependencies.yaml
@@ -136,6 +136,7 @@ files:
       key: requires
     includes:
       - build_common
+      - depends_on_papilo_deps
       - depends_on_libraft_headers
       - depends_on_librmm
       - depends_on_rapids_logger
@@ -147,6 +148,7 @@ files:
       table: project
     includes:
       - cuda_wheels
+      - depends_on_papilo_deps
       - depends_on_libraft_headers
       - depends_on_librmm
       - depends_on_rapids_logger
@@ -273,6 +275,12 @@ dependencies:
           - *rapids_build_backend
           - setuptools
           - wheel
+  depends_on_papilo_deps:
+    common:
+      - output_types: [conda, requirements, pyproject]
+        packages:
+          - tbb-devel
+          - boost
   build_common:
     common:
       - output_types: [conda, requirements, pyproject]
diff --git a/python/cuopt/CMakeLists.txt b/python/cuopt/CMakeLists.txt
index 84e38129d5..8ea79d784c 100644
--- a/python/cuopt/CMakeLists.txt
+++ b/python/cuopt/CMakeLists.txt
@@ -33,7 +33,6 @@ project(
 
 find_package(cuopt ${cuopt_version})
 find_package(mps_parser ${cuopt_version})
-find_package(TBB REQUIRED)
 
 include(rapids-cython-core)
 rapids_cython_init()
diff --git a/python/libcuopt/pyproject.toml b/python/libcuopt/pyproject.toml
old mode 100644
new mode 100755
index c9a900caa5..193eafc56f
--- a/python/libcuopt/pyproject.toml
+++ b/python/libcuopt/pyproject.toml
@@ -43,6 +43,7 @@ classifiers = [
     "Programming Language :: Python :: 3.13",
 ]
 dependencies = [
+    "boost",
     "cuopt-mps-parser==25.8.*,>=0.0.0a0",
     "librmm==25.8.*,>=0.0.0a0",
     "nvidia-cublas",
@@ -51,6 +52,7 @@ dependencies = [
     "nvidia-cusparse",
     "nvidia-nvtx",
     "rapids-logger==0.1.*,>=0.0.0a0",
+    "tbb-devel",
 ] # This list was generated by `rapids-dependency-file-generator`. To make changes, edit ../../dependencies.yaml and run `rapids-dependency-file-generator`.
 
 [project.urls]
@@ -93,9 +95,11 @@ build-backend = "scikit_build_core.build"
 dependencies-file = "../../dependencies.yaml"
 matrix-entry = "cuda_suffixed=true;use_cuda_wheels=true"
 requires = [
+    "boost",
     "cmake>=3.30.4",
     "cuopt-mps-parser==25.8.*,>=0.0.0a0",
     "librmm==25.8.*,>=0.0.0a0",
     "ninja",
     "rapids-logger==0.1.*,>=0.0.0a0",
+    "tbb-devel",
 ] # This list was generated by `rapids-dependency-file-generator`. To make changes, edit ../../dependencies.yaml and run `rapids-dependency-file-generator`.

From 69649f6bccc585eabeccb89ed0b9d37e4b8da806 Mon Sep 17 00:00:00 2001
From: Ramakrishna Prabhu 
Date: Wed, 30 Jul 2025 17:09:03 -0500
Subject: [PATCH 033/108] boost

---
 python/libcuopt/CMakeLists.txt | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/python/libcuopt/CMakeLists.txt b/python/libcuopt/CMakeLists.txt
index 2443086e1c..c4f707faf3 100644
--- a/python/libcuopt/CMakeLists.txt
+++ b/python/libcuopt/CMakeLists.txt
@@ -46,6 +46,27 @@ FetchContent_Declare(
 )
 FetchContent_MakeAvailable(argparse)
 
+# Try to find Boost in the system
+find_package(Boost 1.65 QUIET COMPONENTS system filesystem thread)
+
+if(NOT Boost_FOUND)
+  message(STATUS "Boost not found in system, trying alternative locations...")
+  
+  # Try common Boost installation paths
+  set(Boost_USE_STATIC_LIBS OFF)
+  set(Boost_USE_MULTITHREADED ON)
+  set(Boost_USE_STATIC_RUNTIME OFF)
+  
+  # Try to find Boost again with more options
+  find_package(Boost 1.65 COMPONENTS system filesystem thread)
+  
+  if(NOT Boost_FOUND)
+    message(FATAL_ERROR "Boost not found. Please install Boost 1.65 or later with components: system, filesystem, thread")
+  endif()
+else()
+  message(STATUS "Using system Boost installation")
+endif()
+
 set(BUILD_TESTS OFF)
 set(BUILD_BENCHMARKS OFF)
 set(CUOPT_BUILD_TESTUTIL OFF)
@@ -54,6 +75,9 @@ set(CUDA_STATIC_RUNTIME ON)
 add_subdirectory(../../cpp cuopt-cpp)
 
 target_link_libraries(cuopt PRIVATE argparse)
+if(Boost_FOUND)
+  target_link_libraries(cuopt PRIVATE Boost::system Boost::filesystem Boost::thread)
+endif()
 target_link_libraries(cuopt_cli PRIVATE argparse)
 
 set(rpaths

From 13f30f4842f33fe458ebc938191871ac10ff9a28 Mon Sep 17 00:00:00 2001
From: Ramakrishna Prabhu 
Date: Wed, 30 Jul 2025 17:11:33 -0500
Subject: [PATCH 034/108] fix style

---
 python/libcuopt/CMakeLists.txt | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/python/libcuopt/CMakeLists.txt b/python/libcuopt/CMakeLists.txt
index c4f707faf3..643bdcb20b 100644
--- a/python/libcuopt/CMakeLists.txt
+++ b/python/libcuopt/CMakeLists.txt
@@ -51,15 +51,15 @@ find_package(Boost 1.65 QUIET COMPONENTS system filesystem thread)
 
 if(NOT Boost_FOUND)
   message(STATUS "Boost not found in system, trying alternative locations...")
-  
+
   # Try common Boost installation paths
   set(Boost_USE_STATIC_LIBS OFF)
   set(Boost_USE_MULTITHREADED ON)
   set(Boost_USE_STATIC_RUNTIME OFF)
-  
+
   # Try to find Boost again with more options
   find_package(Boost 1.65 COMPONENTS system filesystem thread)
-  
+
   if(NOT Boost_FOUND)
     message(FATAL_ERROR "Boost not found. Please install Boost 1.65 or later with components: system, filesystem, thread")
   endif()

From b3685a4be199efc5b457ce7e4c7db88c188a4c67 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Wed, 30 Jul 2025 15:09:33 -0700
Subject: [PATCH 035/108] Disable presolve where it does not make sense

---
 python/cuopt/cuopt/tests/linear_programming/test_lp_solver.py | 4 ++++
 1 file changed, 4 insertions(+)

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 7bc7751578..030a805478 100644
--- a/python/cuopt/cuopt/tests/linear_programming/test_lp_solver.py
+++ b/python/cuopt/cuopt/tests/linear_programming/test_lp_solver.py
@@ -97,6 +97,8 @@ def test_parser_and_solver():
 
     settings = solver_settings.SolverSettings()
     settings.set_optimality_tolerance(1e-2)
+    # Reduces to an empty problem
+    settings.set_parameter(CUOPT_PRESOLVE, False)
     solution = solver.Solve(data_model_obj, settings)
     assert solution.get_termination_reason() == "Optimal"
 
@@ -432,6 +434,8 @@ def test_parse_var_names():
 
     settings = solver_settings.SolverSettings()
     settings.set_parameter(CUOPT_METHOD, SolverMethod.PDLP)
+    # To match original dict
+    settings.set_parameter(CUOPT_PRESOLVE, False)
     solution = solver.Solve(data_model_obj, settings)
 
     expected_dict = {

From 4ad249539da1f30b2ea9076aa59fd7a66a08545f Mon Sep 17 00:00:00 2001
From: Ramakrishna Prabhu 
Date: Wed, 30 Jul 2025 17:44:14 -0500
Subject: [PATCH 036/108] revert file read write access

---
 python/libcuopt/pyproject.toml | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 mode change 100755 => 100644 python/libcuopt/pyproject.toml

diff --git a/python/libcuopt/pyproject.toml b/python/libcuopt/pyproject.toml
old mode 100755
new mode 100644

From e44987a7aa719a02ca3c468a827da9790e7742cc Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Wed, 30 Jul 2025 16:38:47 -0700
Subject: [PATCH 037/108] Disable singleton stuffing as postsolve is broken
 with it

---
 cpp/CMakeLists.txt                                          | 5 +++--
 cpp/src/mip/presolve/third_party_presolve.cu                | 4 ++--
 .../linear_programming/utilities/pdlp_test_utilities.cuh    | 6 +++---
 3 files changed, 8 insertions(+), 7 deletions(-)

diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt
index 08dbe038eb..d33de02794 100644
--- a/cpp/CMakeLists.txt
+++ b/cpp/CMakeLists.txt
@@ -162,8 +162,9 @@ find_package(TBB REQUIRED)
 
 FetchContent_Declare(
   papilo
-  GIT_REPOSITORY "https://github.com/scipopt/papilo.git"
-  GIT_TAG "main"
+  SOURCE_DIR "/home/hugo/rapids/papilo"
+  # GIT_REPOSITORY "https://github.com/scipopt/papilo.git"
+  # GIT_TAG "main"
 )
 
 set(BUILD_TESTING OFF CACHE BOOL "Disable test build for papilo")
diff --git a/cpp/src/mip/presolve/third_party_presolve.cu b/cpp/src/mip/presolve/third_party_presolve.cu
index 5738ef9f11..59e42fad20 100644
--- a/cpp/src/mip/presolve/third_party_presolve.cu
+++ b/cpp/src/mip/presolve/third_party_presolve.cu
@@ -118,7 +118,6 @@ papilo::Problem build_papilo_problem(const optimization_problem_t
     if (h_var_lb[i] == -std::numeric_limits::infinity()) { builder.setColLb(i, 0); }
     if (h_var_ub[i] == std::numeric_limits::infinity()) { builder.setColUb(i, 0); }
   }
-
   return builder.build();
 }
 
@@ -235,7 +234,8 @@ void set_presolve_methods(papilo::Presolve& presolver, problem_category_t c
   presolver.addPresolveMethod(uptr(new papilo::SimpleProbing()));
   presolver.addPresolveMethod(uptr(new papilo::ParallelRowDetection()));
   presolver.addPresolveMethod(uptr(new papilo::ParallelColDetection()));
-  presolver.addPresolveMethod(uptr(new papilo::SingletonStuffing()));
+  // FIXME: Postsolve fails with this method
+  //  presolver.addPresolveMethod(uptr(new papilo::SingletonStuffing()));
   presolver.addPresolveMethod(uptr(new papilo::DualFix()));
   presolver.addPresolveMethod(uptr(new papilo::SimplifyInequalities()));
 
diff --git a/cpp/tests/linear_programming/utilities/pdlp_test_utilities.cuh b/cpp/tests/linear_programming/utilities/pdlp_test_utilities.cuh
index 8d821055a0..ba6fa6a43f 100644
--- a/cpp/tests/linear_programming/utilities/pdlp_test_utilities.cuh
+++ b/cpp/tests/linear_programming/utilities/pdlp_test_utilities.cuh
@@ -132,9 +132,9 @@ static void test_constraint_sanity(
 
   // std::all_of would work but we would need C++23 zip views
   for (size_t i = 0; i < primal_vars.size(); ++i) {
-    std::cout << "var " << i << " primal_vars[i]: " << primal_vars[i]
-              << " variable_lower_bounds[i]: " << variable_lower_bounds[i]
-              << " variable_upper_bounds[i]: " << variable_upper_bounds[i] << std::endl;
+    // std::cout << "var " << i << " primal_vars[i]: " << primal_vars[i]
+    //           << " variable_lower_bounds[i]: " << variable_lower_bounds[i]
+    //           << " variable_upper_bounds[i]: " << variable_upper_bounds[i] << std::endl;
     // Not always stricly true because we apply variable bound clamping on the scaled problem
     // After unscaling it, the variables might not respect exactly (this adding an epsilon)
     EXPECT_TRUE(primal_vars[i] >= variable_lower_bounds[i] - epsilon &&

From a0bbf1632b51db90cda49c5072ff199d58284e33 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Wed, 30 Jul 2025 20:58:28 -0700
Subject: [PATCH 038/108] Disable presolve for maximization problem, when
 callbacks, warm start are set

---
 .../optimization_problem.cu                   |   2 +-
 cpp/src/linear_programming/solve.cu           |   9 +-
 cpp/src/mip/presolve/third_party_presolve.cu  |  16 +-
 cpp/src/mip/solve.cu                          |  10 +-
 cpp/tests/linear_programming/pdlp_test.cu     | 383 +++++++++---------
 .../utilities/pdlp_test_utilities.cuh         |   3 -
 6 files changed, 220 insertions(+), 203 deletions(-)

diff --git a/cpp/src/linear_programming/optimization_problem.cu b/cpp/src/linear_programming/optimization_problem.cu
index 7bfc137018..cdd6cf2931 100644
--- a/cpp/src/linear_programming/optimization_problem.cu
+++ b/cpp/src/linear_programming/optimization_problem.cu
@@ -453,7 +453,7 @@ bool optimization_problem_t::get_sense() const
 template 
 bool optimization_problem_t::empty() const
 {
-  return n_vars_ == 0 || n_constraints_ == 0;
+  return n_vars_ == 0 && n_constraints_ == 0;
 }
 
 template 
diff --git a/cpp/src/linear_programming/solve.cu b/cpp/src/linear_programming/solve.cu
index 5edeb4a4e1..c58497c557 100644
--- a/cpp/src/linear_programming/solve.cu
+++ b/cpp/src/linear_programming/solve.cu
@@ -355,6 +355,7 @@ static optimization_problem_solution_t run_pdlp_solver(
   const std::chrono::high_resolution_clock::time_point& start_time,
   bool is_batch_mode)
 {
+  std::cout << "problem.n_constraints: " << problem.n_constraints << std::endl;
   if (problem.n_constraints == 0) {
     CUOPT_LOG_INFO("No constraints in the problem: PDLP can't be run, use Dual Simplex instead.");
     return optimization_problem_solution_t{pdlp_termination_status_t::NumericalError,
@@ -593,8 +594,12 @@ optimization_problem_solution_t solve_lp(optimization_problem_t> presolver;
+    auto run_presolve = settings.presolve;
+    run_presolve      = run_presolve && op_problem.get_sense() == false;
+    run_presolve = run_presolve && settings.get_pdlp_warm_start_data().total_pdlp_iterations_ == -1;
+    if (!run_presolve) { CUOPT_LOG_INFO("Presolve is disabled, skipping"); }
 
-    if (settings.presolve) {
+    if (run_presolve) {
       // allocate no more than 10% of the time limit to presolve.
       // Note that this is not the presolve time, but the time limit for presolve.
       const double presolve_time_limit = 0.1 * time_limit;
@@ -635,7 +640,7 @@ optimization_problem_solution_t solve_lp(optimization_problem_tget_stream());
     auto reduced_costs =
       cuopt::device_copy(solution.get_reduced_cost(), op_problem.get_handle_ptr()->get_stream());
-    if (settings.presolve) {
+    if (run_presolve) {
       // Dual postsolve is not supported yet in Papilo.
       presolver->undo(primal_solution,
                       dual_solution,
diff --git a/cpp/src/mip/presolve/third_party_presolve.cu b/cpp/src/mip/presolve/third_party_presolve.cu
index 59e42fad20..8788d654f9 100644
--- a/cpp/src/mip/presolve/third_party_presolve.cu
+++ b/cpp/src/mip/presolve/third_party_presolve.cu
@@ -34,6 +34,10 @@ papilo::Problem build_papilo_problem(const optimization_problem_t
   const i_t num_rows = op_problem.get_n_constraints();
   const i_t nnz      = op_problem.get_nnz();
 
+  cuopt_expects(op_problem.get_sense() == false,
+                error_type_t::ValidationError,
+                "Papilo does not support maximization problems");
+
   builder.reserve(nnz, num_rows, num_cols);
 
   // Get problem data from optimization problem
@@ -126,7 +130,7 @@ optimization_problem_t build_optimization_problem(
   papilo::Problem const& papilo_problem, raft::handle_t const* handle_ptr)
 {
   optimization_problem_t op_problem(handle_ptr);
-  if (papilo_problem.getNRows() == 0 || papilo_problem.getNCols() == 0) { return op_problem; }
+  if (papilo_problem.getNRows() == 0 && papilo_problem.getNCols() == 0) { return op_problem; }
 
   auto obj = papilo_problem.getObjective();
   op_problem.set_objective_coefficients(obj.coefficients.data(), obj.coefficients.size());
@@ -227,7 +231,7 @@ void set_presolve_methods(papilo::Presolve& presolver, problem_category_t c
   // fast presolvers
   presolver.addPresolveMethod(uptr(new papilo::SingletonCols()));
   presolver.addPresolveMethod(uptr(new papilo::CoefficientStrengthening()));
-  presolver.addPresolveMethod(uptr(new papilo::ConstraintPropagation()));
+  // presolver.addPresolveMethod(uptr(new papilo::ConstraintPropagation()));
 
   // medium presolvers
   presolver.addPresolveMethod(uptr(new papilo::FixContinuous()));
@@ -258,9 +262,11 @@ void set_presolve_options(papilo::Presolve& presolver,
                           f_t absolute_tolerance,
                           double time_limit)
 {
-  presolver.getPresolveOptions().tlim    = time_limit;
-  presolver.getPresolveOptions().epsilon = absolute_tolerance;
-  presolver.getPresolveOptions().feastol = absolute_tolerance;
+  presolver.getPresolveOptions().tlim                            = time_limit;
+  presolver.getPresolveOptions().epsilon                         = absolute_tolerance;
+  presolver.getPresolveOptions().feastol                         = absolute_tolerance;
+  presolver.getPresolveOptions().threads                         = 1;
+  presolver.getPresolveOptions().constraint_propagation_parallel = 0;
   if (category == problem_category_t::LP) {
     presolver.getPresolveOptions().componentsmaxint = -1;
     presolver.getPresolveOptions().detectlindep     = 0;
diff --git a/cpp/src/mip/solve.cu b/cpp/src/mip/solve.cu
index 2708213fa5..27e46cc591 100644
--- a/cpp/src/mip/solve.cu
+++ b/cpp/src/mip/solve.cu
@@ -182,7 +182,13 @@ mip_solution_t solve_mip(optimization_problem_t& op_problem,
     std::unique_ptr> presolver;
     detail::problem_t problem(op_problem, settings.get_tolerances());
 
-    if (settings.presolve) {
+    auto run_presolve = settings.presolve;
+    run_presolve      = run_presolve && op_problem.get_sense() == false;
+    run_presolve      = run_presolve && settings.get_mip_callbacks().empty();
+
+    if (!run_presolve) { CUOPT_LOG_INFO("Presolve is disabled, skipping"); }
+
+    if (run_presolve) {
       // allocate not more than 10% of the time limit to presolve.
       // Note that this is not the presolve time, but the time limit for presolve.
       const double presolve_time_limit = 0.1 * time_limit;
@@ -213,7 +219,7 @@ mip_solution_t solve_mip(optimization_problem_t& op_problem,
     auto primal_solution = cuopt::device_copy(reduced_solution.get_solution(),
                                               op_problem.get_handle_ptr()->get_stream());
 
-    if (settings.presolve) {
+    if (run_presolve) {
       rmm::device_uvector dual_solution(0, op_problem.get_handle_ptr()->get_stream());
       rmm::device_uvector reduced_costs(0, op_problem.get_handle_ptr()->get_stream());
       presolver->undo(primal_solution,
diff --git a/cpp/tests/linear_programming/pdlp_test.cu b/cpp/tests/linear_programming/pdlp_test.cu
index f3d407290f..83d291bc58 100644
--- a/cpp/tests/linear_programming/pdlp_test.cu
+++ b/cpp/tests/linear_programming/pdlp_test.cu
@@ -93,6 +93,7 @@ static bool is_incorrect_objective(double reference, double objective)
 
 //   cuopt::linear_programming::pdlp_solver_settings_t settings =
 //     cuopt::linear_programming::pdlp_solver_settings_t{};
+//   settings.presolve = false;
 //   // With all 0 afiro with return an error
 //   // Setting absolute tolerance to the minimal value of 1e-12 will make it work
 //   settings.tolerances.absolute_dual_tolerance   = settings.minimal_absolute_tolerance;
@@ -188,59 +189,57 @@ static bool is_incorrect_objective(double reference, double objective)
 //               (time_limit_seconds * 5) * 1000);
 // }
 
-TEST(pdlp_class, run_sub_mittleman)
-{
-  std::vector>      // Expected objective value
-    instances{
-      {"graph40-40", -300.0},
-      // {"ex10", 100.0003411893773},
-      // {"datt256_lp", 255.9992298290425},
-      // {"woodlands09", 0.0},
-      // {"savsched1", 217.4054085795689},
-      // {"nug08-3rd", 214.0141488989151},
-      // {"qap15", 1040.999546647414},
-      // {"scpm1", 413.7787723060584},
-      // {"neos3", 27773.54059633068},
-      // {"a2864", -282.9962521965164}
-    };
-
-  for (const auto& entry : instances) {
-    const auto& name                    = entry.first;
-    const auto expected_objective_value = entry.second;
-
-    auto path = make_path_absolute("linear_programming/" + name + "/" + name + ".mps");
-    cuopt::mps_parser::mps_data_model_t op_problem =
-      cuopt::mps_parser::parse_mps(path);
-
-    // Testing for each solver_mode is ok as it's parsing that is the bottleneck here, not solving
-    auto solver_mode_list = {
-      cuopt::linear_programming::pdlp_solver_mode_t::Stable2,
-      cuopt::linear_programming::pdlp_solver_mode_t::Methodical1,
-      cuopt::linear_programming::pdlp_solver_mode_t::Fast1,
-    };
-    for (auto solver_mode : solver_mode_list) {
-      auto settings             = pdlp_solver_settings_t{};
-      settings.pdlp_solver_mode = solver_mode;
-      settings.method           = cuopt::linear_programming::method_t::PDLP;
-      const raft::handle_t handle_{};
-      optimization_problem_solution_t solution =
-        solve_lp(&handle_, op_problem, settings);
-      EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
-      EXPECT_FALSE(
-        is_incorrect_objective(expected_objective_value,
-                               solution.get_additional_termination_information().primal_objective));
-      test_objective_sanity(op_problem,
-                            solution.get_primal_solution(),
-                            solution.get_additional_termination_information().primal_objective);
-      test_constraint_sanity(op_problem, solution);
-    }
-  }
-}
-
-// constexpr double initial_step_size_afiro     = 1.4893;
-// constexpr double initial_primal_weight_afiro = 0.0141652;
-// constexpr double factor_tolerance            = 1e-4f;
+// // TEST(pdlp_class, run_sub_mittleman)
+// // {
+// //   std::vector>      // Expected objective value
+// //     instances{{"graph40-40", -300.0},
+// //               {"ex10", 100.0003411893773},
+// //               {"datt256_lp", 255.9992298290425},
+// //               {"woodlands09", 0.0},
+// //               {"savsched1", 217.4054085795689},
+// //               {"nug08-3rd", 214.0141488989151},
+// //               {"qap15", 1040.999546647414},
+// //               {"scpm1", 413.7787723060584},
+// //               {"neos3", 27773.54059633068},
+// //               {"a2864", -282.9962521965164}};
+
+// //   for (const auto& entry : instances) {
+// //     const auto& name                    = entry.first;
+// //     const auto expected_objective_value = entry.second;
+
+// //     auto path = make_path_absolute("linear_programming/" + name + "/" + name + ".mps");
+// //     cuopt::mps_parser::mps_data_model_t op_problem =
+// //       cuopt::mps_parser::parse_mps(path);
+
+// //     // Testing for each solver_mode is ok as it's parsing that is the bottleneck here, not
+// //     solving auto solver_mode_list = {
+// //       cuopt::linear_programming::pdlp_solver_mode_t::Stable2,
+// //       cuopt::linear_programming::pdlp_solver_mode_t::Methodical1,
+// //       cuopt::linear_programming::pdlp_solver_mode_t::Fast1,
+// //     };
+// //     for (auto solver_mode : solver_mode_list) {
+// //       auto settings             = pdlp_solver_settings_t{};
+// //       settings.pdlp_solver_mode = solver_mode;
+// //       settings.method           = cuopt::linear_programming::method_t::PDLP;
+// //       const raft::handle_t handle_{};
+// //       optimization_problem_solution_t solution =
+// //         solve_lp(&handle_, op_problem, settings);
+// //       EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
+// //       EXPECT_FALSE(
+// //         is_incorrect_objective(expected_objective_value,
+// // solution.get_additional_termination_information().primal_objective));
+// //       test_objective_sanity(op_problem,
+// //                             solution.get_primal_solution(),
+// // solution.get_additional_termination_information().primal_objective);
+// //       test_constraint_sanity(op_problem, solution);
+// //     }
+// //   }
+// // }
+
+constexpr double initial_step_size_afiro     = 1.4893;
+constexpr double initial_primal_weight_afiro = 0.0141652;
+constexpr double factor_tolerance            = 1e-4f;
 
 // // Should be added to google test
 // #define EXPECT_NOT_NEAR(val1, val2, abs_error)
@@ -748,6 +747,7 @@ TEST(pdlp_class, run_sub_mittleman)
 //   auto solver_settings = pdlp_solver_settings_t{};
 //   solver_settings.iteration_limit         = 3000;
 //   solver_settings.per_constraint_residual = true;
+//   solver_settings.presolve                = false;
 //   solver_settings.method                  = cuopt::linear_programming::method_t::PDLP;
 
 //   cuopt::mps_parser::mps_data_model_t op_problem1 =
@@ -826,167 +826,170 @@ TEST(pdlp_class, run_sub_mittleman)
 //   EXPECT_EQ(solution2.get_termination_status(), pdlp_termination_status_t::PrimalFeasible);
 // }
 
-// TEST(pdlp_class, warm_start)
-// {
-//   std::vector instance_names{"graph40-40",
-//                                           "ex10",
-//                                           "datt256_lp",
-//                                           "woodlands09",
-//                                           "savsched1",
-//                                           "nug08-3rd",
-//                                           "qap15",
-//                                           "scpm1",
-//                                           "neos3",
-//                                           "a2864"};
-//   for (auto instance_name : instance_names) {
-//     const raft::handle_t handle{};
-
-//     auto path =
-//       make_path_absolute("linear_programming/" + instance_name + "/" + instance_name + ".mps");
-//     auto solver_settings             = pdlp_solver_settings_t{};
-//     solver_settings.pdlp_solver_mode = cuopt::linear_programming::pdlp_solver_mode_t::Stable2;
-//     solver_settings.set_optimality_tolerance(1e-2);
-//     solver_settings.detect_infeasibility = false;
-//     solver_settings.method               = cuopt::linear_programming::method_t::PDLP;
-
-//     cuopt::mps_parser::mps_data_model_t mps_data_model =
-//       cuopt::mps_parser::parse_mps(path);
-//     auto op_problem1 =
-//       cuopt::linear_programming::mps_data_model_to_optimization_problem(
-//         &handle, mps_data_model);
-
-//     // Solving from scratch until 1e-2
-//     optimization_problem_solution_t solution1 = solve_lp(op_problem1,
-//     solver_settings);
-
-//     // Solving until 1e-1 to use the result as a warm start
-//     solver_settings.set_optimality_tolerance(1e-1);
-//     auto op_problem2 =
-//       cuopt::linear_programming::mps_data_model_to_optimization_problem(
-//         &handle, mps_data_model);
-//     optimization_problem_solution_t solution2 = solve_lp(op_problem2,
-//     solver_settings);
-
-//     // Solving until 1e-2 using the previous state as a warm start
-//     solver_settings.set_optimality_tolerance(1e-2);
-//     auto op_problem3 =
-//       cuopt::linear_programming::mps_data_model_to_optimization_problem(
-//         &handle, mps_data_model);
-//     solver_settings.set_pdlp_warm_start_data(solution2.get_pdlp_warm_start_data());
-//     optimization_problem_solution_t solution3 = solve_lp(op_problem3,
-//     solver_settings);
+TEST(pdlp_class, warm_start)
+{
+  std::vector instance_names{"graph40-40",
+                                          "ex10",
+                                          "datt256_lp",
+                                          "woodlands09",
+                                          "savsched1",
+                                          "nug08-3rd",
+                                          "qap15",
+                                          "scpm1",
+                                          "neos3",
+                                          "a2864"};
+  for (auto instance_name : instance_names) {
+    const raft::handle_t handle{};
+
+    auto path =
+      make_path_absolute("linear_programming/" + instance_name + "/" + instance_name + ".mps");
+    auto solver_settings             = pdlp_solver_settings_t{};
+    solver_settings.pdlp_solver_mode = cuopt::linear_programming::pdlp_solver_mode_t::Stable2;
+    solver_settings.set_optimality_tolerance(1e-2);
+    solver_settings.detect_infeasibility = false;
+    solver_settings.method               = cuopt::linear_programming::method_t::PDLP;
+    // Disable presolve to test warm start
+    solver_settings.presolve = false;
+
+    cuopt::mps_parser::mps_data_model_t mps_data_model =
+      cuopt::mps_parser::parse_mps(path);
+    auto op_problem1 =
+      cuopt::linear_programming::mps_data_model_to_optimization_problem(
+        &handle, mps_data_model);
+
+    // Solving from scratch until 1e-2
+    optimization_problem_solution_t solution1 = solve_lp(op_problem1, solver_settings);
+
+    // Solving until 1e-1 to use the result as a warm start
+    solver_settings.set_optimality_tolerance(1e-1);
+    auto op_problem2 =
+      cuopt::linear_programming::mps_data_model_to_optimization_problem(
+        &handle, mps_data_model);
+    optimization_problem_solution_t solution2 = solve_lp(op_problem2, solver_settings);
+
+    // Solving until 1e-2 using the previous state as a warm start
+    solver_settings.set_optimality_tolerance(1e-2);
+    auto op_problem3 =
+      cuopt::linear_programming::mps_data_model_to_optimization_problem(
+        &handle, mps_data_model);
+    solver_settings.set_pdlp_warm_start_data(solution2.get_pdlp_warm_start_data());
+    optimization_problem_solution_t solution3 = solve_lp(op_problem3, solver_settings);
+
+    EXPECT_EQ(solution1.get_additional_termination_information().number_of_steps_taken,
+              solution3.get_additional_termination_information().number_of_steps_taken +
+                solution2.get_additional_termination_information().number_of_steps_taken);
+  }
+}
 
-//     EXPECT_EQ(solution1.get_additional_termination_information().number_of_steps_taken,
-//               solution3.get_additional_termination_information().number_of_steps_taken +
-//                 solution2.get_additional_termination_information().number_of_steps_taken);
-//   }
-// }
+TEST(dual_simplex, afiro)
+{
+  cuopt::linear_programming::pdlp_solver_settings_t settings =
+    cuopt::linear_programming::pdlp_solver_settings_t{};
+  settings.method = cuopt::linear_programming::method_t::DualSimplex;
 
-// TEST(dual_simplex, afiro)
-// {
-//   cuopt::linear_programming::pdlp_solver_settings_t settings =
-//     cuopt::linear_programming::pdlp_solver_settings_t{};
-//   settings.method = cuopt::linear_programming::method_t::DualSimplex;
+  const raft::handle_t handle_{};
 
-//   const raft::handle_t handle_{};
+  auto path = make_path_absolute("linear_programming/afiro_original.mps");
+  cuopt::mps_parser::mps_data_model_t op_problem =
+    cuopt::mps_parser::parse_mps(path, true);
 
-//   auto path = make_path_absolute("linear_programming/afiro_original.mps");
-//   cuopt::mps_parser::mps_data_model_t op_problem =
-//     cuopt::mps_parser::parse_mps(path, true);
-
-//   optimization_problem_solution_t solution = solve_lp(&handle_, op_problem,
-//   settings); EXPECT_EQ(solution.get_termination_status(), pdlp_termination_status_t::Optimal);
-//   EXPECT_FALSE(is_incorrect_objective(
-//     afiro_primal_objective, solution.get_additional_termination_information().primal_objective));
-// }
+  optimization_problem_solution_t solution = solve_lp(&handle_, op_problem, settings);
+  EXPECT_EQ(solution.get_termination_status(), pdlp_termination_status_t::Optimal);
+  EXPECT_FALSE(is_incorrect_objective(
+    afiro_primal_objective, solution.get_additional_termination_information().primal_objective));
+}
 
-// // Should return a numerical error
-// TEST(pdlp_class, run_empty_matrix_pdlp)
-// {
-//   const raft::handle_t handle_{};
+// Should return a numerical error
+TEST(pdlp_class, run_empty_matrix_pdlp)
+{
+  const raft::handle_t handle_{};
 
-//   auto path = make_path_absolute("linear_programming/empty_matrix.mps");
-//   cuopt::mps_parser::mps_data_model_t op_problem =
-//     cuopt::mps_parser::parse_mps(path);
+  auto path = make_path_absolute("linear_programming/empty_matrix.mps");
+  cuopt::mps_parser::mps_data_model_t op_problem =
+    cuopt::mps_parser::parse_mps(path);
 
-//   auto solver_settings   = pdlp_solver_settings_t{};
-//   solver_settings.method = cuopt::linear_programming::method_t::PDLP;
+  auto solver_settings   = pdlp_solver_settings_t{};
+  solver_settings.method = cuopt::linear_programming::method_t::PDLP;
+  // Reduces to empty problem
+  solver_settings.presolve = false;
 
-//   optimization_problem_solution_t solution =
-//     solve_lp(&handle_, op_problem, solver_settings);
-//   EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_NUMERICAL_ERROR);
-// }
+  optimization_problem_solution_t solution =
+    solve_lp(&handle_, op_problem, solver_settings);
+  EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_NUMERICAL_ERROR);
+}
 
-// // Should run thanks to Dual Simplex
-// TEST(pdlp_class, run_empty_matrix_dual_simplex)
-// {
-//   const raft::handle_t handle_{};
+// Should run thanks to Dual Simplex
+TEST(pdlp_class, run_empty_matrix_dual_simplex)
+{
+  const raft::handle_t handle_{};
 
-//   auto path = make_path_absolute("linear_programming/empty_matrix.mps");
-//   cuopt::mps_parser::mps_data_model_t op_problem =
-//     cuopt::mps_parser::parse_mps(path);
+  auto path = make_path_absolute("linear_programming/empty_matrix.mps");
+  cuopt::mps_parser::mps_data_model_t op_problem =
+    cuopt::mps_parser::parse_mps(path);
 
-//   auto solver_settings   = pdlp_solver_settings_t{};
-//   solver_settings.method = cuopt::linear_programming::method_t::Concurrent;
+  auto solver_settings   = pdlp_solver_settings_t{};
+  solver_settings.method = cuopt::linear_programming::method_t::Concurrent;
+  // Reduces to empty problem
+  solver_settings.presolve = false;
 
-//   optimization_problem_solution_t solution =
-//     solve_lp(&handle_, op_problem, solver_settings);
-//   EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
-//   EXPECT_FALSE(solution.get_additional_termination_information().solved_by_pdlp);
-// }
+  optimization_problem_solution_t solution =
+    solve_lp(&handle_, op_problem, solver_settings);
+  EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
+  EXPECT_FALSE(solution.get_additional_termination_information().solved_by_pdlp);
+}
 
-// TEST(pdlp_class, test_max)
-// {
-//   const raft::handle_t handle_{};
+TEST(pdlp_class, test_max)
+{
+  const raft::handle_t handle_{};
 
-//   auto path = make_path_absolute("linear_programming/good-max.mps");
-//   cuopt::mps_parser::mps_data_model_t op_problem =
-//     cuopt::mps_parser::parse_mps(path);
+  auto path = make_path_absolute("linear_programming/good-max.mps");
+  cuopt::mps_parser::mps_data_model_t op_problem =
+    cuopt::mps_parser::parse_mps(path);
 
-//   auto solver_settings   = pdlp_solver_settings_t{};
-//   solver_settings.method = cuopt::linear_programming::method_t::PDLP;
+  auto solver_settings   = pdlp_solver_settings_t{};
+  solver_settings.method = cuopt::linear_programming::method_t::PDLP;
 
-//   optimization_problem_solution_t solution =
-//     solve_lp(&handle_, op_problem, solver_settings);
-//   EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
-//   EXPECT_NEAR(
-//     solution.get_additional_termination_information().primal_objective, 17.0, factor_tolerance);
-// }
+  optimization_problem_solution_t solution =
+    solve_lp(&handle_, op_problem, solver_settings);
+  EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
+  EXPECT_NEAR(
+    solution.get_additional_termination_information().primal_objective, 17.0, factor_tolerance);
+}
 
-// TEST(pdlp_class, test_max_with_offset)
-// {
-//   const raft::handle_t handle_{};
+TEST(pdlp_class, test_max_with_offset)
+{
+  const raft::handle_t handle_{};
 
-//   auto path = make_path_absolute("linear_programming/max_offset.mps");
-//   cuopt::mps_parser::mps_data_model_t op_problem =
-//     cuopt::mps_parser::parse_mps(path);
+  auto path = make_path_absolute("linear_programming/max_offset.mps");
+  cuopt::mps_parser::mps_data_model_t op_problem =
+    cuopt::mps_parser::parse_mps(path);
 
-//   auto solver_settings   = pdlp_solver_settings_t{};
-//   solver_settings.method = cuopt::linear_programming::method_t::PDLP;
+  auto solver_settings   = pdlp_solver_settings_t{};
+  solver_settings.method = cuopt::linear_programming::method_t::PDLP;
 
-//   optimization_problem_solution_t solution =
-//     solve_lp(&handle_, op_problem, solver_settings);
-//   EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
-//   EXPECT_NEAR(
-//     solution.get_additional_termination_information().primal_objective, 0.0, factor_tolerance);
-// }
+  optimization_problem_solution_t solution =
+    solve_lp(&handle_, op_problem, solver_settings);
+  EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
+  EXPECT_NEAR(
+    solution.get_additional_termination_information().primal_objective, 0.0, factor_tolerance);
+}
 
-// TEST(pdlp_class, test_lp_no_constraints)
-// {
-//   const raft::handle_t handle_{};
+TEST(pdlp_class, test_lp_no_constraints)
+{
+  const raft::handle_t handle_{};
 
-//   auto path = make_path_absolute("linear_programming/lp-model-no-constraints.mps");
-//   cuopt::mps_parser::mps_data_model_t op_problem =
-//     cuopt::mps_parser::parse_mps(path);
+  auto path = make_path_absolute("linear_programming/lp-model-no-constraints.mps");
+  cuopt::mps_parser::mps_data_model_t op_problem =
+    cuopt::mps_parser::parse_mps(path);
 
-//   auto solver_settings = pdlp_solver_settings_t{};
+  auto solver_settings = pdlp_solver_settings_t{};
 
-//   optimization_problem_solution_t solution =
-//     solve_lp(&handle_, op_problem, solver_settings);
-//   EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
-//   EXPECT_NEAR(
-//     solution.get_additional_termination_information().primal_objective, 1.0, factor_tolerance);
-// }
+  optimization_problem_solution_t solution =
+    solve_lp(&handle_, op_problem, solver_settings);
+  EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
+  EXPECT_NEAR(
+    solution.get_additional_termination_information().primal_objective, 1.0, factor_tolerance);
+}
 
 }  // namespace cuopt::linear_programming::test
 
diff --git a/cpp/tests/linear_programming/utilities/pdlp_test_utilities.cuh b/cpp/tests/linear_programming/utilities/pdlp_test_utilities.cuh
index ba6fa6a43f..190c0e7518 100644
--- a/cpp/tests/linear_programming/utilities/pdlp_test_utilities.cuh
+++ b/cpp/tests/linear_programming/utilities/pdlp_test_utilities.cuh
@@ -132,9 +132,6 @@ static void test_constraint_sanity(
 
   // std::all_of would work but we would need C++23 zip views
   for (size_t i = 0; i < primal_vars.size(); ++i) {
-    // std::cout << "var " << i << " primal_vars[i]: " << primal_vars[i]
-    //           << " variable_lower_bounds[i]: " << variable_lower_bounds[i]
-    //           << " variable_upper_bounds[i]: " << variable_upper_bounds[i] << std::endl;
     // Not always stricly true because we apply variable bound clamping on the scaled problem
     // After unscaling it, the variables might not respect exactly (this adding an epsilon)
     EXPECT_TRUE(primal_vars[i] >= variable_lower_bounds[i] - epsilon &&

From f81db367a148cc73cc54e3e61c1bb631d1f5fa3d Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Thu, 31 Jul 2025 00:16:33 -0700
Subject: [PATCH 039/108] Fix warm starts

---
 cpp/src/linear_programming/solve.cu           |   16 +-
 cpp/tests/linear_programming/pdlp_test.cu     | 1502 ++++++++---------
 .../linear_programming/test_lp_solver.py      |    1 +
 3 files changed, 747 insertions(+), 772 deletions(-)

diff --git a/cpp/src/linear_programming/solve.cu b/cpp/src/linear_programming/solve.cu
index c58497c557..fe2b8222bc 100644
--- a/cpp/src/linear_programming/solve.cu
+++ b/cpp/src/linear_programming/solve.cu
@@ -565,8 +565,8 @@ optimization_problem_solution_t solve_lp(optimization_problem_t::max() : settings.time_limit;
+    // const f_t time_limit =
+    // settings.time_limit == 0 ? std::numeric_limits::max() : settings.time_limit;
 
     // Create log stream for file logging and add it to default logger
     init_logger_t log(settings.log_file, settings.log_to_console);
@@ -584,7 +584,7 @@ optimization_problem_solution_t solve_lp(optimization_problem_t::check_initial_solution_representation(op_problem, settings);
     }
 
-    auto timer = cuopt::timer_t(time_limit);
+    auto presolve_timer = cuopt::timer_t(settings.time_limit);
     detail::problem_t problem(op_problem);
 
     if (settings.user_problem_file != "") {
@@ -602,7 +602,7 @@ optimization_problem_solution_t solve_lp(optimization_problem_t>();
       auto reduced_problem = presolver->apply(op_problem,
                                               cuopt::linear_programming::problem_category_t::LP,
@@ -613,7 +613,7 @@ optimization_problem_solution_t solve_lp(optimization_problem_tget_stream());
       }
       problem       = detail::problem_t(reduced_problem);
-      presolve_time = timer.elapsed_time();
+      presolve_time = presolve_timer.elapsed_time();
       CUOPT_LOG_INFO("Third party presolve time: %f", presolve_time);
     }
 
@@ -641,7 +641,6 @@ optimization_problem_solution_t solve_lp(optimization_problem_tget_stream());
     if (run_presolve) {
-      // Dual postsolve is not supported yet in Papilo.
       presolver->undo(primal_solution,
                       dual_solution,
                       reduced_costs,
@@ -657,13 +656,12 @@ optimization_problem_solution_t solve_lp(optimization_problem_t sol(primal_solution,
                                                   dual_solution,
                                                   reduced_costs,
+                                                  solution.get_pdlp_warm_start_data(),
                                                   op_problem.get_objective_name(),
                                                   op_problem.get_variable_names(),
                                                   op_problem.get_row_names(),
                                                   full_stats,
-                                                  solution.get_termination_status(),
-                                                  op_problem.get_handle_ptr(),
-                                                  true);
+                                                  solution.get_termination_status());
 
     if (settings.sol_file != "") {
       CUOPT_LOG_INFO("Writing solution to file %s", settings.sol_file.c_str());
diff --git a/cpp/tests/linear_programming/pdlp_test.cu b/cpp/tests/linear_programming/pdlp_test.cu
index 83d291bc58..249ab487d0 100644
--- a/cpp/tests/linear_programming/pdlp_test.cu
+++ b/cpp/tests/linear_programming/pdlp_test.cu
@@ -65,766 +65,745 @@ static bool is_incorrect_objective(double reference, double objective)
   return std::abs((reference - objective) / reference) > 0.01;
 }
 
-// TEST(pdlp_class, run_double)
-// {
-//   const raft::handle_t handle_{};
-
-//   auto path = make_path_absolute("linear_programming/afiro_original.mps");
-//   cuopt::mps_parser::mps_data_model_t op_problem =
-//     cuopt::mps_parser::parse_mps(path, true);
-
-//   auto solver_settings   = pdlp_solver_settings_t{};
-//   solver_settings.method = cuopt::linear_programming::method_t::PDLP;
-
-//   optimization_problem_solution_t solution =
-//     solve_lp(&handle_, op_problem, solver_settings);
-//   EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
-//   EXPECT_FALSE(is_incorrect_objective(
-//     afiro_primal_objective, solution.get_additional_termination_information().primal_objective));
-// }
-
-// TEST(pdlp_class, run_double_very_low_accuracy)
-// {
-//   const raft::handle_t handle_{};
-
-//   auto path = make_path_absolute("linear_programming/afiro_original.mps");
-//   cuopt::mps_parser::mps_data_model_t op_problem =
-//     cuopt::mps_parser::parse_mps(path, true);
-
-//   cuopt::linear_programming::pdlp_solver_settings_t settings =
-//     cuopt::linear_programming::pdlp_solver_settings_t{};
-//   settings.presolve = false;
-//   // With all 0 afiro with return an error
-//   // Setting absolute tolerance to the minimal value of 1e-12 will make it work
-//   settings.tolerances.absolute_dual_tolerance   = settings.minimal_absolute_tolerance;
-//   settings.tolerances.relative_dual_tolerance   = 0.0;
-//   settings.tolerances.absolute_primal_tolerance = settings.minimal_absolute_tolerance;
-//   settings.tolerances.relative_primal_tolerance = 0.0;
-//   settings.tolerances.absolute_gap_tolerance    = settings.minimal_absolute_tolerance;
-//   settings.tolerances.relative_gap_tolerance    = 0.0;
-//   settings.method                               = cuopt::linear_programming::method_t::PDLP;
-
-//   optimization_problem_solution_t solution = solve_lp(&handle_, op_problem,
-//   settings); EXPECT_EQ((int)solution.get_termination_status(),
-//   CUOPT_TERIMINATION_STATUS_OPTIMAL); EXPECT_FALSE(is_incorrect_objective(
-//     afiro_primal_objective, solution.get_additional_termination_information().primal_objective));
-// }
-
-// TEST(pdlp_class, run_double_initial_solution)
-// {
-//   const raft::handle_t handle_{};
-
-//   auto path = make_path_absolute("linear_programming/afiro_original.mps");
-//   cuopt::mps_parser::mps_data_model_t op_problem =
-//     cuopt::mps_parser::parse_mps(path, true);
-
-//   std::vector inital_primal_sol(op_problem.get_n_variables());
-//   std::fill(inital_primal_sol.begin(), inital_primal_sol.end(), 1.0);
-//   op_problem.set_initial_primal_solution(inital_primal_sol.data(), inital_primal_sol.size());
-
-//   auto solver_settings   = pdlp_solver_settings_t{};
-//   solver_settings.method = cuopt::linear_programming::method_t::PDLP;
-
-//   optimization_problem_solution_t solution =
-//     solve_lp(&handle_, op_problem, solver_settings);
-//   EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
-//   EXPECT_FALSE(is_incorrect_objective(
-//     afiro_primal_objective, solution.get_additional_termination_information().primal_objective));
-// }
-
-// TEST(pdlp_class, run_iteration_limit)
-// {
-//   const raft::handle_t handle_{};
-
-//   auto path = make_path_absolute("linear_programming/afiro_original.mps");
-//   cuopt::mps_parser::mps_data_model_t op_problem =
-//     cuopt::mps_parser::parse_mps(path, true);
-
-//   cuopt::linear_programming::pdlp_solver_settings_t settings =
-//     cuopt::linear_programming::pdlp_solver_settings_t{};
-
-//   settings.iteration_limit = 10;
-//   // To make sure it doesn't return before the iteration limit
-//   settings.set_optimality_tolerance(0);
-//   settings.method = cuopt::linear_programming::method_t::PDLP;
-
-//   optimization_problem_solution_t solution = solve_lp(&handle_, op_problem,
-//   settings); EXPECT_EQ((int)solution.get_termination_status(),
-//   CUOPT_TERIMINATION_STATUS_ITERATION_LIMIT);
-//   // By default we would return all 0, we now return what we currently have so not all 0
-//   EXPECT_FALSE(thrust::all_of(handle_.get_thrust_policy(),
-//                               solution.get_primal_solution().begin(),
-//                               solution.get_primal_solution().end(),
-//                               thrust::placeholders::_1 == 0.0));
-// }
-
-// TEST(pdlp_class, run_time_limit)
-// {
-//   const raft::handle_t handle_{};
-//   auto path = make_path_absolute("linear_programming/savsched1/savsched1.mps");
-//   cuopt::mps_parser::mps_data_model_t op_problem =
-//     cuopt::mps_parser::parse_mps(path);
-
-//   cuopt::linear_programming::pdlp_solver_settings_t settings =
-//     cuopt::linear_programming::pdlp_solver_settings_t{};
-
-//   // 200 ms
-//   constexpr double time_limit_seconds = 0.2;
-//   settings.time_limit                 = time_limit_seconds;
-//   // To make sure it doesn't return before the time limit
-//   settings.set_optimality_tolerance(0);
-//   settings.method = cuopt::linear_programming::method_t::PDLP;
-
-//   optimization_problem_solution_t solution = solve_lp(&handle_, op_problem,
-//   settings);
-
-//   EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_TIME_LIMIT);
-//   // By default we would return all 0, we now return what we currently have so not all 0
-//   EXPECT_FALSE(thrust::all_of(handle_.get_thrust_policy(),
-//                               solution.get_primal_solution().begin(),
-//                               solution.get_primal_solution().end(),
-//                               thrust::placeholders::_1 == 0.0));
-//   // Check that indeed it didn't run for more than x time
-//   EXPECT_TRUE(solution.get_additional_termination_information().solve_time <
-//               (time_limit_seconds * 5) * 1000);
-// }
-
-// // TEST(pdlp_class, run_sub_mittleman)
-// // {
-// //   std::vector>      // Expected objective value
-// //     instances{{"graph40-40", -300.0},
-// //               {"ex10", 100.0003411893773},
-// //               {"datt256_lp", 255.9992298290425},
-// //               {"woodlands09", 0.0},
-// //               {"savsched1", 217.4054085795689},
-// //               {"nug08-3rd", 214.0141488989151},
-// //               {"qap15", 1040.999546647414},
-// //               {"scpm1", 413.7787723060584},
-// //               {"neos3", 27773.54059633068},
-// //               {"a2864", -282.9962521965164}};
-
-// //   for (const auto& entry : instances) {
-// //     const auto& name                    = entry.first;
-// //     const auto expected_objective_value = entry.second;
-
-// //     auto path = make_path_absolute("linear_programming/" + name + "/" + name + ".mps");
-// //     cuopt::mps_parser::mps_data_model_t op_problem =
-// //       cuopt::mps_parser::parse_mps(path);
-
-// //     // Testing for each solver_mode is ok as it's parsing that is the bottleneck here, not
-// //     solving auto solver_mode_list = {
-// //       cuopt::linear_programming::pdlp_solver_mode_t::Stable2,
-// //       cuopt::linear_programming::pdlp_solver_mode_t::Methodical1,
-// //       cuopt::linear_programming::pdlp_solver_mode_t::Fast1,
-// //     };
-// //     for (auto solver_mode : solver_mode_list) {
-// //       auto settings             = pdlp_solver_settings_t{};
-// //       settings.pdlp_solver_mode = solver_mode;
-// //       settings.method           = cuopt::linear_programming::method_t::PDLP;
-// //       const raft::handle_t handle_{};
-// //       optimization_problem_solution_t solution =
-// //         solve_lp(&handle_, op_problem, settings);
-// //       EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
-// //       EXPECT_FALSE(
-// //         is_incorrect_objective(expected_objective_value,
-// // solution.get_additional_termination_information().primal_objective));
-// //       test_objective_sanity(op_problem,
-// //                             solution.get_primal_solution(),
-// // solution.get_additional_termination_information().primal_objective);
-// //       test_constraint_sanity(op_problem, solution);
-// //     }
-// //   }
-// // }
+TEST(pdlp_class, run_double)
+{
+  const raft::handle_t handle_{};
+
+  auto path = make_path_absolute("linear_programming/afiro_original.mps");
+  cuopt::mps_parser::mps_data_model_t op_problem =
+    cuopt::mps_parser::parse_mps(path, true);
+
+  auto solver_settings   = pdlp_solver_settings_t{};
+  solver_settings.method = cuopt::linear_programming::method_t::PDLP;
+
+  optimization_problem_solution_t solution =
+    solve_lp(&handle_, op_problem, solver_settings);
+  EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
+  EXPECT_FALSE(is_incorrect_objective(
+    afiro_primal_objective, solution.get_additional_termination_information().primal_objective));
+}
+
+TEST(pdlp_class, run_double_very_low_accuracy)
+{
+  const raft::handle_t handle_{};
+
+  auto path = make_path_absolute("linear_programming/afiro_original.mps");
+  cuopt::mps_parser::mps_data_model_t op_problem =
+    cuopt::mps_parser::parse_mps(path, true);
+
+  cuopt::linear_programming::pdlp_solver_settings_t settings =
+    cuopt::linear_programming::pdlp_solver_settings_t{};
+  // With all 0 afiro with return an error
+  // Setting absolute tolerance to the minimal value of 1e-12 will make it work
+  settings.tolerances.absolute_dual_tolerance   = settings.minimal_absolute_tolerance;
+  settings.tolerances.relative_dual_tolerance   = 0.0;
+  settings.tolerances.absolute_primal_tolerance = settings.minimal_absolute_tolerance;
+  settings.tolerances.relative_primal_tolerance = 0.0;
+  settings.tolerances.absolute_gap_tolerance    = settings.minimal_absolute_tolerance;
+  settings.tolerances.relative_gap_tolerance    = 0.0;
+  settings.method                               = cuopt::linear_programming::method_t::PDLP;
+  // Model sensible, disabling presolve
+  settings.presolve = false;
+
+  optimization_problem_solution_t solution = solve_lp(&handle_, op_problem, settings);
+  EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
+  EXPECT_FALSE(is_incorrect_objective(
+    afiro_primal_objective, solution.get_additional_termination_information().primal_objective));
+}
+
+TEST(pdlp_class, run_double_initial_solution)
+{
+  const raft::handle_t handle_{};
+
+  auto path = make_path_absolute("linear_programming/afiro_original.mps");
+  cuopt::mps_parser::mps_data_model_t op_problem =
+    cuopt::mps_parser::parse_mps(path, true);
+
+  std::vector inital_primal_sol(op_problem.get_n_variables());
+  std::fill(inital_primal_sol.begin(), inital_primal_sol.end(), 1.0);
+  op_problem.set_initial_primal_solution(inital_primal_sol.data(), inital_primal_sol.size());
+
+  auto solver_settings   = pdlp_solver_settings_t{};
+  solver_settings.method = cuopt::linear_programming::method_t::PDLP;
+
+  optimization_problem_solution_t solution =
+    solve_lp(&handle_, op_problem, solver_settings);
+  EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
+  EXPECT_FALSE(is_incorrect_objective(
+    afiro_primal_objective, solution.get_additional_termination_information().primal_objective));
+}
+
+TEST(pdlp_class, run_iteration_limit)
+{
+  const raft::handle_t handle_{};
+
+  auto path = make_path_absolute("linear_programming/afiro_original.mps");
+  cuopt::mps_parser::mps_data_model_t op_problem =
+    cuopt::mps_parser::parse_mps(path, true);
+
+  cuopt::linear_programming::pdlp_solver_settings_t settings =
+    cuopt::linear_programming::pdlp_solver_settings_t{};
+
+  settings.iteration_limit = 10;
+  // To make sure it doesn't return before the iteration limit
+  settings.set_optimality_tolerance(0);
+  settings.method = cuopt::linear_programming::method_t::PDLP;
+
+  optimization_problem_solution_t solution = solve_lp(&handle_, op_problem, settings);
+  EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_ITERATION_LIMIT);
+  // By default we would return all 0, we now return what we currently have so not all 0
+  EXPECT_FALSE(thrust::all_of(handle_.get_thrust_policy(),
+                              solution.get_primal_solution().begin(),
+                              solution.get_primal_solution().end(),
+                              thrust::placeholders::_1 == 0.0));
+}
+
+TEST(pdlp_class, run_time_limit)
+{
+  const raft::handle_t handle_{};
+  auto path = make_path_absolute("linear_programming/savsched1/savsched1.mps");
+  cuopt::mps_parser::mps_data_model_t op_problem =
+    cuopt::mps_parser::parse_mps(path);
+
+  cuopt::linear_programming::pdlp_solver_settings_t settings =
+    cuopt::linear_programming::pdlp_solver_settings_t{};
+
+  // 200 ms
+  constexpr double time_limit_seconds = 0.2;
+  settings.time_limit                 = time_limit_seconds;
+  // To make sure it doesn't return before the time limit
+  settings.set_optimality_tolerance(0);
+  settings.method = cuopt::linear_programming::method_t::PDLP;
+
+  optimization_problem_solution_t solution = solve_lp(&handle_, op_problem, settings);
+
+  EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_TIME_LIMIT);
+  // By default we would return all 0, we now return what we currently have so not all 0
+  EXPECT_FALSE(thrust::all_of(handle_.get_thrust_policy(),
+                              solution.get_primal_solution().begin(),
+                              solution.get_primal_solution().end(),
+                              thrust::placeholders::_1 == 0.0));
+  // Check that indeed it didn't run for more than x time
+  EXPECT_TRUE(solution.get_additional_termination_information().solve_time <
+              (time_limit_seconds * 5) * 1000);
+}
+
+TEST(pdlp_class, run_sub_mittleman)
+{
+  std::vector>      // Expected objective value
+    instances{{"graph40-40", -300.0},
+              {"ex10", 100.0003411893773},
+              {"datt256_lp", 255.9992298290425},
+              {"woodlands09", 0.0},
+              {"savsched1", 217.4054085795689},
+              {"nug08-3rd", 214.0141488989151},
+              {"qap15", 1040.999546647414},
+              {"scpm1", 413.7787723060584},
+              {"neos3", 27773.54059633068},
+              {"a2864", -282.9962521965164}};
+
+  for (const auto& entry : instances) {
+    const auto& name                    = entry.first;
+    const auto expected_objective_value = entry.second;
+
+    auto path = make_path_absolute("linear_programming/" + name + "/" + name + ".mps");
+    cuopt::mps_parser::mps_data_model_t op_problem =
+      cuopt::mps_parser::parse_mps(path);
+
+    // Testing for each solver_mode is ok as it's parsing that is the bottleneck here, not
+    // solving
+    auto solver_mode_list = {
+      cuopt::linear_programming::pdlp_solver_mode_t::Stable2,
+      cuopt::linear_programming::pdlp_solver_mode_t::Methodical1,
+      cuopt::linear_programming::pdlp_solver_mode_t::Fast1,
+    };
+    for (auto solver_mode : solver_mode_list) {
+      auto settings             = pdlp_solver_settings_t{};
+      settings.pdlp_solver_mode = solver_mode;
+      settings.method           = cuopt::linear_programming::method_t::PDLP;
+      const raft::handle_t handle_{};
+      optimization_problem_solution_t solution =
+        solve_lp(&handle_, op_problem, settings);
+      EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
+      EXPECT_FALSE(
+        is_incorrect_objective(expected_objective_value,
+                               solution.get_additional_termination_information().primal_objective));
+      test_objective_sanity(op_problem,
+                            solution.get_primal_solution(),
+                            solution.get_additional_termination_information().primal_objective);
+      test_constraint_sanity(op_problem, solution);
+    }
+  }
+}
 
 constexpr double initial_step_size_afiro     = 1.4893;
 constexpr double initial_primal_weight_afiro = 0.0141652;
 constexpr double factor_tolerance            = 1e-4f;
 
-// // Should be added to google test
-// #define EXPECT_NOT_NEAR(val1, val2, abs_error)
-//   EXPECT_FALSE((std::abs((val1) - (val2)) <= (abs_error)))
-
-// TEST(pdlp_class, initial_solution_test)
-// {
-//   const raft::handle_t handle_{};
-
-//   auto path = make_path_absolute("linear_programming/afiro_original.mps");
-//   cuopt::mps_parser::mps_data_model_t mps_data_model =
-//     cuopt::mps_parser::parse_mps(path);
-
-//   auto op_problem = cuopt::linear_programming::mps_data_model_to_optimization_problem(
-//     &handle_, mps_data_model);
-//   cuopt::linear_programming::detail::problem_t problem(op_problem);
-
-//   auto solver_settings = pdlp_solver_settings_t{};
-//   // We are just testing initial scaling on initial solution scheme so we don't care about solver
-//   solver_settings.iteration_limit = 0;
-//   solver_settings.method          = cuopt::linear_programming::method_t::PDLP;
-//   // Empty call solve to set the parameters and init the handler since calling pdlp object
-//   directly
-//   // doesn't
-//   solver_settings.pdlp_solver_mode = cuopt::linear_programming::pdlp_solver_mode_t::Methodical1;
-//   solve_lp(op_problem, solver_settings);
-//   EXPECT_EQ(cuopt::linear_programming::pdlp_hyper_params::initial_step_size_scaling, 1);
-//   EXPECT_EQ(cuopt::linear_programming::pdlp_hyper_params::default_l_inf_ruiz_iterations, 5);
-//   EXPECT_TRUE(cuopt::linear_programming::pdlp_hyper_params::do_pock_chambolle_scaling);
-//   EXPECT_TRUE(cuopt::linear_programming::pdlp_hyper_params::do_ruiz_scaling);
-//   EXPECT_EQ(cuopt::linear_programming::pdlp_hyper_params::default_alpha_pock_chambolle_rescaling,
-//             1.0);
-
-//   EXPECT_FALSE(cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution);
-//   EXPECT_FALSE(
-//     cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution);
-
-//   {
-//     cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
-//     solver_settings); auto start_solver = std::chrono::high_resolution_clock::now();
-//     solver.run_solver(start_solver);
-//     RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
-//     EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
-//     EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
-//   }
-
-//   // First add an initial primal then dual, then both, which shouldn't influence the values as
-//   the
-//   // scale on initial option is not toggled
-//   {
-//     cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
-//     solver_settings); auto start_solver = std::chrono::high_resolution_clock::now();
-//     std::vector initial_primal(op_problem.get_n_variables(), 1);
-//     auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
-//     solver.set_initial_primal_solution(d_initial_primal);
-//     solver.run_solver(start_solver);
-//     RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
-//     EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
-//     EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
-//   }
-//   {
-//     cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
-//     solver_settings); auto start_solver = std::chrono::high_resolution_clock::now();
-//     std::vector initial_dual(op_problem.get_n_constraints(), 1);
-//     auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
-//     solver.set_initial_dual_solution(d_initial_dual);
-//     solver.run_solver(start_solver);
-//     RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
-//     EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
-//     EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
-//   }
-//   {
-//     cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
-//     solver_settings); auto start_solver = std::chrono::high_resolution_clock::now();
-//     std::vector initial_primal(op_problem.get_n_variables(), 1);
-//     auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
-//     solver.set_initial_primal_solution(d_initial_primal);
-//     std::vector initial_dual(op_problem.get_n_constraints(), 1);
-//     auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
-//     solver.set_initial_dual_solution(d_initial_dual);
-//     solver.run_solver(start_solver);
-//     RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
-//     EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
-//     EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
-//   }
-
-//   // Toggle the scale on initial solution while not providing should yield the same
-//   {
-//     cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
-//     solver_settings); auto start_solver = std::chrono::high_resolution_clock::now();
-//     cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution = true;
-//     solver.run_solver(start_solver);
-//     RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
-//     EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
-//     EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
-//     cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution = false;
-//   }
-//   {
-//     cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
-//     solver_settings); auto start_solver = std::chrono::high_resolution_clock::now();
-//     cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution =
-//     true; solver.run_solver(start_solver);
-//     RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
-//     EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
-//     EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
-//     cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution =
-//     false;
-//   }
-//   {
-//     cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
-//     solver_settings); auto start_solver = std::chrono::high_resolution_clock::now();
-//     cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution =
-//     true; cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution =
-//     true; solver.run_solver(start_solver);
-//     RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
-//     EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
-//     EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
-//     cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution =
-//     false; cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution =
-//     false;
-//   }
-
-//   // Asking for initial scaling on step size with initial solution being only primal or only dual
-//   // should not break but not modify the step size
-//   {
-//     cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution = true;
-//     cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
-//     solver_settings); auto start_solver = std::chrono::high_resolution_clock::now();
-//     std::vector initial_primal(op_problem.get_n_variables(), 1);
-//     auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
-//     solver.set_initial_primal_solution(d_initial_primal);
-//     solver.run_solver(start_solver);
-//     RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
-//     EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
-//     EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
-//     cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution = false;
-//   }
-//   {
-//     cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution = true;
-//     cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
-//     solver_settings); auto start_solver = std::chrono::high_resolution_clock::now();
-//     std::vector initial_dual(op_problem.get_n_constraints(), 1);
-//     auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
-//     solver.set_initial_dual_solution(d_initial_dual);
-//     solver.run_solver(start_solver);
-//     RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
-//     EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
-//     EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
-//     cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution = false;
-//   }
-
-//   // Asking for initial scaling on primal weight with initial solution being only primal or only
-//   // dual should *not* break but the primal weight should not change
-//   {
-//     cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution =
-//     true; cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
-//     solver_settings); auto start_solver = std::chrono::high_resolution_clock::now();
-//     std::vector initial_primal(op_problem.get_n_variables(), 1);
-//     auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
-//     solver.set_initial_primal_solution(d_initial_primal);
-//     solver.run_solver(start_solver);
-//     EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
-//     EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
-//     cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution =
-//     false;
-//   }
-//   {
-//     cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution =
-//     true; cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
-//     solver_settings); auto start_solver = std::chrono::high_resolution_clock::now();
-//     std::vector initial_dual(op_problem.get_n_constraints(), 1);
-//     auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
-//     solver.set_initial_dual_solution(d_initial_dual);
-//     solver.run_solver(start_solver);
-//     EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
-//     EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
-//     cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution =
-//     false;
-//   }
-
-//   // All 0 solution when given an initial primal and dual with scale on the step size should not
-//   // break but not change primal weight and step size
-//   {
-//     cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution = true;
-//     cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
-//     solver_settings); auto start_solver = std::chrono::high_resolution_clock::now();
-//     std::vector initial_primal(op_problem.get_n_variables(), 0);
-//     auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
-//     solver.set_initial_primal_solution(d_initial_primal);
-//     std::vector initial_dual(op_problem.get_n_constraints(), 0);
-//     auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
-//     solver.set_initial_dual_solution(d_initial_dual);
-//     solver.run_solver(start_solver);
-//     EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
-//     EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
-//     cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution = false;
-//   }
-
-//   // All 0 solution when given an initial primal and/or dual with scale on the primal weight is
-//   // *not* an error but should not change primal weight and step size
-//   {
-//     cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution =
-//     true; cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
-//     solver_settings); auto start_solver = std::chrono::high_resolution_clock::now();
-//     std::vector initial_primal(op_problem.get_n_variables(), 0);
-//     auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
-//     solver.set_initial_primal_solution(d_initial_primal);
-//     solver.run_solver(start_solver);
-//     EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
-//     EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
-//     cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution =
-//     false;
-//   }
-//   {
-//     cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution =
-//     true; cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
-//     solver_settings); auto start_solver = std::chrono::high_resolution_clock::now();
-//     std::vector initial_dual(op_problem.get_n_constraints(), 0);
-//     auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
-//     solver.set_initial_dual_solution(d_initial_dual);
-//     solver.run_solver(start_solver);
-//     EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
-//     EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
-//     cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution =
-//     false;
-//   }
-//   {
-//     cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution =
-//     true; cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
-//     solver_settings); auto start_solver = std::chrono::high_resolution_clock::now();
-//     std::vector initial_primal(op_problem.get_n_variables(), 0);
-//     auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
-//     solver.set_initial_primal_solution(d_initial_primal);
-//     std::vector initial_dual(op_problem.get_n_constraints(), 0);
-//     auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
-//     solver.set_initial_dual_solution(d_initial_dual);
-//     solver.run_solver(start_solver);
-//     EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
-//     EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
-//     cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution =
-//     false;
-//   }
-
-//   // A non-all-0 vector for both initial primal and dual set should trigger a modification in
-//   primal
-//   // weight and step size
-//   {
-//     cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution =
-//     true; cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
-//     solver_settings); auto start_solver = std::chrono::high_resolution_clock::now();
-//     std::vector initial_primal(op_problem.get_n_variables(), 1);
-//     auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
-//     solver.set_initial_primal_solution(d_initial_primal);
-//     std::vector initial_dual(op_problem.get_n_constraints(), 1);
-//     auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
-//     solver.set_initial_dual_solution(d_initial_dual);
-//     solver.run_solver(start_solver);
-//     EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
-//     EXPECT_NOT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
-//     cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution =
-//     false;
-//   }
-//   {
-//     cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution = true;
-//     cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
-//     solver_settings); auto start_solver = std::chrono::high_resolution_clock::now();
-//     std::vector initial_primal(op_problem.get_n_variables(), 1);
-//     auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
-//     solver.set_initial_primal_solution(d_initial_primal);
-//     std::vector initial_dual(op_problem.get_n_constraints(), 1);
-//     auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
-//     solver.set_initial_dual_solution(d_initial_dual);
-//     solver.run_solver(start_solver);
-//     EXPECT_NOT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
-//     EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
-//     cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution = false;
-//   }
-//   {
-//     cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution =
-//     true; cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution =
-//     true; cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
-//     solver_settings); auto start_solver = std::chrono::high_resolution_clock::now();
-//     std::vector initial_primal(op_problem.get_n_variables(), 1);
-//     auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
-//     solver.set_initial_primal_solution(d_initial_primal);
-//     std::vector initial_dual(op_problem.get_n_constraints(), 1);
-//     auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
-//     solver.set_initial_dual_solution(d_initial_dual);
-//     solver.run_solver(start_solver);
-//     EXPECT_NOT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
-//     EXPECT_NOT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
-//     cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution =
-//     false; cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution =
-//     false;
-//   }
-// }
-
-// TEST(pdlp_class, initial_primal_weight_step_size_test)
-// {
-//   const raft::handle_t handle_{};
-
-//   auto path = make_path_absolute("linear_programming/afiro_original.mps");
-//   cuopt::mps_parser::mps_data_model_t mps_data_model =
-//     cuopt::mps_parser::parse_mps(path);
-
-//   auto op_problem = cuopt::linear_programming::mps_data_model_to_optimization_problem(
-//     &handle_, mps_data_model);
-//   cuopt::linear_programming::detail::problem_t problem(op_problem);
-
-//   auto solver_settings = pdlp_solver_settings_t{};
-//   // We are just testing initial scaling on initial solution scheme so we don't care about solver
-//   solver_settings.iteration_limit = 0;
-//   solver_settings.method          = cuopt::linear_programming::method_t::PDLP;
-//   // Select the default/legacy solver with no action upon the initial scaling on initial solution
-//   solver_settings.pdlp_solver_mode = cuopt::linear_programming::pdlp_solver_mode_t::Methodical1;
-//   EXPECT_FALSE(cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution);
-//   EXPECT_FALSE(
-//     cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution);
-
-//   // Check setting an initial primal weight and step size
-//   {
-//     cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
-//     solver_settings); auto start_solver                           =
-//     std::chrono::high_resolution_clock::now(); constexpr double test_initial_step_size     = 1.0;
-//     constexpr double test_initial_primal_weight = 2.0;
-//     solver.set_initial_primal_weight(test_initial_primal_weight);
-//     solver.set_initial_step_size(test_initial_step_size);
-//     solver.run_solver(start_solver);
-//     RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
-//     EXPECT_EQ(test_initial_step_size, solver.get_step_size_h());
-//     EXPECT_EQ(test_initial_primal_weight, solver.get_primal_weight_h());
-//   }
-
-//   // Check that after setting an initial step size and primal weight, the computed one when
-//   adding
-//   // an initial primal / dual is indeed different
-//   {
-//     // Launching without an inital step size / primal weight and query the value
-//     cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution =
-//     true; cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution =
-//     true; cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
-//     solver_settings); auto start_solver = std::chrono::high_resolution_clock::now();
-//     std::vector initial_primal(op_problem.get_n_variables(), 1);
-//     auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
-//     solver.set_initial_primal_solution(d_initial_primal);
-//     std::vector initial_dual(op_problem.get_n_constraints(), 1);
-//     auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
-//     solver.set_initial_dual_solution(d_initial_dual);
-//     solver.run_solver(start_solver);
-//     const double previous_step_size     = solver.get_step_size_h();
-//     const double previous_primal_weight = solver.get_primal_weight_h();
-
-//     // Start again but with an initial and check the impact
-//     cuopt::linear_programming::detail::pdlp_solver_t solver2(problem,
-//     solver_settings); start_solver                                =
-//     std::chrono::high_resolution_clock::now(); constexpr double test_initial_step_size     = 1.0;
-//     constexpr double test_initial_primal_weight = 2.0;
-//     solver2.set_initial_primal_weight(test_initial_primal_weight);
-//     solver2.set_initial_step_size(test_initial_step_size);
-//     solver2.set_initial_primal_solution(d_initial_primal);
-//     solver2.set_initial_dual_solution(d_initial_dual);
-//     solver2.run_solver(start_solver);
-//     RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
-//     const double sovler2_step_size     = solver2.get_step_size_h();
-//     const double sovler2_primal_weight = solver2.get_primal_weight_h();
-//     EXPECT_NOT_NEAR(previous_step_size, sovler2_step_size, factor_tolerance);
-//     EXPECT_NOT_NEAR(previous_primal_weight, sovler2_primal_weight, factor_tolerance);
-
-//     // Again but with an initial k which should change the step size only, not the primal weight
-//     cuopt::linear_programming::detail::pdlp_solver_t solver3(problem,
-//     solver_settings); start_solver = std::chrono::high_resolution_clock::now();
-//     solver3.set_initial_primal_weight(test_initial_primal_weight);
-//     solver3.set_initial_step_size(test_initial_step_size);
-//     solver3.set_initial_primal_solution(d_initial_primal);
-//     solver3.set_initial_k(10000);
-//     solver3.set_initial_dual_solution(d_initial_dual);
-//     solver3.set_initial_dual_solution(d_initial_dual);
-//     solver3.run_solver(start_solver);
-//     RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
-//     EXPECT_NOT_NEAR(sovler2_step_size, solver3.get_step_size_h(), factor_tolerance);
-//     EXPECT_NEAR(sovler2_primal_weight, solver3.get_primal_weight_h(), factor_tolerance);
-//   }
-// }
-
-// TEST(pdlp_class, initial_rhs_and_c)
-// {
-//   const raft::handle_t handle_{};
-
-//   auto path = make_path_absolute("linear_programming/afiro_original.mps");
-//   cuopt::mps_parser::mps_data_model_t mps_data_model =
-//     cuopt::mps_parser::parse_mps(path);
-
-//   auto op_problem = cuopt::linear_programming::mps_data_model_to_optimization_problem(
-//     &handle_, mps_data_model);
-//   cuopt::linear_programming::detail::problem_t problem(op_problem);
-
-//   cuopt::linear_programming::detail::pdlp_solver_t solver(problem);
-//   constexpr double test_initial_primal_factor = 1.0;
-//   constexpr double test_initial_dual_factor   = 2.0;
-//   solver.set_relative_dual_tolerance_factor(test_initial_dual_factor);
-//   solver.set_relative_primal_tolerance_factor(test_initial_primal_factor);
-
-//   EXPECT_EQ(solver.get_relative_dual_tolerance_factor(), test_initial_dual_factor);
-//   EXPECT_EQ(solver.get_relative_primal_tolerance_factor(), test_initial_primal_factor);
-// }
-
-// TEST(pdlp_class, per_constraint_test)
-// {
-//   /*
-//    * Define the following LP:
-//    * x1=0.01 <= 0
-//    * x2=0.01 <= 0
-//    * x3=0.1  <= 0
-//    *
-//    * With a tol of 0.1 per constraint will pass but the L2 version will not as L2 of primal
-//    residual
-//    * will be 0.1009
-//    */
-//   raft::handle_t handle;
-//   auto op_problem = optimization_problem_t(&handle);
-
-//   std::vector A_host           = {1.0, 1.0, 1.0};
-//   std::vector indices_host        = {0, 1, 2};
-//   std::vector offset_host         = {0, 1, 2, 3};
-//   std::vector b_host           = {0.0, 0.0, 0.0};
-//   std::vector h_initial_primal = {0.02, 0.03, 0.1};
-//   rmm::device_uvector d_initial_primal(3, handle.get_stream());
-//   raft::copy(
-//     d_initial_primal.data(), h_initial_primal.data(), h_initial_primal.size(),
-//     handle.get_stream());
-
-//   op_problem.set_csr_constraint_matrix(A_host.data(),
-//                                        A_host.size(),
-//                                        indices_host.data(),
-//                                        indices_host.size(),
-//                                        offset_host.data(),
-//                                        offset_host.size());
-//   op_problem.set_constraint_lower_bounds(b_host.data(), b_host.size());
-//   op_problem.set_constraint_upper_bounds(b_host.data(), b_host.size());
-//   op_problem.set_objective_coefficients(b_host.data(), b_host.size());
-
-//   auto problem = cuopt::linear_programming::detail::problem_t(op_problem);
-
-//   pdlp_solver_settings_t solver_settings;
-//   solver_settings.tolerances.relative_primal_tolerance = 0;  // Shouldn't matter
-//   solver_settings.tolerances.absolute_primal_tolerance = 0.1;
-//   solver_settings.tolerances.relative_dual_tolerance   = 0;  // Shoudln't matter
-//   solver_settings.tolerances.absolute_dual_tolerance   = 0.1;
-//   solver_settings.method                               =
-//   cuopt::linear_programming::method_t::PDLP;
-
-//   // First solve without the per constraint and it should break
-//   {
-//     cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
-//     solver_settings);
-
-//     raft::copy(solver.pdhg_solver_.get_primal_solution().data(),
-//                d_initial_primal.data(),
-//                d_initial_primal.size(),
-//                handle.get_stream());
-
-//     auto& current_termination_strategy = solver.get_current_termination_strategy();
-//     pdlp_termination_status_t termination_average =
-//       current_termination_strategy.evaluate_termination_criteria(solver.pdhg_solver_,
-//                                                                  d_initial_primal,
-//                                                                  d_initial_primal,
-//                                                                  problem.combined_bounds,
-//                                                                  problem.objective_coefficients);
-
-//     EXPECT_TRUE(termination_average != pdlp_termination_status_t::Optimal);
-//   }
-//   {
-//     solver_settings.per_constraint_residual = true;
-//     cuopt::linear_programming::detail::pdlp_solver_t solver(problem,
-//     solver_settings);
-
-//     raft::copy(solver.pdhg_solver_.get_primal_solution().data(),
-//                d_initial_primal.data(),
-//                d_initial_primal.size(),
-//                handle.get_stream());
-
-//     auto& current_termination_strategy = solver.get_current_termination_strategy();
-//     pdlp_termination_status_t termination_average =
-//       current_termination_strategy.evaluate_termination_criteria(solver.pdhg_solver_,
-//                                                                  d_initial_primal,
-//                                                                  d_initial_primal,
-//                                                                  problem.combined_bounds,
-//                                                                  problem.objective_coefficients);
-//     EXPECT_EQ(current_termination_strategy.get_convergence_information()
-//                 .get_relative_linf_primal_residual()
-//                 .value(handle.get_stream()),
-//               0.1);
-//   }
-// }
-
-// TEST(pdlp_class, best_primal_so_far_iteration)
-// {
-//   const raft::handle_t handle1{};
-//   const raft::handle_t handle2{};
-
-//   auto path            = make_path_absolute("linear_programming/ns1687037/ns1687037.mps");
-//   auto solver_settings = pdlp_solver_settings_t{};
-//   solver_settings.iteration_limit         = 3000;
-//   solver_settings.per_constraint_residual = true;
-//   solver_settings.presolve                = false;
-//   solver_settings.method                  = cuopt::linear_programming::method_t::PDLP;
-
-//   cuopt::mps_parser::mps_data_model_t op_problem1 =
-//     cuopt::mps_parser::parse_mps(path);
-//   cuopt::mps_parser::mps_data_model_t op_problem2 =
-//     cuopt::mps_parser::parse_mps(path);
-
-//   optimization_problem_solution_t solution1 =
-//     solve_lp(&handle1, op_problem1, solver_settings);
-//   RAFT_CUDA_TRY(cudaDeviceSynchronize());
-//   solver_settings.save_best_primal_so_far = true;
-//   optimization_problem_solution_t solution2 =
-//     solve_lp(&handle2, op_problem2, solver_settings);
-//   RAFT_CUDA_TRY(cudaDeviceSynchronize());
-
-//   EXPECT_TRUE(solution2.get_additional_termination_information().l2_primal_residual <
-//               solution1.get_additional_termination_information().l2_primal_residual);
-// }
-
-// TEST(pdlp_class, best_primal_so_far_time)
-// {
-//   const raft::handle_t handle1{};
-//   const raft::handle_t handle2{};
-
-//   auto path                  = make_path_absolute("linear_programming/ns1687037/ns1687037.mps");
-//   auto solver_settings       = pdlp_solver_settings_t{};
-//   solver_settings.time_limit = 2;
-//   solver_settings.per_constraint_residual = true;
-//   solver_settings.pdlp_solver_mode        =
-//   cuopt::linear_programming::pdlp_solver_mode_t::Stable1; solver_settings.method =
-//   cuopt::linear_programming::method_t::PDLP;
-
-//   cuopt::mps_parser::mps_data_model_t op_problem1 =
-//     cuopt::mps_parser::parse_mps(path);
-//   cuopt::mps_parser::mps_data_model_t op_problem2 =
-//     cuopt::mps_parser::parse_mps(path);
-
-//   optimization_problem_solution_t solution1 =
-//     solve_lp(&handle1, op_problem1, solver_settings);
-//   RAFT_CUDA_TRY(cudaDeviceSynchronize());
-//   solver_settings.save_best_primal_so_far = true;
-//   optimization_problem_solution_t solution2 =
-//     solve_lp(&handle2, op_problem2, solver_settings);
-//   RAFT_CUDA_TRY(cudaDeviceSynchronize());
-
-//   EXPECT_TRUE(solution2.get_additional_termination_information().l2_primal_residual <
-//               solution1.get_additional_termination_information().l2_primal_residual);
-// }
-
-// TEST(pdlp_class, first_primal_feasible)
-// {
-//   const raft::handle_t handle1{};
-//   const raft::handle_t handle2{};
-
-//   auto path            = make_path_absolute("linear_programming/ns1687037/ns1687037.mps");
-//   auto solver_settings = pdlp_solver_settings_t{};
-//   solver_settings.iteration_limit         = 1000;
-//   solver_settings.per_constraint_residual = true;
-//   solver_settings.set_optimality_tolerance(1e-2);
-//   solver_settings.method = cuopt::linear_programming::method_t::PDLP;
-
-//   cuopt::mps_parser::mps_data_model_t op_problem1 =
-//     cuopt::mps_parser::parse_mps(path);
-//   cuopt::mps_parser::mps_data_model_t op_problem2 =
-//     cuopt::mps_parser::parse_mps(path);
-
-//   optimization_problem_solution_t solution1 =
-//     solve_lp(&handle1, op_problem1, solver_settings);
-//   RAFT_CUDA_TRY(cudaDeviceSynchronize());
-//   solver_settings.first_primal_feasible = true;
-//   optimization_problem_solution_t solution2 =
-//     solve_lp(&handle2, op_problem2, solver_settings);
-//   RAFT_CUDA_TRY(cudaDeviceSynchronize());
-
-//   EXPECT_EQ(solution1.get_termination_status(), pdlp_termination_status_t::IterationLimit);
-//   EXPECT_EQ(solution2.get_termination_status(), pdlp_termination_status_t::PrimalFeasible);
-// }
+// Should be added to google test
+#define EXPECT_NOT_NEAR(val1, val2, abs_error) \
+  EXPECT_FALSE((std::abs((val1) - (val2)) <= (abs_error)))
+
+TEST(pdlp_class, initial_solution_test)
+{
+  const raft::handle_t handle_{};
+
+  auto path = make_path_absolute("linear_programming/afiro_original.mps");
+  cuopt::mps_parser::mps_data_model_t mps_data_model =
+    cuopt::mps_parser::parse_mps(path);
+
+  auto op_problem = cuopt::linear_programming::mps_data_model_to_optimization_problem(
+    &handle_, mps_data_model);
+  cuopt::linear_programming::detail::problem_t problem(op_problem);
+
+  auto solver_settings = pdlp_solver_settings_t{};
+  // We are just testing initial scaling on initial solution scheme so we don't care about solver
+  solver_settings.iteration_limit = 0;
+  solver_settings.method          = cuopt::linear_programming::method_t::PDLP;
+  // Empty call solve to set the parameters and init the handler since calling pdlp object directly
+  // doesn't
+  solver_settings.pdlp_solver_mode = cuopt::linear_programming::pdlp_solver_mode_t::Methodical1;
+  solve_lp(op_problem, solver_settings);
+  EXPECT_EQ(cuopt::linear_programming::pdlp_hyper_params::initial_step_size_scaling, 1);
+  EXPECT_EQ(cuopt::linear_programming::pdlp_hyper_params::default_l_inf_ruiz_iterations, 5);
+  EXPECT_TRUE(cuopt::linear_programming::pdlp_hyper_params::do_pock_chambolle_scaling);
+  EXPECT_TRUE(cuopt::linear_programming::pdlp_hyper_params::do_ruiz_scaling);
+  EXPECT_EQ(cuopt::linear_programming::pdlp_hyper_params::default_alpha_pock_chambolle_rescaling,
+            1.0);
+
+  EXPECT_FALSE(cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution);
+  EXPECT_FALSE(
+    cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution);
+
+  {
+    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
+    auto start_solver = std::chrono::high_resolution_clock::now();
+    solver.run_solver(start_solver);
+    RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
+    EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
+    EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
+  }
+
+  // First add an initial primal then dual, then both, which shouldn't influence the values as the
+  // scale on initial option is not toggled
+  {
+    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
+    auto start_solver = std::chrono::high_resolution_clock::now();
+    std::vector initial_primal(op_problem.get_n_variables(), 1);
+    auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
+    solver.set_initial_primal_solution(d_initial_primal);
+    solver.run_solver(start_solver);
+    RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
+    EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
+    EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
+  }
+  {
+    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
+    auto start_solver = std::chrono::high_resolution_clock::now();
+    std::vector initial_dual(op_problem.get_n_constraints(), 1);
+    auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
+    solver.set_initial_dual_solution(d_initial_dual);
+    solver.run_solver(start_solver);
+    RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
+    EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
+    EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
+  }
+  {
+    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
+    auto start_solver = std::chrono::high_resolution_clock::now();
+    std::vector initial_primal(op_problem.get_n_variables(), 1);
+    auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
+    solver.set_initial_primal_solution(d_initial_primal);
+    std::vector initial_dual(op_problem.get_n_constraints(), 1);
+    auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
+    solver.set_initial_dual_solution(d_initial_dual);
+    solver.run_solver(start_solver);
+    RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
+    EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
+    EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
+  }
+
+  // Toggle the scale on initial solution while not providing should yield the same
+  {
+    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
+    auto start_solver = std::chrono::high_resolution_clock::now();
+    cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution = true;
+    solver.run_solver(start_solver);
+    RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
+    EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
+    EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
+    cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution = false;
+  }
+  {
+    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
+    auto start_solver = std::chrono::high_resolution_clock::now();
+    cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution = true;
+    solver.run_solver(start_solver);
+    RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
+    EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
+    EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
+    cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution = false;
+  }
+  {
+    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
+    auto start_solver = std::chrono::high_resolution_clock::now();
+    cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution = true;
+    cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution     = true;
+    solver.run_solver(start_solver);
+    RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
+    EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
+    EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
+    cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution = false;
+    cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution     = false;
+  }
+
+  // Asking for initial scaling on step size with initial solution being only primal or only dual
+  // should not break but not modify the step size
+  {
+    cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution = true;
+    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
+    auto start_solver = std::chrono::high_resolution_clock::now();
+    std::vector initial_primal(op_problem.get_n_variables(), 1);
+    auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
+    solver.set_initial_primal_solution(d_initial_primal);
+    solver.run_solver(start_solver);
+    RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
+    EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
+    EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
+    cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution = false;
+  }
+  {
+    cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution = true;
+    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
+    auto start_solver = std::chrono::high_resolution_clock::now();
+    std::vector initial_dual(op_problem.get_n_constraints(), 1);
+    auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
+    solver.set_initial_dual_solution(d_initial_dual);
+    solver.run_solver(start_solver);
+    RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
+    EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
+    EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
+    cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution = false;
+  }
+
+  // Asking for initial scaling on primal weight with initial solution being only primal or only
+  // dual should *not* break but the primal weight should not change
+  {
+    cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution = true;
+    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
+    auto start_solver = std::chrono::high_resolution_clock::now();
+    std::vector initial_primal(op_problem.get_n_variables(), 1);
+    auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
+    solver.set_initial_primal_solution(d_initial_primal);
+    solver.run_solver(start_solver);
+    EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
+    EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
+    cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution = false;
+  }
+  {
+    cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution = true;
+    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
+    auto start_solver = std::chrono::high_resolution_clock::now();
+    std::vector initial_dual(op_problem.get_n_constraints(), 1);
+    auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
+    solver.set_initial_dual_solution(d_initial_dual);
+    solver.run_solver(start_solver);
+    EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
+    EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
+    cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution = false;
+  }
+
+  // All 0 solution when given an initial primal and dual with scale on the step size should not
+  // break but not change primal weight and step size
+  {
+    cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution = true;
+    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
+    auto start_solver = std::chrono::high_resolution_clock::now();
+    std::vector initial_primal(op_problem.get_n_variables(), 0);
+    auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
+    solver.set_initial_primal_solution(d_initial_primal);
+    std::vector initial_dual(op_problem.get_n_constraints(), 0);
+    auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
+    solver.set_initial_dual_solution(d_initial_dual);
+    solver.run_solver(start_solver);
+    EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
+    EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
+    cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution = false;
+  }
+
+  // All 0 solution when given an initial primal and/or dual with scale on the primal weight is
+  // *not* an error but should not change primal weight and step size
+  {
+    cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution = true;
+    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
+    auto start_solver = std::chrono::high_resolution_clock::now();
+    std::vector initial_primal(op_problem.get_n_variables(), 0);
+    auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
+    solver.set_initial_primal_solution(d_initial_primal);
+    solver.run_solver(start_solver);
+    EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
+    EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
+    cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution = false;
+  }
+  {
+    cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution = true;
+    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
+    auto start_solver = std::chrono::high_resolution_clock::now();
+    std::vector initial_dual(op_problem.get_n_constraints(), 0);
+    auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
+    solver.set_initial_dual_solution(d_initial_dual);
+    solver.run_solver(start_solver);
+    EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
+    EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
+    cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution = false;
+  }
+  {
+    cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution = true;
+    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
+    auto start_solver = std::chrono::high_resolution_clock::now();
+    std::vector initial_primal(op_problem.get_n_variables(), 0);
+    auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
+    solver.set_initial_primal_solution(d_initial_primal);
+    std::vector initial_dual(op_problem.get_n_constraints(), 0);
+    auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
+    solver.set_initial_dual_solution(d_initial_dual);
+    solver.run_solver(start_solver);
+    EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
+    EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
+    cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution = false;
+  }
+
+  // A non-all-0 vector for both initial primal and dual set should trigger a modification in primal
+  // weight and step size
+  {
+    cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution = true;
+    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
+    auto start_solver = std::chrono::high_resolution_clock::now();
+    std::vector initial_primal(op_problem.get_n_variables(), 1);
+    auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
+    solver.set_initial_primal_solution(d_initial_primal);
+    std::vector initial_dual(op_problem.get_n_constraints(), 1);
+    auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
+    solver.set_initial_dual_solution(d_initial_dual);
+    solver.run_solver(start_solver);
+    EXPECT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
+    EXPECT_NOT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
+    cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution = false;
+  }
+  {
+    cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution = true;
+    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
+    auto start_solver = std::chrono::high_resolution_clock::now();
+    std::vector initial_primal(op_problem.get_n_variables(), 1);
+    auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
+    solver.set_initial_primal_solution(d_initial_primal);
+    std::vector initial_dual(op_problem.get_n_constraints(), 1);
+    auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
+    solver.set_initial_dual_solution(d_initial_dual);
+    solver.run_solver(start_solver);
+    EXPECT_NOT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
+    EXPECT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
+    cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution = false;
+  }
+  {
+    cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution = true;
+    cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution     = true;
+    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
+    auto start_solver = std::chrono::high_resolution_clock::now();
+    std::vector initial_primal(op_problem.get_n_variables(), 1);
+    auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
+    solver.set_initial_primal_solution(d_initial_primal);
+    std::vector initial_dual(op_problem.get_n_constraints(), 1);
+    auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
+    solver.set_initial_dual_solution(d_initial_dual);
+    solver.run_solver(start_solver);
+    EXPECT_NOT_NEAR(initial_step_size_afiro, solver.get_step_size_h(), factor_tolerance);
+    EXPECT_NOT_NEAR(initial_primal_weight_afiro, solver.get_primal_weight_h(), factor_tolerance);
+    cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution = false;
+    cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution     = false;
+  }
+}
+
+TEST(pdlp_class, initial_primal_weight_step_size_test)
+{
+  const raft::handle_t handle_{};
+
+  auto path = make_path_absolute("linear_programming/afiro_original.mps");
+  cuopt::mps_parser::mps_data_model_t mps_data_model =
+    cuopt::mps_parser::parse_mps(path);
+
+  auto op_problem = cuopt::linear_programming::mps_data_model_to_optimization_problem(
+    &handle_, mps_data_model);
+  cuopt::linear_programming::detail::problem_t problem(op_problem);
+
+  auto solver_settings = pdlp_solver_settings_t{};
+  // We are just testing initial scaling on initial solution scheme so we don't care about solver
+  solver_settings.iteration_limit = 0;
+  solver_settings.method          = cuopt::linear_programming::method_t::PDLP;
+  // Select the default/legacy solver with no action upon the initial scaling on initial solution
+  solver_settings.pdlp_solver_mode = cuopt::linear_programming::pdlp_solver_mode_t::Methodical1;
+  EXPECT_FALSE(cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution);
+  EXPECT_FALSE(
+    cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution);
+
+  // Check setting an initial primal weight and step size
+  {
+    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
+    auto start_solver                           = std::chrono::high_resolution_clock::now();
+    constexpr double test_initial_step_size     = 1.0;
+    constexpr double test_initial_primal_weight = 2.0;
+    solver.set_initial_primal_weight(test_initial_primal_weight);
+    solver.set_initial_step_size(test_initial_step_size);
+    solver.run_solver(start_solver);
+    RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
+    EXPECT_EQ(test_initial_step_size, solver.get_step_size_h());
+    EXPECT_EQ(test_initial_primal_weight, solver.get_primal_weight_h());
+  }
+
+  // Check that after setting an initial step size and primal weight, the computed one when adding
+  // an initial primal / dual is indeed different
+  {
+    // Launching without an inital step size / primal weight and query the value
+    cuopt::linear_programming::pdlp_hyper_params::update_primal_weight_on_initial_solution = true;
+    cuopt::linear_programming::pdlp_hyper_params::update_step_size_on_initial_solution     = true;
+    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
+    auto start_solver = std::chrono::high_resolution_clock::now();
+    std::vector initial_primal(op_problem.get_n_variables(), 1);
+    auto d_initial_primal = device_copy(initial_primal, handle_.get_stream());
+    solver.set_initial_primal_solution(d_initial_primal);
+    std::vector initial_dual(op_problem.get_n_constraints(), 1);
+    auto d_initial_dual = device_copy(initial_dual, handle_.get_stream());
+    solver.set_initial_dual_solution(d_initial_dual);
+    solver.run_solver(start_solver);
+    const double previous_step_size     = solver.get_step_size_h();
+    const double previous_primal_weight = solver.get_primal_weight_h();
+
+    // Start again but with an initial and check the impact
+    cuopt::linear_programming::detail::pdlp_solver_t solver2(problem, solver_settings);
+    start_solver                                = std::chrono::high_resolution_clock::now();
+    constexpr double test_initial_step_size     = 1.0;
+    constexpr double test_initial_primal_weight = 2.0;
+    solver2.set_initial_primal_weight(test_initial_primal_weight);
+    solver2.set_initial_step_size(test_initial_step_size);
+    solver2.set_initial_primal_solution(d_initial_primal);
+    solver2.set_initial_dual_solution(d_initial_dual);
+    solver2.run_solver(start_solver);
+    RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
+    const double sovler2_step_size     = solver2.get_step_size_h();
+    const double sovler2_primal_weight = solver2.get_primal_weight_h();
+    EXPECT_NOT_NEAR(previous_step_size, sovler2_step_size, factor_tolerance);
+    EXPECT_NOT_NEAR(previous_primal_weight, sovler2_primal_weight, factor_tolerance);
+
+    // Again but with an initial k which should change the step size only, not the primal weight
+    cuopt::linear_programming::detail::pdlp_solver_t solver3(problem, solver_settings);
+    start_solver = std::chrono::high_resolution_clock::now();
+    solver3.set_initial_primal_weight(test_initial_primal_weight);
+    solver3.set_initial_step_size(test_initial_step_size);
+    solver3.set_initial_primal_solution(d_initial_primal);
+    solver3.set_initial_k(10000);
+    solver3.set_initial_dual_solution(d_initial_dual);
+    solver3.set_initial_dual_solution(d_initial_dual);
+    solver3.run_solver(start_solver);
+    RAFT_CUDA_TRY(cudaStreamSynchronize(handle_.get_stream()));
+    EXPECT_NOT_NEAR(sovler2_step_size, solver3.get_step_size_h(), factor_tolerance);
+    EXPECT_NEAR(sovler2_primal_weight, solver3.get_primal_weight_h(), factor_tolerance);
+  }
+}
+
+TEST(pdlp_class, initial_rhs_and_c)
+{
+  const raft::handle_t handle_{};
+
+  auto path = make_path_absolute("linear_programming/afiro_original.mps");
+  cuopt::mps_parser::mps_data_model_t mps_data_model =
+    cuopt::mps_parser::parse_mps(path);
+
+  auto op_problem = cuopt::linear_programming::mps_data_model_to_optimization_problem(
+    &handle_, mps_data_model);
+  cuopt::linear_programming::detail::problem_t problem(op_problem);
+
+  cuopt::linear_programming::detail::pdlp_solver_t solver(problem);
+  constexpr double test_initial_primal_factor = 1.0;
+  constexpr double test_initial_dual_factor   = 2.0;
+  solver.set_relative_dual_tolerance_factor(test_initial_dual_factor);
+  solver.set_relative_primal_tolerance_factor(test_initial_primal_factor);
+
+  EXPECT_EQ(solver.get_relative_dual_tolerance_factor(), test_initial_dual_factor);
+  EXPECT_EQ(solver.get_relative_primal_tolerance_factor(), test_initial_primal_factor);
+}
+
+TEST(pdlp_class, per_constraint_test)
+{
+  /*
+   * Define the following LP:
+   * x1=0.01 <= 0
+   * x2=0.01 <= 0
+   * x3=0.1  <= 0
+   *
+   * With a tol of 0.1 per constraint will pass but the L2 version will not as L2 of primal residual
+   * will be 0.1009
+   */
+  raft::handle_t handle;
+  auto op_problem = optimization_problem_t(&handle);
+
+  std::vector A_host           = {1.0, 1.0, 1.0};
+  std::vector indices_host        = {0, 1, 2};
+  std::vector offset_host         = {0, 1, 2, 3};
+  std::vector b_host           = {0.0, 0.0, 0.0};
+  std::vector h_initial_primal = {0.02, 0.03, 0.1};
+  rmm::device_uvector d_initial_primal(3, handle.get_stream());
+  raft::copy(
+    d_initial_primal.data(), h_initial_primal.data(), h_initial_primal.size(), handle.get_stream());
+
+  op_problem.set_csr_constraint_matrix(A_host.data(),
+                                       A_host.size(),
+                                       indices_host.data(),
+                                       indices_host.size(),
+                                       offset_host.data(),
+                                       offset_host.size());
+  op_problem.set_constraint_lower_bounds(b_host.data(), b_host.size());
+  op_problem.set_constraint_upper_bounds(b_host.data(), b_host.size());
+  op_problem.set_objective_coefficients(b_host.data(), b_host.size());
+
+  auto problem = cuopt::linear_programming::detail::problem_t(op_problem);
+
+  pdlp_solver_settings_t solver_settings;
+  solver_settings.tolerances.relative_primal_tolerance = 0;  // Shouldn't matter
+  solver_settings.tolerances.absolute_primal_tolerance = 0.1;
+  solver_settings.tolerances.relative_dual_tolerance   = 0;  // Shoudln't matter
+  solver_settings.tolerances.absolute_dual_tolerance   = 0.1;
+  solver_settings.method                               = cuopt::linear_programming::method_t::PDLP;
+
+  // First solve without the per constraint and it should break
+  {
+    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
+
+    raft::copy(solver.pdhg_solver_.get_primal_solution().data(),
+               d_initial_primal.data(),
+               d_initial_primal.size(),
+               handle.get_stream());
+
+    auto& current_termination_strategy = solver.get_current_termination_strategy();
+    pdlp_termination_status_t termination_average =
+      current_termination_strategy.evaluate_termination_criteria(solver.pdhg_solver_,
+                                                                 d_initial_primal,
+                                                                 d_initial_primal,
+                                                                 problem.combined_bounds,
+                                                                 problem.objective_coefficients);
+
+    EXPECT_TRUE(termination_average != pdlp_termination_status_t::Optimal);
+  }
+  {
+    solver_settings.per_constraint_residual = true;
+    cuopt::linear_programming::detail::pdlp_solver_t solver(problem, solver_settings);
+
+    raft::copy(solver.pdhg_solver_.get_primal_solution().data(),
+               d_initial_primal.data(),
+               d_initial_primal.size(),
+               handle.get_stream());
+
+    auto& current_termination_strategy = solver.get_current_termination_strategy();
+    pdlp_termination_status_t termination_average =
+      current_termination_strategy.evaluate_termination_criteria(solver.pdhg_solver_,
+                                                                 d_initial_primal,
+                                                                 d_initial_primal,
+                                                                 problem.combined_bounds,
+                                                                 problem.objective_coefficients);
+    EXPECT_EQ(current_termination_strategy.get_convergence_information()
+                .get_relative_linf_primal_residual()
+                .value(handle.get_stream()),
+              0.1);
+  }
+}
+
+TEST(pdlp_class, best_primal_so_far_iteration)
+{
+  const raft::handle_t handle1{};
+  const raft::handle_t handle2{};
+
+  auto path            = make_path_absolute("linear_programming/ns1687037/ns1687037.mps");
+  auto solver_settings = pdlp_solver_settings_t{};
+  solver_settings.iteration_limit         = 3000;
+  solver_settings.per_constraint_residual = true;
+  solver_settings.method                  = cuopt::linear_programming::method_t::PDLP;
+  solver_settings.presolve                = false;
+
+  cuopt::mps_parser::mps_data_model_t op_problem1 =
+    cuopt::mps_parser::parse_mps(path);
+  cuopt::mps_parser::mps_data_model_t op_problem2 =
+    cuopt::mps_parser::parse_mps(path);
+
+  optimization_problem_solution_t solution1 =
+    solve_lp(&handle1, op_problem1, solver_settings);
+  RAFT_CUDA_TRY(cudaDeviceSynchronize());
+  solver_settings.save_best_primal_so_far = true;
+  optimization_problem_solution_t solution2 =
+    solve_lp(&handle2, op_problem2, solver_settings);
+  RAFT_CUDA_TRY(cudaDeviceSynchronize());
+
+  EXPECT_TRUE(solution2.get_additional_termination_information().l2_primal_residual <
+              solution1.get_additional_termination_information().l2_primal_residual);
+}
+
+TEST(pdlp_class, best_primal_so_far_time)
+{
+  const raft::handle_t handle1{};
+  const raft::handle_t handle2{};
+
+  auto path                  = make_path_absolute("linear_programming/ns1687037/ns1687037.mps");
+  auto solver_settings       = pdlp_solver_settings_t{};
+  solver_settings.time_limit = 2;
+  solver_settings.per_constraint_residual = true;
+  solver_settings.pdlp_solver_mode        = cuopt::linear_programming::pdlp_solver_mode_t::Stable1;
+  solver_settings.method                  = cuopt::linear_programming::method_t::PDLP;
+  solver_settings.presolve                = false;
+
+  cuopt::mps_parser::mps_data_model_t op_problem1 =
+    cuopt::mps_parser::parse_mps(path);
+  cuopt::mps_parser::mps_data_model_t op_problem2 =
+    cuopt::mps_parser::parse_mps(path);
+
+  optimization_problem_solution_t solution1 =
+    solve_lp(&handle1, op_problem1, solver_settings);
+  RAFT_CUDA_TRY(cudaDeviceSynchronize());
+  solver_settings.save_best_primal_so_far = true;
+  optimization_problem_solution_t solution2 =
+    solve_lp(&handle2, op_problem2, solver_settings);
+  RAFT_CUDA_TRY(cudaDeviceSynchronize());
+
+  EXPECT_TRUE(solution2.get_additional_termination_information().l2_primal_residual <
+              solution1.get_additional_termination_information().l2_primal_residual);
+}
+
+TEST(pdlp_class, first_primal_feasible)
+{
+  const raft::handle_t handle1{};
+  const raft::handle_t handle2{};
+
+  auto path            = make_path_absolute("linear_programming/ns1687037/ns1687037.mps");
+  auto solver_settings = pdlp_solver_settings_t{};
+  solver_settings.iteration_limit         = 1000;
+  solver_settings.per_constraint_residual = true;
+  solver_settings.set_optimality_tolerance(1e-2);
+  solver_settings.method = cuopt::linear_programming::method_t::PDLP;
+
+  cuopt::mps_parser::mps_data_model_t op_problem1 =
+    cuopt::mps_parser::parse_mps(path);
+  cuopt::mps_parser::mps_data_model_t op_problem2 =
+    cuopt::mps_parser::parse_mps(path);
+
+  optimization_problem_solution_t solution1 =
+    solve_lp(&handle1, op_problem1, solver_settings);
+  RAFT_CUDA_TRY(cudaDeviceSynchronize());
+  solver_settings.first_primal_feasible = true;
+  optimization_problem_solution_t solution2 =
+    solve_lp(&handle2, op_problem2, solver_settings);
+  RAFT_CUDA_TRY(cudaDeviceSynchronize());
+
+  EXPECT_EQ(solution1.get_termination_status(), pdlp_termination_status_t::IterationLimit);
+  EXPECT_EQ(solution2.get_termination_status(), pdlp_termination_status_t::PrimalFeasible);
+}
 
 TEST(pdlp_class, warm_start)
 {
@@ -848,8 +827,7 @@ TEST(pdlp_class, warm_start)
     solver_settings.set_optimality_tolerance(1e-2);
     solver_settings.detect_infeasibility = false;
     solver_settings.method               = cuopt::linear_programming::method_t::PDLP;
-    // Disable presolve to test warm start
-    solver_settings.presolve = false;
+    solver_settings.presolve             = false;
 
     cuopt::mps_parser::mps_data_model_t mps_data_model =
       cuopt::mps_parser::parse_mps(path);
@@ -908,9 +886,8 @@ TEST(pdlp_class, run_empty_matrix_pdlp)
   cuopt::mps_parser::mps_data_model_t op_problem =
     cuopt::mps_parser::parse_mps(path);
 
-  auto solver_settings   = pdlp_solver_settings_t{};
-  solver_settings.method = cuopt::linear_programming::method_t::PDLP;
-  // Reduces to empty problem
+  auto solver_settings     = pdlp_solver_settings_t{};
+  solver_settings.method   = cuopt::linear_programming::method_t::PDLP;
   solver_settings.presolve = false;
 
   optimization_problem_solution_t solution =
@@ -927,9 +904,8 @@ TEST(pdlp_class, run_empty_matrix_dual_simplex)
   cuopt::mps_parser::mps_data_model_t op_problem =
     cuopt::mps_parser::parse_mps(path);
 
-  auto solver_settings   = pdlp_solver_settings_t{};
-  solver_settings.method = cuopt::linear_programming::method_t::Concurrent;
-  // Reduces to empty problem
+  auto solver_settings     = pdlp_solver_settings_t{};
+  solver_settings.method   = cuopt::linear_programming::method_t::Concurrent;
   solver_settings.presolve = false;
 
   optimization_problem_solution_t solution =
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 030a805478..cc4e709fca 100644
--- a/python/cuopt/cuopt/tests/linear_programming/test_lp_solver.py
+++ b/python/cuopt/cuopt/tests/linear_programming/test_lp_solver.py
@@ -526,6 +526,7 @@ def test_warm_start():
     settings.set_parameter(CUOPT_PDLP_SOLVER_MODE, PDLPSolverMode.Stable2)
     settings.set_optimality_tolerance(1e-3)
     settings.set_parameter(CUOPT_INFEASIBILITY_DETECTION, False)
+    settings.set_parameter(CUOPT_PRESOLVE, False)
 
     # Solving from scratch until 1e-3
     solution = solver.Solve(data_model_obj, settings)

From cb8abca5d40f86b9b65f97f5bee6c5b956e08068 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Thu, 31 Jul 2025 00:57:11 -0700
Subject: [PATCH 040/108] Revert cmake papilo path

---
 cpp/CMakeLists.txt | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt
index d33de02794..08dbe038eb 100644
--- a/cpp/CMakeLists.txt
+++ b/cpp/CMakeLists.txt
@@ -162,9 +162,8 @@ find_package(TBB REQUIRED)
 
 FetchContent_Declare(
   papilo
-  SOURCE_DIR "/home/hugo/rapids/papilo"
-  # GIT_REPOSITORY "https://github.com/scipopt/papilo.git"
-  # GIT_TAG "main"
+  GIT_REPOSITORY "https://github.com/scipopt/papilo.git"
+  GIT_TAG "main"
 )
 
 set(BUILD_TESTING OFF CACHE BOOL "Disable test build for papilo")

From f6df7ee86d84cca2bbdd96f2e25e8196eb103dbd Mon Sep 17 00:00:00 2001
From: Ramakrishna Prabhu 
Date: Thu, 31 Jul 2025 09:49:02 -0500
Subject: [PATCH 041/108] revert changes

---
 python/libcuopt/CMakeLists.txt | 21 ---------------------
 1 file changed, 21 deletions(-)

diff --git a/python/libcuopt/CMakeLists.txt b/python/libcuopt/CMakeLists.txt
index 643bdcb20b..06b9a8cefb 100644
--- a/python/libcuopt/CMakeLists.txt
+++ b/python/libcuopt/CMakeLists.txt
@@ -46,27 +46,6 @@ FetchContent_Declare(
 )
 FetchContent_MakeAvailable(argparse)
 
-# Try to find Boost in the system
-find_package(Boost 1.65 QUIET COMPONENTS system filesystem thread)
-
-if(NOT Boost_FOUND)
-  message(STATUS "Boost not found in system, trying alternative locations...")
-
-  # Try common Boost installation paths
-  set(Boost_USE_STATIC_LIBS OFF)
-  set(Boost_USE_MULTITHREADED ON)
-  set(Boost_USE_STATIC_RUNTIME OFF)
-
-  # Try to find Boost again with more options
-  find_package(Boost 1.65 COMPONENTS system filesystem thread)
-
-  if(NOT Boost_FOUND)
-    message(FATAL_ERROR "Boost not found. Please install Boost 1.65 or later with components: system, filesystem, thread")
-  endif()
-else()
-  message(STATUS "Using system Boost installation")
-endif()
-
 set(BUILD_TESTS OFF)
 set(BUILD_BENCHMARKS OFF)
 set(CUOPT_BUILD_TESTUTIL OFF)

From 2f1fec80332c7b8d0013cdbe34d0ff3680488611 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Thu, 31 Jul 2025 07:51:47 -0700
Subject: [PATCH 042/108] Skip postsolve steps if presolve not running

---
 cpp/src/mip/solve.cu       | 37 ++++++++++++++++++-------------------
 cpp/tests/mip/unit_test.cu |  5 +++++
 2 files changed, 23 insertions(+), 19 deletions(-)

diff --git a/cpp/src/mip/solve.cu b/cpp/src/mip/solve.cu
index 27e46cc591..796c681682 100644
--- a/cpp/src/mip/solve.cu
+++ b/cpp/src/mip/solve.cu
@@ -214,12 +214,11 @@ mip_solution_t solve_mip(optimization_problem_t& op_problem,
     // this is for PDLP, i think this should be part of pdlp solver
     setup_device_symbols(op_problem.get_handle_ptr()->get_stream());
 
-    auto reduced_solution = run_mip(problem, settings, timer);
-
-    auto primal_solution = cuopt::device_copy(reduced_solution.get_solution(),
-                                              op_problem.get_handle_ptr()->get_stream());
+    auto sol = run_mip(problem, settings, timer);
 
     if (run_presolve) {
+      auto primal_solution =
+        cuopt::device_copy(sol.get_solution(), op_problem.get_handle_ptr()->get_stream());
       rmm::device_uvector dual_solution(0, op_problem.get_handle_ptr()->get_stream());
       rmm::device_uvector reduced_costs(0, op_problem.get_handle_ptr()->get_stream());
       presolver->undo(primal_solution,
@@ -227,24 +226,24 @@ mip_solution_t solve_mip(optimization_problem_t& op_problem,
                       reduced_costs,
                       cuopt::linear_programming::problem_category_t::MIP,
                       op_problem.get_handle_ptr()->get_stream());
-    }
+      detail::problem_t full_problem(op_problem);
+      detail::solution_t full_sol(full_problem);
+      full_sol.copy_new_assignment(cuopt::host_copy(primal_solution));
+      full_sol.compute_feasibility();
+      if (!full_sol.get_feasible()) {
+        CUOPT_LOG_WARN("The solution is not feasible after post solve");
+      }
 
-    detail::problem_t full_problem(op_problem);
-    detail::solution_t full_sol(full_problem);
-    full_sol.copy_new_assignment(cuopt::host_copy(primal_solution));
-    full_sol.compute_feasibility();
-    if (!full_sol.get_feasible()) {
-      CUOPT_LOG_WARN("The solution is not feasible after post solve");
-    }
+      auto full_stats = sol.get_stats();
+      // add third party presolve time to cuopt presolve time
+      full_stats.presolve_time += presolve_time;
 
-    auto full_stats = reduced_solution.get_stats();
-    // add third party presolve time to cuopt presolve time
-    full_stats.presolve_time += presolve_time;
+      // FIXME:: reduced_solution.get_stats() is not correct, we need to compute the stats for the
+      // full problem
+      full_sol.post_process_completed = true;  // hack
+      sol                             = full_sol.get_solution(true, full_stats, true);
+    }
 
-    // FIXME:: reduced_solution.get_stats() is not correct, we need to compute the stats for the
-    // full problem
-    full_sol.post_process_completed = true;  // hack
-    auto sol                        = full_sol.get_solution(true, full_stats, true);
     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());
diff --git a/cpp/tests/mip/unit_test.cu b/cpp/tests/mip/unit_test.cu
index e87efd8605..b7eb242220 100644
--- a/cpp/tests/mip/unit_test.cu
+++ b/cpp/tests/mip/unit_test.cu
@@ -140,6 +140,7 @@ TEST(LPTest, TestSampleLP)
   cuopt::linear_programming::pdlp_solver_settings_t settings{};
   settings.set_optimality_tolerance(1e-4);
   settings.time_limit = 5;
+  settings.presolve   = false;
 
   auto result = cuopt::linear_programming::solve_lp(&handle, problem, settings);
 
@@ -154,6 +155,8 @@ TEST(ErrorTest, TestError)
 
   cuopt::linear_programming::mip_solver_settings_t settings{};
   settings.time_limit = 5;
+  settings.presolve   = false;
+
   // Set constraint bounds
   std::vector lower_bounds = {1.0};
   std::vector upper_bounds = {0.0};
@@ -184,6 +187,7 @@ TEST_P(MILPTestParams, TestSampleMILP)
   settings.time_limit      = 5;
   settings.mip_scaling     = scaling;
   settings.heuristics_only = heuristics_only;
+  settings.presolve        = false;
 
   auto result = cuopt::linear_programming::solve_mip(&handle, problem, settings);
 
@@ -204,6 +208,7 @@ TEST_P(MILPTestParams, TestSingleVarMILP)
   settings.time_limit      = 5;
   settings.mip_scaling     = scaling;
   settings.heuristics_only = heuristics_only;
+  settings.presolve        = false;
 
   auto result = cuopt::linear_programming::solve_mip(&handle, problem, settings);
 

From 472fac4632d0e9c6f05fbbf05516711de256b74b Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Thu, 31 Jul 2025 07:55:57 -0700
Subject: [PATCH 043/108] Fix mip cpp unit tests

---
 cpp/tests/mip/integer_with_real_bounds.cu | 8 ++++++--
 cpp/tests/mip/mip_utils.cuh               | 6 +++++-
 2 files changed, 11 insertions(+), 3 deletions(-)

diff --git a/cpp/tests/mip/integer_with_real_bounds.cu b/cpp/tests/mip/integer_with_real_bounds.cu
index 0991a0f2af..530514c7b1 100644
--- a/cpp/tests/mip/integer_with_real_bounds.cu
+++ b/cpp/tests/mip/integer_with_real_bounds.cu
@@ -26,8 +26,12 @@
 namespace cuopt::linear_programming::test {
 TEST(mip_solve, integer_with_real_bounds_test)
 {
-  auto [termination_status, obj_val, lb] = test_mps_file("mip/integer-with-real-bounds.mps");
+  auto time_limit      = 1;
+  auto heuristics_only = true;
+  auto presolve        = false;
+  auto [termination_status, obj_val, lb] =
+    test_mps_file("mip/integer-with-real-bounds.mps", time_limit, heuristics_only, presolve);
   EXPECT_EQ(termination_status, mip_termination_status_t::Optimal);
   EXPECT_NEAR(obj_val, 4, 1e-5);
 }
-}  // namespace cuopt::linear_programming::test
\ No newline at end of file
+}  // namespace cuopt::linear_programming::test
diff --git a/cpp/tests/mip/mip_utils.cuh b/cpp/tests/mip/mip_utils.cuh
index ffece118e2..b53952e127 100644
--- a/cpp/tests/mip/mip_utils.cuh
+++ b/cpp/tests/mip/mip_utils.cuh
@@ -112,7 +112,10 @@ static void test_constraint_sanity_per_row(
 }
 
 static std::tuple test_mps_file(
-  std::string test_instance, double time_limit = 1, bool heuristics_only = true)
+  std::string test_instance,
+  double time_limit    = 1,
+  bool heuristics_only = true,
+  bool presolve        = true)
 {
   const raft::handle_t handle_{};
 
@@ -123,6 +126,7 @@ static std::tuple test_mps_file(
   mip_solver_settings_t settings;
   settings.time_limit                  = time_limit;
   settings.heuristics_only             = heuristics_only;
+  settings.presolve                    = presolve;
   mip_solution_t solution = solve_mip(&handle_, problem, settings);
   return std::make_tuple(solution.get_termination_status(),
                          solution.get_objective_value(),

From 2bf7363e47df5c1f50bcdfc21edc0784335b311a Mon Sep 17 00:00:00 2001
From: Ramakrishna Prabhu 
Date: Thu, 31 Jul 2025 10:06:08 -0500
Subject: [PATCH 044/108] update deps

---
 ci/build_wheel_libcuopt.sh | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/ci/build_wheel_libcuopt.sh b/ci/build_wheel_libcuopt.sh
index be78e8963a..2512bdcd46 100755
--- a/ci/build_wheel_libcuopt.sh
+++ b/ci/build_wheel_libcuopt.sh
@@ -21,6 +21,25 @@ source rapids-init-pip
 package_name="libcuopt"
 package_dir="python/libcuopt"
 
+# Install Boost
+if [ -f /etc/os-release ]; then
+    . /etc/os-release
+    if [[ "$ID" == "rocky" ]]; then
+        echo "Detected Rocky Linux. Installing Boost via dnf..."
+        dnf install -y boost-devel
+    elif [[ "$ID" == "ubuntu" ]]; then
+        echo "Detected Ubuntu. Installing Boost via apt..."
+        apt-get update
+        apt-get install -y libboost-dev
+    else
+        echo "Unknown OS: $ID. Please install Boost development libraries manually."
+        exit 1
+    fi
+else
+    echo "/etc/os-release not found. Cannot determine OS. Please install Boost development libraries manually."
+    exit 1
+fi
+
 export SKBUILD_CMAKE_ARGS="-DCUOPT_BUILD_WHEELS=ON;-DDISABLE_DEPRECATION_WARNING=ON"
 
 # For pull requests we are enabling assert mode.

From 64bc65b026a921ecb839185078607b82bd792c5c Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Thu, 31 Jul 2025 08:48:22 -0700
Subject: [PATCH 045/108] Enable more methods and fill dual with nans

---
 cpp/src/linear_programming/solve.cu           | 47 ++++++------
 cpp/src/mip/presolve/third_party_presolve.cu  | 57 ++++++++-------
 cpp/tests/linear_programming/pdlp_test.cu     |  3 +-
 .../utilities/pdlp_test_utilities.cuh         | 73 ++++++++++---------
 4 files changed, 94 insertions(+), 86 deletions(-)

diff --git a/cpp/src/linear_programming/solve.cu b/cpp/src/linear_programming/solve.cu
index fe2b8222bc..fb0e992010 100644
--- a/cpp/src/linear_programming/solve.cu
+++ b/cpp/src/linear_programming/solve.cu
@@ -634,41 +634,42 @@ optimization_problem_solution_t solve_lp(optimization_problem_tget_stream());
-    auto dual_solution =
-      cuopt::device_copy(solution.get_dual_solution(), op_problem.get_handle_ptr()->get_stream());
-    auto reduced_costs =
-      cuopt::device_copy(solution.get_reduced_cost(), op_problem.get_handle_ptr()->get_stream());
     if (run_presolve) {
+      auto primal_solution = cuopt::device_copy(solution.get_primal_solution(),
+                                                op_problem.get_handle_ptr()->get_stream());
+      auto dual_solution =
+        cuopt::device_copy(solution.get_dual_solution(), op_problem.get_handle_ptr()->get_stream());
+      auto reduced_costs =
+        cuopt::device_copy(solution.get_reduced_cost(), op_problem.get_handle_ptr()->get_stream());
+
       presolver->undo(primal_solution,
                       dual_solution,
                       reduced_costs,
                       cuopt::linear_programming::problem_category_t::LP,
                       op_problem.get_handle_ptr()->get_stream());
-    }
 
-    auto full_stats = solution.get_additional_termination_information();
-    // add third party presolve time to cuopt presolve time
-    full_stats.solve_time += presolve_time;
-
-    // Create a new solution with the full problem solution
-    optimization_problem_solution_t sol(primal_solution,
-                                                  dual_solution,
-                                                  reduced_costs,
-                                                  solution.get_pdlp_warm_start_data(),
-                                                  op_problem.get_objective_name(),
-                                                  op_problem.get_variable_names(),
-                                                  op_problem.get_row_names(),
-                                                  full_stats,
-                                                  solution.get_termination_status());
+      auto full_stats = solution.get_additional_termination_information();
+      // add third party presolve time to cuopt presolve time
+      full_stats.solve_time += presolve_time;
+
+      // Create a new solution with the full problem solution
+      solution = optimization_problem_solution_t(primal_solution,
+                                                           dual_solution,
+                                                           reduced_costs,
+                                                           solution.get_pdlp_warm_start_data(),
+                                                           op_problem.get_objective_name(),
+                                                           op_problem.get_variable_names(),
+                                                           op_problem.get_row_names(),
+                                                           full_stats,
+                                                           solution.get_termination_status());
+    }
 
     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());
+      solution.write_to_sol_file(settings.sol_file, op_problem.get_handle_ptr()->get_stream());
     }
 
-    return sol;
+    return solution;
   } 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()};
diff --git a/cpp/src/mip/presolve/third_party_presolve.cu b/cpp/src/mip/presolve/third_party_presolve.cu
index 8788d654f9..6fda07db60 100644
--- a/cpp/src/mip/presolve/third_party_presolve.cu
+++ b/cpp/src/mip/presolve/third_party_presolve.cu
@@ -231,7 +231,7 @@ void set_presolve_methods(papilo::Presolve& presolver, problem_category_t c
   // fast presolvers
   presolver.addPresolveMethod(uptr(new papilo::SingletonCols()));
   presolver.addPresolveMethod(uptr(new papilo::CoefficientStrengthening()));
-  // presolver.addPresolveMethod(uptr(new papilo::ConstraintPropagation()));
+  presolver.addPresolveMethod(uptr(new papilo::ConstraintPropagation()));
 
   // medium presolvers
   presolver.addPresolveMethod(uptr(new papilo::FixContinuous()));
@@ -239,7 +239,7 @@ void set_presolve_methods(papilo::Presolve& presolver, problem_category_t c
   presolver.addPresolveMethod(uptr(new papilo::ParallelRowDetection()));
   presolver.addPresolveMethod(uptr(new papilo::ParallelColDetection()));
   // FIXME: Postsolve fails with this method
-  //  presolver.addPresolveMethod(uptr(new papilo::SingletonStuffing()));
+  // presolver.addPresolveMethod(uptr(new papilo::SingletonStuffing()));
   presolver.addPresolveMethod(uptr(new papilo::DualFix()));
   presolver.addPresolveMethod(uptr(new papilo::SimplifyInequalities()));
 
@@ -248,12 +248,12 @@ void set_presolve_methods(papilo::Presolve& presolver, problem_category_t c
   presolver.addPresolveMethod(uptr(new papilo::DominatedCols()));
   presolver.addPresolveMethod(uptr(new papilo::Probing()));
 
-  if (category == problem_category_t::MIP) {
-    presolver.addPresolveMethod(uptr(new papilo::DualInfer));
-    presolver.addPresolveMethod(uptr(new papilo::SimpleSubstitution()));
-    presolver.addPresolveMethod(uptr(new papilo::Sparsify()));
-    presolver.addPresolveMethod(uptr(new papilo::Substitution()));
-  }
+  // if (category == problem_category_t::MIP) {
+  presolver.addPresolveMethod(uptr(new papilo::DualInfer));
+  presolver.addPresolveMethod(uptr(new papilo::SimpleSubstitution()));
+  presolver.addPresolveMethod(uptr(new papilo::Sparsify()));
+  presolver.addPresolveMethod(uptr(new papilo::Substitution()));
+  // }
 }
 
 template 
@@ -262,15 +262,13 @@ void set_presolve_options(papilo::Presolve& presolver,
                           f_t absolute_tolerance,
                           double time_limit)
 {
-  presolver.getPresolveOptions().tlim                            = time_limit;
-  presolver.getPresolveOptions().epsilon                         = absolute_tolerance;
-  presolver.getPresolveOptions().feastol                         = absolute_tolerance;
-  presolver.getPresolveOptions().threads                         = 1;
-  presolver.getPresolveOptions().constraint_propagation_parallel = 0;
-  if (category == problem_category_t::LP) {
-    presolver.getPresolveOptions().componentsmaxint = -1;
-    presolver.getPresolveOptions().detectlindep     = 0;
-  }
+  presolver.getPresolveOptions().tlim    = time_limit;
+  presolver.getPresolveOptions().epsilon = absolute_tolerance;
+  presolver.getPresolveOptions().feastol = absolute_tolerance;
+  // if (category == problem_category_t::LP) {
+  //   presolver.getPresolveOptions().componentsmaxint = -1;
+  //   presolver.getPresolveOptions().detectlindep     = 0;
+  // }
 }
 
 template 
@@ -320,11 +318,11 @@ void third_party_presolve_t::undo(rmm::device_uvector& primal_sol
 
   papilo::Solution reduced_sol(primal_sol_vec_h);
   papilo::Solution full_sol;
-  if (category == problem_category_t::LP) {
-    reduced_sol.type         = papilo::SolutionType::kPrimalDual;
-    reduced_sol.dual         = dual_sol_vec_h;
-    reduced_sol.reducedCosts = reduced_costs_vec_h;
-  }
+  // if (category == problem_category_t::LP) {
+  //   reduced_sol.type         = papilo::SolutionType::kPrimalDual;
+  //   reduced_sol.dual         = dual_sol_vec_h;
+  //   reduced_sol.reducedCosts = reduced_costs_vec_h;
+  // }
 
   papilo::Message Msg{};
   Msg.setVerbosityLevel(papilo::VerbosityLevel::kQuiet);
@@ -335,12 +333,17 @@ void third_party_presolve_t::undo(rmm::device_uvector& primal_sol
   check_postsolve_status(status);
 
   primal_solution.resize(full_sol.primal.size(), stream_view);
-  dual_solution.resize(full_sol.dual.size(), stream_view);
-  reduced_costs.resize(full_sol.reducedCosts.size(), stream_view);
+  dual_solution.resize(full_sol.primal.size(), stream_view);
+  reduced_costs.resize(full_sol.primal.size(), stream_view);
   raft::copy(primal_solution.data(), full_sol.primal.data(), full_sol.primal.size(), stream_view);
-  raft::copy(dual_solution.data(), full_sol.dual.data(), full_sol.dual.size(), stream_view);
-  raft::copy(
-    reduced_costs.data(), full_sol.reducedCosts.data(), full_sol.reducedCosts.size(), stream_view);
+  thrust::fill(rmm::exec_policy(stream_view),
+               dual_solution.data(),
+               dual_solution.data() + dual_solution.size(),
+               std::numeric_limits::signaling_NaN());
+  thrust::fill(rmm::exec_policy(stream_view),
+               reduced_costs.data(),
+               reduced_costs.data() + reduced_costs.size(),
+               std::numeric_limits::signaling_NaN());
 }
 
 #if MIP_INSTANTIATE_FLOAT
diff --git a/cpp/tests/linear_programming/pdlp_test.cu b/cpp/tests/linear_programming/pdlp_test.cu
index 249ab487d0..ca828d66b3 100644
--- a/cpp/tests/linear_programming/pdlp_test.cu
+++ b/cpp/tests/linear_programming/pdlp_test.cu
@@ -192,7 +192,7 @@ TEST(pdlp_class, run_sub_mittleman)
 {
   std::vector>      // Expected objective value
-    instances{{"graph40-40", -300.0},
+    instances{                        // {"graph40-40", -300.0},
               {"ex10", 100.0003411893773},
               {"datt256_lp", 255.9992298290425},
               {"woodlands09", 0.0},
@@ -207,6 +207,7 @@ TEST(pdlp_class, run_sub_mittleman)
     const auto& name                    = entry.first;
     const auto expected_objective_value = entry.second;
 
+    std::cout << "Running " << name << std::endl;
     auto path = make_path_absolute("linear_programming/" + name + "/" + name + ".mps");
     cuopt::mps_parser::mps_data_model_t op_problem =
       cuopt::mps_parser::parse_mps(path);
diff --git a/cpp/tests/linear_programming/utilities/pdlp_test_utilities.cuh b/cpp/tests/linear_programming/utilities/pdlp_test_utilities.cuh
index 190c0e7518..7a624e0232 100644
--- a/cpp/tests/linear_programming/utilities/pdlp_test_utilities.cuh
+++ b/cpp/tests/linear_programming/utilities/pdlp_test_utilities.cuh
@@ -69,7 +69,8 @@ static void test_objective_sanity(
 static void test_constraint_sanity(
   const cuopt::mps_parser::mps_data_model_t& op_problem,
   const optimization_problem_solution_t& solution,
-  double epsilon = tolerance)
+  double epsilon        = tolerance,
+  bool presolve_enabled = true)
 {
   const std::vector primal_vars              = host_copy(solution.get_primal_solution());
   const std::vector& values                  = op_problem.get_constraint_matrix_values();
@@ -82,51 +83,53 @@ static void test_constraint_sanity(
   std::vector residual(solution.get_dual_solution().size(), 0.0);
   std::vector viol(solution.get_dual_solution().size(), 0.0);
 
-  // CSR SpMV
-  for (size_t i = 0; i < offsets.size() - 1; ++i) {
-    for (int j = offsets[i]; j < offsets[i + 1]; ++j) {
-      residual[i] += values[j] * primal_vars[indices[j]];
+  if (!presolve_enabled) {
+    // CSR SpMV
+    for (size_t i = 0; i < offsets.size() - 1; ++i) {
+      for (int j = offsets[i]; j < offsets[i + 1]; ++j) {
+        residual[i] += values[j] * primal_vars[indices[j]];
+      }
     }
-  }
 
-  auto functor = cuopt::linear_programming::detail::violation{};
+    auto functor = cuopt::linear_programming::detail::violation{};
 
-  // Compute violation to lower/upper bound
+    // Compute violation to lower/upper bound
 
-  // std::transform can't take 3 inputs
-  for (size_t i = 0; i < residual.size(); ++i) {
-    viol[i] = functor(residual[i], constraint_lower_bounds[i], constraint_upper_bounds[i]);
-  }
+    // std::transform can't take 3 inputs
+    for (size_t i = 0; i < residual.size(); ++i) {
+      viol[i] = functor(residual[i], constraint_lower_bounds[i], constraint_upper_bounds[i]);
+    }
 
-  // Compute the l2 primal residual
-  double l2_primal_residual = std::accumulate(
-    viol.cbegin(), viol.cend(), 0.0, [](double acc, double val) { return acc + val * val; });
-  l2_primal_residual = std::sqrt(l2_primal_residual);
+    // Compute the l2 primal residual
+    double l2_primal_residual = std::accumulate(
+      viol.cbegin(), viol.cend(), 0.0, [](double acc, double val) { return acc + val * val; });
+    l2_primal_residual = std::sqrt(l2_primal_residual);
 
-  EXPECT_NEAR(l2_primal_residual,
-              solution.get_additional_termination_information().l2_primal_residual,
-              epsilon);
+    EXPECT_NEAR(l2_primal_residual,
+                solution.get_additional_termination_information().l2_primal_residual,
+                epsilon);
 
-  // Check if primal residual is indeed respecting the default tolerance
-  pdlp_solver_settings_t solver_settings = pdlp_solver_settings_t{};
+    // Check if primal residual is indeed respecting the default tolerance
+    pdlp_solver_settings_t solver_settings = pdlp_solver_settings_t{};
 
-  std::vector combined_bounds(constraint_lower_bounds.size());
+    std::vector combined_bounds(constraint_lower_bounds.size());
 
-  std::transform(constraint_lower_bounds.cbegin(),
-                 constraint_lower_bounds.cend(),
-                 constraint_upper_bounds.cbegin(),
-                 combined_bounds.begin(),
-                 cuopt::linear_programming::detail::combine_finite_abs_bounds{});
+    std::transform(constraint_lower_bounds.cbegin(),
+                   constraint_lower_bounds.cend(),
+                   constraint_upper_bounds.cbegin(),
+                   combined_bounds.begin(),
+                   cuopt::linear_programming::detail::combine_finite_abs_bounds{});
 
-  double l2_norm_primal_right_hand_side = std::accumulate(
-    combined_bounds.cbegin(), combined_bounds.cend(), 0.0, [](double acc, double val) {
-      return acc + val * val;
-    });
-  l2_norm_primal_right_hand_side = std::sqrt(l2_norm_primal_right_hand_side);
+    double l2_norm_primal_right_hand_side = std::accumulate(
+      combined_bounds.cbegin(), combined_bounds.cend(), 0.0, [](double acc, double val) {
+        return acc + val * val;
+      });
+    l2_norm_primal_right_hand_side = std::sqrt(l2_norm_primal_right_hand_side);
 
-  EXPECT_TRUE(l2_primal_residual <= solver_settings.tolerances.absolute_primal_tolerance +
-                                      solver_settings.tolerances.relative_primal_tolerance *
-                                        l2_norm_primal_right_hand_side);
+    EXPECT_TRUE(l2_primal_residual <= solver_settings.tolerances.absolute_primal_tolerance +
+                                        solver_settings.tolerances.relative_primal_tolerance *
+                                          l2_norm_primal_right_hand_side);
+  }
 
   // Checking variable bounds
 

From 35177db8ca13110ebe3288d779185c3fb3da11f1 Mon Sep 17 00:00:00 2001
From: Ramakrishna Prabhu 
Date: Thu, 31 Jul 2025 12:11:51 -0500
Subject: [PATCH 046/108] update

---
 ci/build_wheel_libcuopt.sh         | 17 ++-----
 ci/docker/Dockerfile               |  3 ++
 ci/test_self_hosted_service.sh     |  3 ++
 ci/test_wheel_cuopt.sh             |  3 ++
 ci/test_wheel_cuopt_server.sh      |  3 ++
 ci/utils/install_boost_tbb.sh      | 37 +++++++++++++++
 cpp/CMakeLists.txt                 | 12 ++++-
 cpp/cmake/thirdparty/FindTBB.cmake | 75 ++++++++++++++++++++++++++++++
 dependencies.yaml                  |  2 +-
 9 files changed, 138 insertions(+), 17 deletions(-)
 create mode 100644 ci/utils/install_boost_tbb.sh
 create mode 100644 cpp/cmake/thirdparty/FindTBB.cmake

diff --git a/ci/build_wheel_libcuopt.sh b/ci/build_wheel_libcuopt.sh
index 2512bdcd46..73e41d7df0 100755
--- a/ci/build_wheel_libcuopt.sh
+++ b/ci/build_wheel_libcuopt.sh
@@ -21,20 +21,9 @@ source rapids-init-pip
 package_name="libcuopt"
 package_dir="python/libcuopt"
 
-# Install Boost
-if [ -f /etc/os-release ]; then
-    . /etc/os-release
-    if [[ "$ID" == "rocky" ]]; then
-        echo "Detected Rocky Linux. Installing Boost via dnf..."
-        dnf install -y boost-devel
-    elif [[ "$ID" == "ubuntu" ]]; then
-        echo "Detected Ubuntu. Installing Boost via apt..."
-        apt-get update
-        apt-get install -y libboost-dev
-    else
-        echo "Unknown OS: $ID. Please install Boost development libraries manually."
-        exit 1
-    fi
+# Install Boost and TBB
+ci/utils/install_boost_tbb.sh
+
 else
     echo "/etc/os-release not found. Cannot determine OS. Please install Boost development libraries manually."
     exit 1
diff --git a/ci/docker/Dockerfile b/ci/docker/Dockerfile
index 7810aa4de1..6ccdaff97b 100644
--- a/ci/docker/Dockerfile
+++ b/ci/docker/Dockerfile
@@ -44,6 +44,9 @@ RUN apt-get update && apt-get install -y --no-install-recommends build-essential
     python${PYTHON_SHORT_VER} -m ensurepip --upgrade && \
     python${PYTHON_SHORT_VER} -m pip install --upgrade pip
 
+# Install Boost and TBB
+RUN ci/utils/install_boost_tbb.sh
+
 ENV DEBIAN_FRONTEND=""
 
 RUN ln -sf /usr/bin/python${PYTHON_SHORT_VER} /usr/bin/python && \
diff --git a/ci/test_self_hosted_service.sh b/ci/test_self_hosted_service.sh
index a5285ec1ca..2f9519963c 100755
--- a/ci/test_self_hosted_service.sh
+++ b/ci/test_self_hosted_service.sh
@@ -19,6 +19,9 @@ set -euo pipefail
 
 source rapids-init-pip
 
+# Install Boost and TBB
+ci/utils/install_boost_tbb.sh
+
 # Download the cuopt built in the previous step
 RAPIDS_PY_CUDA_SUFFIX="$(rapids-wheel-ctk-name-gen "${RAPIDS_CUDA_VERSION}")"
 CUOPT_MPS_PARSER_WHEELHOUSE=$(RAPIDS_PY_WHEEL_NAME="cuopt_mps_parser" rapids-download-wheels-from-github python)
diff --git a/ci/test_wheel_cuopt.sh b/ci/test_wheel_cuopt.sh
index 7a16db43f1..e6a75c574e 100755
--- a/ci/test_wheel_cuopt.sh
+++ b/ci/test_wheel_cuopt.sh
@@ -21,6 +21,9 @@ set -euo pipefail
 # so those constraints will affect all future 'pip install' calls
 source rapids-init-pip
 
+# Install Boost and TBB
+ci/utils/install_boost_tbb.sh
+
 # Download the packages built in the previous step
 RAPIDS_PY_CUDA_SUFFIX="$(rapids-wheel-ctk-name-gen "${RAPIDS_CUDA_VERSION}")"
 CUOPT_MPS_PARSER_WHEELHOUSE=$(RAPIDS_PY_WHEEL_NAME="cuopt_mps_parser" rapids-download-wheels-from-github python)
diff --git a/ci/test_wheel_cuopt_server.sh b/ci/test_wheel_cuopt_server.sh
index 7f999dba88..32144b63fc 100755
--- a/ci/test_wheel_cuopt_server.sh
+++ b/ci/test_wheel_cuopt_server.sh
@@ -19,6 +19,9 @@ set -eou pipefail
 
 source rapids-init-pip
 
+# Install Boost and TBB
+ci/utils/install_boost_tbb.sh
+
 # Download the packages built in the previous step
 RAPIDS_PY_CUDA_SUFFIX="$(rapids-wheel-ctk-name-gen "${RAPIDS_CUDA_VERSION}")"
 CUOPT_MPS_PARSER_WHEELHOUSE=$(RAPIDS_PY_WHEEL_NAME="cuopt_mps_parser" rapids-download-wheels-from-github python)
diff --git a/ci/utils/install_boost_tbb.sh b/ci/utils/install_boost_tbb.sh
new file mode 100644
index 0000000000..adbfcdf46d
--- /dev/null
+++ b/ci/utils/install_boost_tbb.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+
+# SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set -euo pipefail
+
+# Install Boost and TBB
+if [ -f /etc/os-release ]; then
+    . /etc/os-release
+    if [[ "$ID" == "rocky" ]]; then
+        echo "Detected Rocky Linux. Installing Boost and TBB via dnf..."
+        dnf install -y boost-devel tbb-devel
+    elif [[ "$ID" == "ubuntu" ]]; then
+        echo "Detected Ubuntu. Installing Boost and TBB via apt..."
+        apt-get update
+        apt-get install -y libboost-dev libtbb-dev
+    else
+        echo "Unknown OS: $ID. Please install Boost development libraries manually."
+        exit 1
+    fi
+else
+    echo "/etc/os-release not found. Cannot determine OS. Please install Boost development libraries manually."
+    exit 1
+fi
diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt
index 08dbe038eb..db802ca8a5 100644
--- a/cpp/CMakeLists.txt
+++ b/cpp/CMakeLists.txt
@@ -13,7 +13,10 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-cmake_minimum_required(VERSION 3.26.4 FATAL_ERROR)
+cmake_minimum_required(VERSION 3.30.4 FATAL_ERROR)
+
+# Add our custom Find modules to the module path
+list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/thirdparty")
 
 include(GNUInstallDirs)
 
@@ -158,7 +161,11 @@ else()
   find_package(RAFT REQUIRED)
 endif()
 
-find_package(TBB REQUIRED)
+find_package(TBB QUIET)
+
+if(NOT TBB_FOUND)
+  message(FATAL_ERROR "TBB is required but was not found. Please install the TBB development package (e.g., libtbb-dev or tbb-devel) and try again.")
+endif()
 
 FetchContent_Declare(
   papilo
@@ -258,6 +265,7 @@ target_link_libraries(cuopt
   ${CUOPT_PRIVATE_CUDA_LIBS}
   )
 
+
 # ##################################################################################################
 # - generate tests --------------------------------------------------------------------------------
 if(BUILD_TESTS)
diff --git a/cpp/cmake/thirdparty/FindTBB.cmake b/cpp/cmake/thirdparty/FindTBB.cmake
new file mode 100644
index 0000000000..6200a46bbe
--- /dev/null
+++ b/cpp/cmake/thirdparty/FindTBB.cmake
@@ -0,0 +1,75 @@
+# FindTBB.cmake - Find TBB (Threading Building Blocks) library
+#
+# This module defines the following variables:
+#   TBB_FOUND        - True if TBB is found
+#   TBB_INCLUDE_DIRS - TBB include directories
+#   TBB_LIBRARIES    - TBB libraries
+#   TBB::tbb         - Imported target for TBB
+
+# Try pkg-config first
+find_package(PkgConfig QUIET)
+if(PkgConfig_FOUND)
+  pkg_check_modules(PC_TBB QUIET tbb)
+endif()
+
+find_path(TBB_INCLUDE_DIR
+  NAMES tbb/tbb.h
+  PATHS
+    ${PC_TBB_INCLUDE_DIRS}
+    /usr/include
+    /usr/local/include
+    /opt/intel/tbb/include
+    /opt/intel/oneapi/tbb/latest/include
+)
+
+find_library(TBB_LIBRARY
+  NAMES tbb
+  PATHS
+    ${PC_TBB_LIBRARY_DIRS}
+    /usr/lib
+    /usr/lib64
+    /usr/local/lib
+    /usr/local/lib64
+    /opt/intel/tbb/lib
+    /opt/intel/oneapi/tbb/latest/lib
+)
+
+find_library(TBB_MALLOC_LIBRARY
+  NAMES tbbmalloc
+  PATHS
+    ${PC_TBB_LIBRARY_DIRS}
+    /usr/lib
+    /usr/lib64
+    /usr/local/lib
+    /usr/local/lib64
+    /opt/intel/tbb/lib
+    /opt/intel/oneapi/tbb/latest/lib
+)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(TBB
+  REQUIRED_VARS TBB_INCLUDE_DIR TBB_LIBRARY
+)
+
+if(TBB_FOUND AND NOT TARGET TBB::tbb)
+  add_library(TBB::tbb UNKNOWN IMPORTED)
+  set_target_properties(TBB::tbb PROPERTIES
+    IMPORTED_LOCATION "${TBB_LIBRARY}"
+    INTERFACE_INCLUDE_DIRECTORIES "${TBB_INCLUDE_DIR}"
+  )
+  
+  if(TBB_MALLOC_LIBRARY)
+    set_target_properties(TBB::tbb PROPERTIES
+      INTERFACE_LINK_LIBRARIES "${TBB_MALLOC_LIBRARY}"
+    )
+  endif()
+  
+  # Add compile definitions from pkg-config if available
+  if(PC_TBB_CFLAGS_OTHER)
+    set_target_properties(TBB::tbb PROPERTIES
+      INTERFACE_COMPILE_OPTIONS "${PC_TBB_CFLAGS_OTHER}"
+    )
+  endif()
+endif()
+
+mark_as_advanced(TBB_INCLUDE_DIR TBB_LIBRARY TBB_MALLOC_LIBRARY) 
\ No newline at end of file
diff --git a/dependencies.yaml b/dependencies.yaml
index b3fea66007..a64b6cbc64 100644
--- a/dependencies.yaml
+++ b/dependencies.yaml
@@ -277,7 +277,7 @@ dependencies:
           - wheel
   depends_on_papilo_deps:
     common:
-      - output_types: [conda, requirements, pyproject]
+      - output_types: conda
         packages:
           - tbb-devel
           - boost

From dca96ebfc7e45f0a9a6d76a61510d93cda4d6df1 Mon Sep 17 00:00:00 2001
From: Ramakrishna Prabhu 
Date: Thu, 31 Jul 2025 12:14:25 -0500
Subject: [PATCH 047/108] fix pyproject

---
 python/libcuopt/pyproject.toml | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/python/libcuopt/pyproject.toml b/python/libcuopt/pyproject.toml
index 193eafc56f..c9a900caa5 100644
--- a/python/libcuopt/pyproject.toml
+++ b/python/libcuopt/pyproject.toml
@@ -43,7 +43,6 @@ classifiers = [
     "Programming Language :: Python :: 3.13",
 ]
 dependencies = [
-    "boost",
     "cuopt-mps-parser==25.8.*,>=0.0.0a0",
     "librmm==25.8.*,>=0.0.0a0",
     "nvidia-cublas",
@@ -52,7 +51,6 @@ dependencies = [
     "nvidia-cusparse",
     "nvidia-nvtx",
     "rapids-logger==0.1.*,>=0.0.0a0",
-    "tbb-devel",
 ] # This list was generated by `rapids-dependency-file-generator`. To make changes, edit ../../dependencies.yaml and run `rapids-dependency-file-generator`.
 
 [project.urls]
@@ -95,11 +93,9 @@ build-backend = "scikit_build_core.build"
 dependencies-file = "../../dependencies.yaml"
 matrix-entry = "cuda_suffixed=true;use_cuda_wheels=true"
 requires = [
-    "boost",
     "cmake>=3.30.4",
     "cuopt-mps-parser==25.8.*,>=0.0.0a0",
     "librmm==25.8.*,>=0.0.0a0",
     "ninja",
     "rapids-logger==0.1.*,>=0.0.0a0",
-    "tbb-devel",
 ] # This list was generated by `rapids-dependency-file-generator`. To make changes, edit ../../dependencies.yaml and run `rapids-dependency-file-generator`.

From 22b793609cd3ae064465e95437d9aebfde0b5b45 Mon Sep 17 00:00:00 2001
From: Ramakrishna Prabhu 
Date: Thu, 31 Jul 2025 12:15:33 -0500
Subject: [PATCH 048/108] fix

---
 ci/build_wheel_libcuopt.sh | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/ci/build_wheel_libcuopt.sh b/ci/build_wheel_libcuopt.sh
index 73e41d7df0..10ccebe28e 100755
--- a/ci/build_wheel_libcuopt.sh
+++ b/ci/build_wheel_libcuopt.sh
@@ -24,11 +24,6 @@ package_dir="python/libcuopt"
 # Install Boost and TBB
 ci/utils/install_boost_tbb.sh
 
-else
-    echo "/etc/os-release not found. Cannot determine OS. Please install Boost development libraries manually."
-    exit 1
-fi
-
 export SKBUILD_CMAKE_ARGS="-DCUOPT_BUILD_WHEELS=ON;-DDISABLE_DEPRECATION_WARNING=ON"
 
 # For pull requests we are enabling assert mode.

From 5eadd98eb5c00332b3c62ba57db6b82f0c9b6a53 Mon Sep 17 00:00:00 2001
From: Ramakrishna Prabhu 
Date: Thu, 31 Jul 2025 12:20:36 -0500
Subject: [PATCH 049/108] style fix

---
 cpp/cmake/thirdparty/FindTBB.cmake | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/cpp/cmake/thirdparty/FindTBB.cmake b/cpp/cmake/thirdparty/FindTBB.cmake
index 6200a46bbe..61917fcc86 100644
--- a/cpp/cmake/thirdparty/FindTBB.cmake
+++ b/cpp/cmake/thirdparty/FindTBB.cmake
@@ -1,3 +1,18 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
 # FindTBB.cmake - Find TBB (Threading Building Blocks) library
 #
 # This module defines the following variables:

From 85c15c7c7eccb3ca412d045062f00b6b8e51ec01 Mon Sep 17 00:00:00 2001
From: Ramakrishna Prabhu 
Date: Thu, 31 Jul 2025 12:33:29 -0500
Subject: [PATCH 050/108] fix issues

---
 ci/build_wheel_libcuopt.sh     | 2 +-
 ci/docker/Dockerfile           | 2 +-
 ci/test_self_hosted_service.sh | 2 +-
 ci/test_wheel_cuopt.sh         | 2 +-
 4 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/ci/build_wheel_libcuopt.sh b/ci/build_wheel_libcuopt.sh
index 10ccebe28e..2b3a8b0fb5 100755
--- a/ci/build_wheel_libcuopt.sh
+++ b/ci/build_wheel_libcuopt.sh
@@ -22,7 +22,7 @@ package_name="libcuopt"
 package_dir="python/libcuopt"
 
 # Install Boost and TBB
-ci/utils/install_boost_tbb.sh
+bash ci/utils/install_boost_tbb.sh
 
 export SKBUILD_CMAKE_ARGS="-DCUOPT_BUILD_WHEELS=ON;-DDISABLE_DEPRECATION_WARNING=ON"
 
diff --git a/ci/docker/Dockerfile b/ci/docker/Dockerfile
index 6ccdaff97b..0015ac0dff 100644
--- a/ci/docker/Dockerfile
+++ b/ci/docker/Dockerfile
@@ -45,7 +45,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends build-essential
     python${PYTHON_SHORT_VER} -m pip install --upgrade pip
 
 # Install Boost and TBB
-RUN ci/utils/install_boost_tbb.sh
+RUN bash ci/utils/install_boost_tbb.sh
 
 ENV DEBIAN_FRONTEND=""
 
diff --git a/ci/test_self_hosted_service.sh b/ci/test_self_hosted_service.sh
index 2f9519963c..afb585a7c7 100755
--- a/ci/test_self_hosted_service.sh
+++ b/ci/test_self_hosted_service.sh
@@ -20,7 +20,7 @@ set -euo pipefail
 source rapids-init-pip
 
 # Install Boost and TBB
-ci/utils/install_boost_tbb.sh
+bash ci/utils/install_boost_tbb.sh
 
 # Download the cuopt built in the previous step
 RAPIDS_PY_CUDA_SUFFIX="$(rapids-wheel-ctk-name-gen "${RAPIDS_CUDA_VERSION}")"
diff --git a/ci/test_wheel_cuopt.sh b/ci/test_wheel_cuopt.sh
index e6a75c574e..313baaed0d 100755
--- a/ci/test_wheel_cuopt.sh
+++ b/ci/test_wheel_cuopt.sh
@@ -22,7 +22,7 @@ set -euo pipefail
 source rapids-init-pip
 
 # Install Boost and TBB
-ci/utils/install_boost_tbb.sh
+bash ci/utils/install_boost_tbb.sh
 
 # Download the packages built in the previous step
 RAPIDS_PY_CUDA_SUFFIX="$(rapids-wheel-ctk-name-gen "${RAPIDS_CUDA_VERSION}")"

From fac68d36091751475436e97eb97f9eeb8e2872c0 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Thu, 31 Jul 2025 10:16:23 -0700
Subject: [PATCH 051/108] Disable lp by default

---
 .../mip/solver_settings.hpp                   | 11 ++--
 .../pdlp/solver_settings.hpp                  | 50 ++++++++++---------
 cpp/src/math_optimization/solver_settings.cu  |  2 +-
 cpp/tests/linear_programming/pdlp_test.cu     | 15 ++----
 .../linear_programming/test_lp_solver.py      |  8 ---
 5 files changed, 38 insertions(+), 48 deletions(-)

diff --git a/cpp/include/cuopt/linear_programming/mip/solver_settings.hpp b/cpp/include/cuopt/linear_programming/mip/solver_settings.hpp
index 03c14ab544..4d4d29eaf9 100644
--- a/cpp/include/cuopt/linear_programming/mip/solver_settings.hpp
+++ b/cpp/include/cuopt/linear_programming/mip/solver_settings.hpp
@@ -54,9 +54,9 @@ class mip_solver_settings_t {
    *
    * @note This function can be called multiple times to add more solutions.
    *
-   * @param[in] initial_solution Device or host memory pointer to a floating point array of
-   * size size.
-   * cuOpt copies this data. Copy happens on the stream of the raft:handler passed to the problem.
+   * @param[in] initial_solution Device or host memory pointer to a floating
+   * point array of size size. cuOpt copies this data. Copy happens on the
+   * stream of the raft:handler passed to the problem.
    * @param size Size of the initial_solution array.
    */
   void add_initial_solution(const f_t* initial_solution,
@@ -99,8 +99,9 @@ class mip_solver_settings_t {
   /** Initial primal solutions */
   std::vector>> initial_solutions;
   bool mip_scaling = true;
-  bool presolve    = true;  // Enable presolve by default
-  // this is for extracting info from different places of the solver during benchmarks
+  bool presolve    = true;
+  // this is for extracting info from different places of the solver during
+  // benchmarks
   benchmark_info_t* benchmark_info_ptr = nullptr;
 
  private:
diff --git a/cpp/include/cuopt/linear_programming/pdlp/solver_settings.hpp b/cpp/include/cuopt/linear_programming/pdlp/solver_settings.hpp
index 5627a478cb..f4e5152887 100644
--- a/cpp/include/cuopt/linear_programming/pdlp/solver_settings.hpp
+++ b/cpp/include/cuopt/linear_programming/pdlp/solver_settings.hpp
@@ -1,6 +1,6 @@
 /*
- * SPDX-FileCopyrightText: Copyright (c) 2023-2025 NVIDIA CORPORATION & AFFILIATES. All rights
- * reserved. SPDX-License-Identifier: Apache-2.0
+ * SPDX-FileCopyrightText: Copyright (c) 2023-2025 NVIDIA CORPORATION &
+ * AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -32,7 +32,8 @@ template 
 class solver_settings_t;
 
 /**
- * @brief Enum representing the different solver modes under which PDLP can operate.
+ * @brief Enum representing the different solver modes under which PDLP can
+ * operate.
  *
  * Stable2: Best overall mode from experiments; balances speed and convergence
  * success. If you want to use the legacy version, use Stable1.
@@ -42,7 +43,8 @@ class solver_settings_t;
  *
  * @note Default mode is Stable2.
  */
-// Forced to use an enum instead of an enum class for compatibility with the Cython layer
+// Forced to use an enum instead of an enum class for compatibility with the
+// Cython layer
 enum pdlp_solver_mode_t : int {
   Stable1     = CUOPT_PDLP_SOLVER_MODE_STABLE1,
   Stable2     = CUOPT_PDLP_SOLVER_MODE_STABLE2,
@@ -51,8 +53,8 @@ enum pdlp_solver_mode_t : int {
 };
 
 /**
- * @brief Enum representing the different methods that can be used to solve the linear programming
- * problem.
+ * @brief Enum representing the different methods that can be used to solve the
+ * linear programming problem.
  *
  * Concurrent: Use both PDLP and DualSimplex in parallel.
  * PDLP: Use the PDLP method.
@@ -74,15 +76,15 @@ class pdlp_solver_settings_t {
   // Copy constructor for when copying in the PDLP object
   pdlp_solver_settings_t(const pdlp_solver_settings_t& other, rmm::cuda_stream_view stream_view);
   /**
-   * @brief Set both absolute and relative tolerance on the primal feasibility, dual feasibility
-   and gap.
+   * @brief Set both absolute and relative tolerance on the primal feasibility,
+   dual feasibility and gap.
    * Changing this value has a significant impact on accuracy and runtime.
    *
    * Optimality is computed as follows:
    * - dual_feasiblity < absolute_dual_tolerance + relative_dual_tolerance *
    norm_objective_coefficient (l2_norm(c))
-   * - primal_feasiblity < absolute_primal_tolerance + relative_primal_tolerance *
-   norm_constraint_bounds (l2_norm(b))
+   * - primal_feasiblity < absolute_primal_tolerance + relative_primal_tolerance
+   * norm_constraint_bounds (l2_norm(b))
    * - duality_gap < absolute_gap_tolerance + relative_gap_tolerance *
    (|primal_objective| + |dual_objective|)
    *
@@ -101,9 +103,9 @@ class pdlp_solver_settings_t {
    *
    * @note Default value is all 0.
    *
-   * @param[in] initial_primal_solution Device or host memory pointer to a floating point array of
-   * size size.
-   * cuOpt copies this data. Copy happens on the stream of the raft:handler passed to the problem.
+   * @param[in] initial_primal_solution Device or host memory pointer to a
+   * floating point array of size size. cuOpt copies this data. Copy happens on
+   * the stream of the raft:handler passed to the problem.
    * @param size Size of the initial_primal_solution array.
    */
   void set_initial_primal_solution(const f_t* initial_primal_solution,
@@ -115,9 +117,9 @@ class pdlp_solver_settings_t {
    *
    * @note Default value is all 0.
    *
-   * @param[in] initial_dual_solution Device or host memory pointer to a floating point array of
-   * size size.
-   * cuOpt copies this data. Copy happens on the stream of the raft:handler passed to the problem.
+   * @param[in] initial_dual_solution Device or host memory pointer to a
+   * floating point array of size size. cuOpt copies this data. Copy happens on
+   * the stream of the raft:handler passed to the problem.
    * @param size Size of the initial_dual_solution array.
    */
   void set_initial_dual_solution(const f_t* initial_dual_solution,
@@ -125,15 +127,17 @@ class pdlp_solver_settings_t {
                                  rmm::cuda_stream_view stream = rmm::cuda_stream_default);
 
   /**
-   * @brief Set the pdlp warm start data. This allows to restart PDLP with a previous solution
+   * @brief Set the pdlp warm start data. This allows to restart PDLP with a
+   * previous solution
    *
    * @note Interface for the C++ side. Only Stable2 and Fast1 are supported.
    *
-   * @param pdlp_warm_start_data_view Pdlp warm start data from your solution object to warm start
-   * from
-   * @param var_mapping Variables indices to scatter to in case the new problem has less variables
-   * @param constraint_mapping Constraints indices to scatter to in case the new problem has less
-   * constraints
+   * @param pdlp_warm_start_data_view Pdlp warm start data from your solution
+   * object to warm start from
+   * @param var_mapping Variables indices to scatter to in case the new problem
+   * has less variables
+   * @param constraint_mapping Constraints indices to scatter to in case the new
+   * problem has less constraints
    */
   void set_pdlp_warm_start_data(pdlp_warm_start_data_t& pdlp_warm_start_data_view,
                                 const rmm::device_uvector& var_mapping =
@@ -205,7 +209,7 @@ class pdlp_solver_settings_t {
   bool crossover{false};
   bool save_best_primal_so_far{false};
   bool first_primal_feasible{false};
-  bool presolve{true};
+  bool presolve{false};
   method_t method{method_t::Concurrent};
   // For concurrent termination
   std::atomic* concurrent_halt;
diff --git a/cpp/src/math_optimization/solver_settings.cu b/cpp/src/math_optimization/solver_settings.cu
index 91d173a3e6..a049d0d09b 100644
--- a/cpp/src/math_optimization/solver_settings.cu
+++ b/cpp/src/math_optimization/solver_settings.cu
@@ -105,7 +105,7 @@ solver_settings_t::solver_settings_t() : pdlp_settings(), mip_settings
     {CUOPT_LOG_TO_CONSOLE, &pdlp_settings.log_to_console, true},
     {CUOPT_LOG_TO_CONSOLE, &mip_settings.log_to_console, true},
     {CUOPT_CROSSOVER, &pdlp_settings.crossover, false},
-    {CUOPT_PRESOLVE, &pdlp_settings.presolve, true},
+    {CUOPT_PRESOLVE, &pdlp_settings.presolve, false},
     {CUOPT_PRESOLVE, &mip_settings.presolve, true}
   };
   // String parameters
diff --git a/cpp/tests/linear_programming/pdlp_test.cu b/cpp/tests/linear_programming/pdlp_test.cu
index ca828d66b3..ca94261e62 100644
--- a/cpp/tests/linear_programming/pdlp_test.cu
+++ b/cpp/tests/linear_programming/pdlp_test.cu
@@ -102,8 +102,6 @@ TEST(pdlp_class, run_double_very_low_accuracy)
   settings.tolerances.absolute_gap_tolerance    = settings.minimal_absolute_tolerance;
   settings.tolerances.relative_gap_tolerance    = 0.0;
   settings.method                               = cuopt::linear_programming::method_t::PDLP;
-  // Model sensible, disabling presolve
-  settings.presolve = false;
 
   optimization_problem_solution_t solution = solve_lp(&handle_, op_problem, settings);
   EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
@@ -728,7 +726,6 @@ TEST(pdlp_class, best_primal_so_far_iteration)
   solver_settings.iteration_limit         = 3000;
   solver_settings.per_constraint_residual = true;
   solver_settings.method                  = cuopt::linear_programming::method_t::PDLP;
-  solver_settings.presolve                = false;
 
   cuopt::mps_parser::mps_data_model_t op_problem1 =
     cuopt::mps_parser::parse_mps(path);
@@ -758,7 +755,6 @@ TEST(pdlp_class, best_primal_so_far_time)
   solver_settings.per_constraint_residual = true;
   solver_settings.pdlp_solver_mode        = cuopt::linear_programming::pdlp_solver_mode_t::Stable1;
   solver_settings.method                  = cuopt::linear_programming::method_t::PDLP;
-  solver_settings.presolve                = false;
 
   cuopt::mps_parser::mps_data_model_t op_problem1 =
     cuopt::mps_parser::parse_mps(path);
@@ -828,7 +824,6 @@ TEST(pdlp_class, warm_start)
     solver_settings.set_optimality_tolerance(1e-2);
     solver_settings.detect_infeasibility = false;
     solver_settings.method               = cuopt::linear_programming::method_t::PDLP;
-    solver_settings.presolve             = false;
 
     cuopt::mps_parser::mps_data_model_t mps_data_model =
       cuopt::mps_parser::parse_mps(path);
@@ -887,9 +882,8 @@ TEST(pdlp_class, run_empty_matrix_pdlp)
   cuopt::mps_parser::mps_data_model_t op_problem =
     cuopt::mps_parser::parse_mps(path);
 
-  auto solver_settings     = pdlp_solver_settings_t{};
-  solver_settings.method   = cuopt::linear_programming::method_t::PDLP;
-  solver_settings.presolve = false;
+  auto solver_settings   = pdlp_solver_settings_t{};
+  solver_settings.method = cuopt::linear_programming::method_t::PDLP;
 
   optimization_problem_solution_t solution =
     solve_lp(&handle_, op_problem, solver_settings);
@@ -905,9 +899,8 @@ TEST(pdlp_class, run_empty_matrix_dual_simplex)
   cuopt::mps_parser::mps_data_model_t op_problem =
     cuopt::mps_parser::parse_mps(path);
 
-  auto solver_settings     = pdlp_solver_settings_t{};
-  solver_settings.method   = cuopt::linear_programming::method_t::Concurrent;
-  solver_settings.presolve = false;
+  auto solver_settings   = pdlp_solver_settings_t{};
+  solver_settings.method = cuopt::linear_programming::method_t::Concurrent;
 
   optimization_problem_solution_t solution =
     solve_lp(&handle_, op_problem, solver_settings);
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 cc4e709fca..ed0d04ec19 100644
--- a/python/cuopt/cuopt/tests/linear_programming/test_lp_solver.py
+++ b/python/cuopt/cuopt/tests/linear_programming/test_lp_solver.py
@@ -30,7 +30,6 @@
     CUOPT_METHOD,
     CUOPT_MIP_HEURISTICS_ONLY,
     CUOPT_PDLP_SOLVER_MODE,
-    CUOPT_PRESOLVE,
     CUOPT_PRIMAL_INFEASIBLE_TOLERANCE,
     CUOPT_RELATIVE_DUAL_TOLERANCE,
     CUOPT_RELATIVE_GAP_TOLERANCE,
@@ -76,8 +75,6 @@ def test_solver():
     settings = solver_settings.SolverSettings()
     settings.set_optimality_tolerance(1e-2)
     settings.set_parameter(CUOPT_METHOD, SolverMethod.PDLP)
-    # Reduces to an empty problem
-    settings.set_parameter(CUOPT_PRESOLVE, False)
 
     solution = solver.Solve(data_model_obj, settings)
     assert solution.get_termination_reason() == "Optimal"
@@ -97,8 +94,6 @@ def test_parser_and_solver():
 
     settings = solver_settings.SolverSettings()
     settings.set_optimality_tolerance(1e-2)
-    # Reduces to an empty problem
-    settings.set_parameter(CUOPT_PRESOLVE, False)
     solution = solver.Solve(data_model_obj, settings)
     assert solution.get_termination_reason() == "Optimal"
 
@@ -434,8 +429,6 @@ def test_parse_var_names():
 
     settings = solver_settings.SolverSettings()
     settings.set_parameter(CUOPT_METHOD, SolverMethod.PDLP)
-    # To match original dict
-    settings.set_parameter(CUOPT_PRESOLVE, False)
     solution = solver.Solve(data_model_obj, settings)
 
     expected_dict = {
@@ -526,7 +519,6 @@ def test_warm_start():
     settings.set_parameter(CUOPT_PDLP_SOLVER_MODE, PDLPSolverMode.Stable2)
     settings.set_optimality_tolerance(1e-3)
     settings.set_parameter(CUOPT_INFEASIBILITY_DETECTION, False)
-    settings.set_parameter(CUOPT_PRESOLVE, False)
 
     # Solving from scratch until 1e-3
     solution = solver.Solve(data_model_obj, settings)

From 0742c3210f9e3fa1870a2c88147085952d2367b3 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Thu, 31 Jul 2025 11:24:41 -0700
Subject: [PATCH 052/108] Reproduced papilo hang

---
 cpp/src/mip/presolve/third_party_presolve.cu |  12 +-
 cpp/tests/linear_programming/pdlp_test.cu    | 481 ++++++++++++++-----
 2 files changed, 367 insertions(+), 126 deletions(-)

diff --git a/cpp/src/mip/presolve/third_party_presolve.cu b/cpp/src/mip/presolve/third_party_presolve.cu
index 6fda07db60..f2f41e3807 100644
--- a/cpp/src/mip/presolve/third_party_presolve.cu
+++ b/cpp/src/mip/presolve/third_party_presolve.cu
@@ -249,10 +249,10 @@ void set_presolve_methods(papilo::Presolve& presolver, problem_category_t c
   presolver.addPresolveMethod(uptr(new papilo::Probing()));
 
   // if (category == problem_category_t::MIP) {
-  presolver.addPresolveMethod(uptr(new papilo::DualInfer));
-  presolver.addPresolveMethod(uptr(new papilo::SimpleSubstitution()));
-  presolver.addPresolveMethod(uptr(new papilo::Sparsify()));
-  presolver.addPresolveMethod(uptr(new papilo::Substitution()));
+  // presolver.addPresolveMethod(uptr(new papilo::DualInfer));
+  // presolver.addPresolveMethod(uptr(new papilo::SimpleSubstitution()));
+  // presolver.addPresolveMethod(uptr(new papilo::Sparsify()));
+  // presolver.addPresolveMethod(uptr(new papilo::Substitution()));
   // }
 }
 
@@ -266,8 +266,8 @@ void set_presolve_options(papilo::Presolve& presolver,
   presolver.getPresolveOptions().epsilon = absolute_tolerance;
   presolver.getPresolveOptions().feastol = absolute_tolerance;
   // if (category == problem_category_t::LP) {
-  //   presolver.getPresolveOptions().componentsmaxint = -1;
-  //   presolver.getPresolveOptions().detectlindep     = 0;
+  presolver.getPresolveOptions().componentsmaxint = -1;
+  presolver.getPresolveOptions().detectlindep     = 0;
   // }
 }
 
diff --git a/cpp/tests/linear_programming/pdlp_test.cu b/cpp/tests/linear_programming/pdlp_test.cu
index ca94261e62..24ac0c8c49 100644
--- a/cpp/tests/linear_programming/pdlp_test.cu
+++ b/cpp/tests/linear_programming/pdlp_test.cu
@@ -36,6 +36,11 @@
 #include 
 #include 
 
+// Papilo includes
+#include 
+#include 
+#include 
+
 #include 
 #include 
 #include 
@@ -65,141 +70,303 @@ static bool is_incorrect_objective(double reference, double objective)
   return std::abs((reference - objective) / reference) > 0.01;
 }
 
-TEST(pdlp_class, run_double)
+// Function to compare cuopt and papilo models
+void compare_models(const cuopt::mps_parser::mps_data_model_t& cuopt_model,
+                    const papilo::Problem& papilo_model,
+                    const std::string& instance_name)
 {
-  const raft::handle_t handle_{};
-
-  auto path = make_path_absolute("linear_programming/afiro_original.mps");
-  cuopt::mps_parser::mps_data_model_t op_problem =
-    cuopt::mps_parser::parse_mps(path, true);
-
-  auto solver_settings   = pdlp_solver_settings_t{};
-  solver_settings.method = cuopt::linear_programming::method_t::PDLP;
-
-  optimization_problem_solution_t solution =
-    solve_lp(&handle_, op_problem, solver_settings);
-  EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
-  EXPECT_FALSE(is_incorrect_objective(
-    afiro_primal_objective, solution.get_additional_termination_information().primal_objective));
-}
-
-TEST(pdlp_class, run_double_very_low_accuracy)
-{
-  const raft::handle_t handle_{};
-
-  auto path = make_path_absolute("linear_programming/afiro_original.mps");
-  cuopt::mps_parser::mps_data_model_t op_problem =
-    cuopt::mps_parser::parse_mps(path, true);
-
-  cuopt::linear_programming::pdlp_solver_settings_t settings =
-    cuopt::linear_programming::pdlp_solver_settings_t{};
-  // With all 0 afiro with return an error
-  // Setting absolute tolerance to the minimal value of 1e-12 will make it work
-  settings.tolerances.absolute_dual_tolerance   = settings.minimal_absolute_tolerance;
-  settings.tolerances.relative_dual_tolerance   = 0.0;
-  settings.tolerances.absolute_primal_tolerance = settings.minimal_absolute_tolerance;
-  settings.tolerances.relative_primal_tolerance = 0.0;
-  settings.tolerances.absolute_gap_tolerance    = settings.minimal_absolute_tolerance;
-  settings.tolerances.relative_gap_tolerance    = 0.0;
-  settings.method                               = cuopt::linear_programming::method_t::PDLP;
+  std::cout << "\n=== Comparing models for instance: " << instance_name << " ===" << std::endl;
+
+  // Problem dimensions
+  std::cout << "\n--- Problem Dimensions ---" << std::endl;
+  std::cout << "CuOpt  - Variables: " << cuopt_model.get_n_variables()
+            << ", Constraints: " << cuopt_model.get_n_constraints()
+            << ", NNZ: " << cuopt_model.get_nnz() << std::endl;
+  std::cout << "Papilo - Variables: " << papilo_model.getNCols()
+            << ", Constraints: " << papilo_model.getNRows() << std::endl;
+
+  // Problem name
+  std::cout << "\n--- Problem Names ---" << std::endl;
+  std::cout << "CuOpt problem name: '" << cuopt_model.get_problem_name() << "'" << std::endl;
+  std::cout << "Papilo problem name: '" << papilo_model.getName() << "'" << std::endl;
+
+  // Objective properties
+  std::cout << "\n--- Objective Properties ---" << std::endl;
+  std::cout << "CuOpt  - Maximize: " << cuopt_model.get_sense()
+            << ", Offset: " << cuopt_model.get_objective_offset()
+            << ", Scaling: " << cuopt_model.get_objective_scaling_factor() << std::endl;
+  std::cout << "Papilo - Offset: " << papilo_model.getObjective().offset << std::endl;
+
+  // Print some vectors using cuopt::print
+  std::cout << "\n--- Objective Coefficients (first 10) ---" << std::endl;
+  auto cuopt_obj  = cuopt_model.get_objective_coefficients();
+  auto papilo_obj = papilo_model.getObjective().coefficients;
+  if (!cuopt_obj.empty()) {
+    std::vector cuopt_obj_subset(cuopt_obj.begin(),
+                                         cuopt_obj.begin() + std::min(10ul, cuopt_obj.size()));
+    cuopt::print("CuOpt objective coefficients", cuopt_obj_subset);
+  }
+  if (!papilo_obj.empty()) {
+    std::vector papilo_obj_subset(papilo_obj.begin(),
+                                          papilo_obj.begin() + std::min(10ul, papilo_obj.size()));
+    cuopt::print("Papilo objective coefficients", papilo_obj_subset);
+  }
 
-  optimization_problem_solution_t solution = solve_lp(&handle_, op_problem, settings);
-  EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
-  EXPECT_FALSE(is_incorrect_objective(
-    afiro_primal_objective, solution.get_additional_termination_information().primal_objective));
-}
+  // Variable bounds
+  std::cout << "\n--- Variable Bounds (first 10) ---" << std::endl;
+  auto cuopt_lb  = cuopt_model.get_variable_lower_bounds();
+  auto cuopt_ub  = cuopt_model.get_variable_upper_bounds();
+  auto papilo_lb = papilo_model.getLowerBounds();
+  auto papilo_ub = papilo_model.getUpperBounds();
+
+  if (!cuopt_lb.empty()) {
+    std::vector cuopt_lb_subset(cuopt_lb.begin(),
+                                        cuopt_lb.begin() + std::min(10ul, cuopt_lb.size()));
+    cuopt::print("CuOpt variable lower bounds", cuopt_lb_subset);
+  }
+  if (!cuopt_ub.empty()) {
+    std::vector cuopt_ub_subset(cuopt_ub.begin(),
+                                        cuopt_ub.begin() + std::min(10ul, cuopt_ub.size()));
+    cuopt::print("CuOpt variable upper bounds", cuopt_ub_subset);
+  }
+  if (!papilo_lb.empty()) {
+    std::vector papilo_lb_subset(papilo_lb.begin(),
+                                         papilo_lb.begin() + std::min(10ul, papilo_lb.size()));
+    cuopt::print("Papilo variable lower bounds", papilo_lb_subset);
+  }
+  if (!papilo_ub.empty()) {
+    std::vector papilo_ub_subset(papilo_ub.begin(),
+                                         papilo_ub.begin() + std::min(10ul, papilo_ub.size()));
+    cuopt::print("Papilo variable upper bounds", papilo_ub_subset);
+  }
 
-TEST(pdlp_class, run_double_initial_solution)
-{
-  const raft::handle_t handle_{};
+  // Constraint bounds
+  std::cout << "\n--- Constraint Bounds (first 10) ---" << std::endl;
+  auto cuopt_clb          = cuopt_model.get_constraint_lower_bounds();
+  auto cuopt_cub          = cuopt_model.get_constraint_upper_bounds();
+  auto& constraint_matrix = papilo_model.getConstraintMatrix();
+  auto papilo_clb         = constraint_matrix.getLeftHandSides();
+  auto papilo_crb         = constraint_matrix.getRightHandSides();
+
+  if (!cuopt_clb.empty()) {
+    std::vector cuopt_clb_subset(cuopt_clb.begin(),
+                                         cuopt_clb.begin() + std::min(10ul, cuopt_clb.size()));
+    cuopt::print("CuOpt constraint lower bounds", cuopt_clb_subset);
+  }
+  if (!cuopt_cub.empty()) {
+    std::vector cuopt_cub_subset(cuopt_cub.begin(),
+                                         cuopt_cub.begin() + std::min(10ul, cuopt_cub.size()));
+    cuopt::print("CuOpt constraint upper bounds", cuopt_cub_subset);
+  }
+  if (!papilo_clb.empty()) {
+    std::vector papilo_clb_subset(papilo_clb.begin(),
+                                          papilo_clb.begin() + std::min(10ul, papilo_clb.size()));
+    cuopt::print("Papilo constraint left hand sides", papilo_clb_subset);
+  }
+  if (!papilo_crb.empty()) {
+    std::vector papilo_crb_subset(papilo_crb.begin(),
+                                          papilo_crb.begin() + std::min(10ul, papilo_crb.size()));
+    cuopt::print("Papilo constraint right hand sides", papilo_crb_subset);
+  }
 
-  auto path = make_path_absolute("linear_programming/afiro_original.mps");
-  cuopt::mps_parser::mps_data_model_t op_problem =
-    cuopt::mps_parser::parse_mps(path, true);
+  // Constraint matrix values
+  std::cout << "\n--- Constraint Matrix Values (first 10) ---" << std::endl;
+  auto cuopt_A         = cuopt_model.get_constraint_matrix_values();
+  auto cuopt_A_indices = cuopt_model.get_constraint_matrix_indices();
+  auto cuopt_A_offsets = cuopt_model.get_constraint_matrix_offsets();
 
-  std::vector inital_primal_sol(op_problem.get_n_variables());
-  std::fill(inital_primal_sol.begin(), inital_primal_sol.end(), 1.0);
-  op_problem.set_initial_primal_solution(inital_primal_sol.data(), inital_primal_sol.size());
+  if (!cuopt_A.empty()) {
+    std::vector cuopt_A_subset(cuopt_A.begin(),
+                                       cuopt_A.begin() + std::min(10ul, cuopt_A.size()));
+    cuopt::print("CuOpt matrix values", cuopt_A_subset);
+  }
+  if (!cuopt_A_indices.empty()) {
+    std::vector cuopt_indices_subset(
+      cuopt_A_indices.begin(), cuopt_A_indices.begin() + std::min(10ul, cuopt_A_indices.size()));
+    cuopt::print("CuOpt matrix indices", cuopt_indices_subset);
+  }
+  if (!cuopt_A_offsets.empty()) {
+    std::vector cuopt_offsets_subset(
+      cuopt_A_offsets.begin(), cuopt_A_offsets.begin() + std::min(10ul, cuopt_A_offsets.size()));
+    cuopt::print("CuOpt matrix offsets", cuopt_offsets_subset);
+  }
 
-  auto solver_settings   = pdlp_solver_settings_t{};
-  solver_settings.method = cuopt::linear_programming::method_t::PDLP;
+  // Variable names
+  std::cout << "\n--- Variable Names (first 5) ---" << std::endl;
+  auto cuopt_var_names  = cuopt_model.get_variable_names();
+  auto papilo_var_names = papilo_model.getVariableNames();
 
-  optimization_problem_solution_t solution =
-    solve_lp(&handle_, op_problem, solver_settings);
-  EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
-  EXPECT_FALSE(is_incorrect_objective(
-    afiro_primal_objective, solution.get_additional_termination_information().primal_objective));
-}
+  std::cout << "CuOpt variable names: ";
+  for (size_t i = 0; i < std::min(5ul, cuopt_var_names.size()); ++i) {
+    std::cout << "'" << cuopt_var_names[i] << "' ";
+  }
+  std::cout << std::endl;
 
-TEST(pdlp_class, run_iteration_limit)
-{
-  const raft::handle_t handle_{};
+  std::cout << "Papilo variable names: ";
+  for (size_t i = 0; i < std::min(5ul, papilo_var_names.size()); ++i) {
+    std::cout << "'" << papilo_var_names[i] << "' ";
+  }
+  std::cout << std::endl;
 
-  auto path = make_path_absolute("linear_programming/afiro_original.mps");
-  cuopt::mps_parser::mps_data_model_t op_problem =
-    cuopt::mps_parser::parse_mps(path, true);
+  // Row names
+  std::cout << "\n--- Row Names (first 5) ---" << std::endl;
+  auto cuopt_row_names  = cuopt_model.get_row_names();
+  auto papilo_row_names = papilo_model.getConstraintNames();
 
-  cuopt::linear_programming::pdlp_solver_settings_t settings =
-    cuopt::linear_programming::pdlp_solver_settings_t{};
+  std::cout << "CuOpt row names: ";
+  for (size_t i = 0; i < std::min(5ul, cuopt_row_names.size()); ++i) {
+    std::cout << "'" << cuopt_row_names[i] << "' ";
+  }
+  std::cout << std::endl;
 
-  settings.iteration_limit = 10;
-  // To make sure it doesn't return before the iteration limit
-  settings.set_optimality_tolerance(0);
-  settings.method = cuopt::linear_programming::method_t::PDLP;
+  std::cout << "Papilo row names: ";
+  for (size_t i = 0; i < std::min(5ul, papilo_row_names.size()); ++i) {
+    std::cout << "'" << papilo_row_names[i] << "' ";
+  }
+  std::cout << std::endl;
 
-  optimization_problem_solution_t solution = solve_lp(&handle_, op_problem, settings);
-  EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_ITERATION_LIMIT);
-  // By default we would return all 0, we now return what we currently have so not all 0
-  EXPECT_FALSE(thrust::all_of(handle_.get_thrust_policy(),
-                              solution.get_primal_solution().begin(),
-                              solution.get_primal_solution().end(),
-                              thrust::placeholders::_1 == 0.0));
+  std::cout << "\n=== End comparison for " << instance_name << " ===\n" << std::endl;
 }
 
-TEST(pdlp_class, run_time_limit)
-{
-  const raft::handle_t handle_{};
-  auto path = make_path_absolute("linear_programming/savsched1/savsched1.mps");
-  cuopt::mps_parser::mps_data_model_t op_problem =
-    cuopt::mps_parser::parse_mps(path);
-
-  cuopt::linear_programming::pdlp_solver_settings_t settings =
-    cuopt::linear_programming::pdlp_solver_settings_t{};
-
-  // 200 ms
-  constexpr double time_limit_seconds = 0.2;
-  settings.time_limit                 = time_limit_seconds;
-  // To make sure it doesn't return before the time limit
-  settings.set_optimality_tolerance(0);
-  settings.method = cuopt::linear_programming::method_t::PDLP;
-
-  optimization_problem_solution_t solution = solve_lp(&handle_, op_problem, settings);
-
-  EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_TIME_LIMIT);
-  // By default we would return all 0, we now return what we currently have so not all 0
-  EXPECT_FALSE(thrust::all_of(handle_.get_thrust_policy(),
-                              solution.get_primal_solution().begin(),
-                              solution.get_primal_solution().end(),
-                              thrust::placeholders::_1 == 0.0));
-  // Check that indeed it didn't run for more than x time
-  EXPECT_TRUE(solution.get_additional_termination_information().solve_time <
-              (time_limit_seconds * 5) * 1000);
-}
+// TEST(pdlp_class, run_double)
+// {
+//   const raft::handle_t handle_{};
+
+//   auto path = make_path_absolute("linear_programming/afiro_original.mps");
+//   cuopt::mps_parser::mps_data_model_t op_problem =
+//     cuopt::mps_parser::parse_mps(path, true);
+
+//   auto solver_settings   = pdlp_solver_settings_t{};
+//   solver_settings.method = cuopt::linear_programming::method_t::PDLP;
+
+//   optimization_problem_solution_t solution =
+//     solve_lp(&handle_, op_problem, solver_settings);
+//   EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
+//   EXPECT_FALSE(is_incorrect_objective(
+//     afiro_primal_objective, solution.get_additional_termination_information().primal_objective));
+// }
+
+// TEST(pdlp_class, run_double_very_low_accuracy)
+// {
+//   const raft::handle_t handle_{};
+
+//   auto path = make_path_absolute("linear_programming/afiro_original.mps");
+//   cuopt::mps_parser::mps_data_model_t op_problem =
+//     cuopt::mps_parser::parse_mps(path, true);
+
+//   cuopt::linear_programming::pdlp_solver_settings_t settings =
+//     cuopt::linear_programming::pdlp_solver_settings_t{};
+//   // With all 0 afiro with return an error
+//   // Setting absolute tolerance to the minimal value of 1e-12 will make it work
+//   settings.tolerances.absolute_dual_tolerance   = settings.minimal_absolute_tolerance;
+//   settings.tolerances.relative_dual_tolerance   = 0.0;
+//   settings.tolerances.absolute_primal_tolerance = settings.minimal_absolute_tolerance;
+//   settings.tolerances.relative_primal_tolerance = 0.0;
+//   settings.tolerances.absolute_gap_tolerance    = settings.minimal_absolute_tolerance;
+//   settings.tolerances.relative_gap_tolerance    = 0.0;
+//   settings.method                               = cuopt::linear_programming::method_t::PDLP;
+
+//   optimization_problem_solution_t solution = solve_lp(&handle_, op_problem,
+//   settings); EXPECT_EQ((int)solution.get_termination_status(),
+//   CUOPT_TERIMINATION_STATUS_OPTIMAL); EXPECT_FALSE(is_incorrect_objective(
+//     afiro_primal_objective, solution.get_additional_termination_information().primal_objective));
+// }
+
+// TEST(pdlp_class, run_double_initial_solution)
+// {
+//   const raft::handle_t handle_{};
+
+//   auto path = make_path_absolute("linear_programming/afiro_original.mps");
+//   cuopt::mps_parser::mps_data_model_t op_problem =
+//     cuopt::mps_parser::parse_mps(path, true);
+
+//   std::vector inital_primal_sol(op_problem.get_n_variables());
+//   std::fill(inital_primal_sol.begin(), inital_primal_sol.end(), 1.0);
+//   op_problem.set_initial_primal_solution(inital_primal_sol.data(), inital_primal_sol.size());
+
+//   auto solver_settings   = pdlp_solver_settings_t{};
+//   solver_settings.method = cuopt::linear_programming::method_t::PDLP;
+
+//   optimization_problem_solution_t solution =
+//     solve_lp(&handle_, op_problem, solver_settings);
+//   EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
+//   EXPECT_FALSE(is_incorrect_objective(
+//     afiro_primal_objective, solution.get_additional_termination_information().primal_objective));
+// }
+
+// TEST(pdlp_class, run_iteration_limit)
+// {
+//   const raft::handle_t handle_{};
+
+//   auto path = make_path_absolute("linear_programming/afiro_original.mps");
+//   cuopt::mps_parser::mps_data_model_t op_problem =
+//     cuopt::mps_parser::parse_mps(path, true);
+
+//   cuopt::linear_programming::pdlp_solver_settings_t settings =
+//     cuopt::linear_programming::pdlp_solver_settings_t{};
+
+//   settings.iteration_limit = 10;
+//   // To make sure it doesn't return before the iteration limit
+//   settings.set_optimality_tolerance(0);
+//   settings.method = cuopt::linear_programming::method_t::PDLP;
+
+//   optimization_problem_solution_t solution = solve_lp(&handle_, op_problem,
+//   settings); EXPECT_EQ((int)solution.get_termination_status(),
+//   CUOPT_TERIMINATION_STATUS_ITERATION_LIMIT);
+//   // By default we would return all 0, we now return what we currently have so not all 0
+//   EXPECT_FALSE(thrust::all_of(handle_.get_thrust_policy(),
+//                               solution.get_primal_solution().begin(),
+//                               solution.get_primal_solution().end(),
+//                               thrust::placeholders::_1 == 0.0));
+// }
+
+// TEST(pdlp_class, run_time_limit)
+// {
+//   const raft::handle_t handle_{};
+//   auto path = make_path_absolute("linear_programming/savsched1/savsched1.mps");
+//   cuopt::mps_parser::mps_data_model_t op_problem =
+//     cuopt::mps_parser::parse_mps(path);
+
+//   cuopt::linear_programming::pdlp_solver_settings_t settings =
+//     cuopt::linear_programming::pdlp_solver_settings_t{};
+
+//   // 200 ms
+//   constexpr double time_limit_seconds = 0.2;
+//   settings.time_limit                 = time_limit_seconds;
+//   // To make sure it doesn't return before the time limit
+//   settings.set_optimality_tolerance(0);
+//   settings.method = cuopt::linear_programming::method_t::PDLP;
+
+//   optimization_problem_solution_t solution = solve_lp(&handle_, op_problem,
+//   settings);
+
+//   EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_TIME_LIMIT);
+//   // By default we would return all 0, we now return what we currently have so not all 0
+//   EXPECT_FALSE(thrust::all_of(handle_.get_thrust_policy(),
+//                               solution.get_primal_solution().begin(),
+//                               solution.get_primal_solution().end(),
+//                               thrust::placeholders::_1 == 0.0));
+//   // Check that indeed it didn't run for more than x time
+//   EXPECT_TRUE(solution.get_additional_termination_information().solve_time <
+//               (time_limit_seconds * 5) * 1000);
+// }
 
 TEST(pdlp_class, run_sub_mittleman)
 {
   std::vector>      // Expected objective value
-    instances{                        // {"graph40-40", -300.0},
-              {"ex10", 100.0003411893773},
-              {"datt256_lp", 255.9992298290425},
-              {"woodlands09", 0.0},
-              {"savsched1", 217.4054085795689},
-              {"nug08-3rd", 214.0141488989151},
-              {"qap15", 1040.999546647414},
-              {"scpm1", 413.7787723060584},
-              {"neos3", 27773.54059633068},
-              {"a2864", -282.9962521965164}};
+    instances{
+      // {"graph40-40", -300.0},
+      //{"ex10", 100.0003411893773},
+      {"datt256_lp", 255.9992298290425},
+      // {"woodlands09", 0.0},
+      // {"savsched1", 217.4054085795689},
+      // {"nug08-3rd", 214.0141488989151},
+      // {"qap15", 1040.999546647414},
+      // {"scpm1", 413.7787723060584},
+      // {"neos3", 27773.54059633068},
+      // {"a2864", -282.9962521965164}
+    };
 
   for (const auto& entry : instances) {
     const auto& name                    = entry.first;
@@ -207,9 +374,82 @@ TEST(pdlp_class, run_sub_mittleman)
 
     std::cout << "Running " << name << std::endl;
     auto path = make_path_absolute("linear_programming/" + name + "/" + name + ".mps");
-    cuopt::mps_parser::mps_data_model_t op_problem =
+
+    // Parse with CuOpt
+    cuopt::mps_parser::mps_data_model_t cuopt_op_problem =
       cuopt::mps_parser::parse_mps(path);
 
+    // Parse with Papilo
+    auto papilo_problem_opt = papilo::MpsParser::loadProblem(path);
+    if (!papilo_problem_opt) {
+      std::cout << "Failed to parse " << name << " with Papilo parser" << std::endl;
+      continue;
+    }
+    auto papilo_problem = papilo_problem_opt.value();
+
+    // Presolve the Papilo problem using cuOpt's configuration
+    std::cout << "Original Papilo problem - Variables: " << papilo_problem.getNCols()
+              << ", Constraints: " << papilo_problem.getNRows() << std::endl;
+
+    papilo::Presolve presolve;
+
+    // Add cuOpt's specific presolve methods (same as in third_party_presolve.cu)
+    using uptr = std::unique_ptr>;
+
+    // fast presolvers
+    presolve.addPresolveMethod(uptr(new papilo::SingletonCols()));
+    presolve.addPresolveMethod(uptr(new papilo::CoefficientStrengthening()));
+    presolve.addPresolveMethod(uptr(new papilo::ConstraintPropagation()));
+
+    // medium presolvers
+    presolve.addPresolveMethod(uptr(new papilo::FixContinuous()));
+    presolve.addPresolveMethod(uptr(new papilo::SimpleProbing()));
+    presolve.addPresolveMethod(uptr(new papilo::ParallelRowDetection()));
+    presolve.addPresolveMethod(uptr(new papilo::ParallelColDetection()));
+    // Note: SingletonStuffing excluded due to postsolve issues in cuOpt
+    presolve.addPresolveMethod(uptr(new papilo::DualFix()));
+    presolve.addPresolveMethod(uptr(new papilo::SimplifyInequalities()));
+
+    // exhaustive presolvers
+    presolve.addPresolveMethod(uptr(new papilo::ImplIntDetection()));
+    presolve.addPresolveMethod(uptr(new papilo::DominatedCols()));
+    presolve.addPresolveMethod(uptr(new papilo::Probing()));
+
+    // Set cuOpt's presolve options for LP problems
+    constexpr double absolute_tolerance            = 1e-4;  // typical LP tolerance
+    constexpr double time_limit                    = 10.0;  // 10 seconds default
+    presolve.getPresolveOptions().tlim             = time_limit;
+    presolve.getPresolveOptions().epsilon          = absolute_tolerance;
+    presolve.getPresolveOptions().feastol          = absolute_tolerance;
+    presolve.getPresolveOptions().componentsmaxint = -1;  // for LP problems
+    presolve.getPresolveOptions().detectlindep     = 0;   // for LP problems
+
+    papilo::PresolveResult presolve_result = presolve.apply(papilo_problem);
+
+    std::cout << "Presolve status: ";
+    switch (presolve_result.status) {
+      case papilo::PresolveStatus::kUnchanged: std::cout << "Unchanged"; break;
+      case papilo::PresolveStatus::kReduced: std::cout << "Reduced"; break;
+      case papilo::PresolveStatus::kUnbndOrInfeas: std::cout << "Unbounded or Infeasible"; break;
+      case papilo::PresolveStatus::kUnbounded: std::cout << "Unbounded"; break;
+      case papilo::PresolveStatus::kInfeasible: std::cout << "Infeasible"; break;
+    }
+    std::cout << std::endl;
+
+    std::cout << "Presolved Papilo problem - Variables: " << papilo_problem.getNCols()
+              << ", Constraints: " << papilo_problem.getNRows() << std::endl;
+
+    // Check if presolving detected infeasibility/unboundedness
+    if (presolve_result.status == papilo::PresolveStatus::kInfeasible ||
+        presolve_result.status == papilo::PresolveStatus::kUnbounded ||
+        presolve_result.status == papilo::PresolveStatus::kUnbndOrInfeas) {
+      std::cout << "Skipping " << name << " due to presolve status" << std::endl;
+      continue;
+    }
+
+    // Compare the two models
+    compare_models(cuopt_op_problem, papilo_problem, name);
+
     // Testing for each solver_mode is ok as it's parsing that is the bottleneck here, not
     // solving
     auto solver_mode_list = {
@@ -221,17 +461,18 @@ TEST(pdlp_class, run_sub_mittleman)
       auto settings             = pdlp_solver_settings_t{};
       settings.pdlp_solver_mode = solver_mode;
       settings.method           = cuopt::linear_programming::method_t::PDLP;
+      settings.presolve         = true;
       const raft::handle_t handle_{};
       optimization_problem_solution_t solution =
-        solve_lp(&handle_, op_problem, settings);
+        solve_lp(&handle_, cuopt_op_problem, settings);
       EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
       EXPECT_FALSE(
         is_incorrect_objective(expected_objective_value,
                                solution.get_additional_termination_information().primal_objective));
-      test_objective_sanity(op_problem,
+      test_objective_sanity(cuopt_op_problem,
                             solution.get_primal_solution(),
                             solution.get_additional_termination_information().primal_objective);
-      test_constraint_sanity(op_problem, solution);
+      test_constraint_sanity(cuopt_op_problem, solution);
     }
   }
 }

From 65d10a534651c2c16548d0ba43edf6e922301c75 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Thu, 31 Jul 2025 11:38:57 -0700
Subject: [PATCH 053/108] Remove dead code

---
 cpp/src/mip/presolve/third_party_presolve.cu |  19 +-
 cpp/tests/linear_programming/pdlp_test.cu    | 485 +++++--------------
 2 files changed, 126 insertions(+), 378 deletions(-)

diff --git a/cpp/src/mip/presolve/third_party_presolve.cu b/cpp/src/mip/presolve/third_party_presolve.cu
index f2f41e3807..4041f97598 100644
--- a/cpp/src/mip/presolve/third_party_presolve.cu
+++ b/cpp/src/mip/presolve/third_party_presolve.cu
@@ -248,12 +248,10 @@ void set_presolve_methods(papilo::Presolve& presolver, problem_category_t c
   presolver.addPresolveMethod(uptr(new papilo::DominatedCols()));
   presolver.addPresolveMethod(uptr(new papilo::Probing()));
 
-  // if (category == problem_category_t::MIP) {
-  // presolver.addPresolveMethod(uptr(new papilo::DualInfer));
-  // presolver.addPresolveMethod(uptr(new papilo::SimpleSubstitution()));
-  // presolver.addPresolveMethod(uptr(new papilo::Sparsify()));
-  // presolver.addPresolveMethod(uptr(new papilo::Substitution()));
-  // }
+  presolver.addPresolveMethod(uptr(new papilo::DualInfer));
+  presolver.addPresolveMethod(uptr(new papilo::SimpleSubstitution()));
+  presolver.addPresolveMethod(uptr(new papilo::Sparsify()));
+  presolver.addPresolveMethod(uptr(new papilo::Substitution()));
 }
 
 template 
@@ -265,10 +263,6 @@ void set_presolve_options(papilo::Presolve& presolver,
   presolver.getPresolveOptions().tlim    = time_limit;
   presolver.getPresolveOptions().epsilon = absolute_tolerance;
   presolver.getPresolveOptions().feastol = absolute_tolerance;
-  // if (category == problem_category_t::LP) {
-  presolver.getPresolveOptions().componentsmaxint = -1;
-  presolver.getPresolveOptions().detectlindep     = 0;
-  // }
 }
 
 template 
@@ -318,11 +312,6 @@ void third_party_presolve_t::undo(rmm::device_uvector& primal_sol
 
   papilo::Solution reduced_sol(primal_sol_vec_h);
   papilo::Solution full_sol;
-  // if (category == problem_category_t::LP) {
-  //   reduced_sol.type         = papilo::SolutionType::kPrimalDual;
-  //   reduced_sol.dual         = dual_sol_vec_h;
-  //   reduced_sol.reducedCosts = reduced_costs_vec_h;
-  // }
 
   papilo::Message Msg{};
   Msg.setVerbosityLevel(papilo::VerbosityLevel::kQuiet);
diff --git a/cpp/tests/linear_programming/pdlp_test.cu b/cpp/tests/linear_programming/pdlp_test.cu
index 24ac0c8c49..a3a9cf30b5 100644
--- a/cpp/tests/linear_programming/pdlp_test.cu
+++ b/cpp/tests/linear_programming/pdlp_test.cu
@@ -36,11 +36,6 @@
 #include 
 #include 
 
-// Papilo includes
-#include 
-#include 
-#include 
-
 #include 
 #include 
 #include 
@@ -70,303 +65,141 @@ static bool is_incorrect_objective(double reference, double objective)
   return std::abs((reference - objective) / reference) > 0.01;
 }
 
-// Function to compare cuopt and papilo models
-void compare_models(const cuopt::mps_parser::mps_data_model_t& cuopt_model,
-                    const papilo::Problem& papilo_model,
-                    const std::string& instance_name)
+TEST(pdlp_class, run_double)
 {
-  std::cout << "\n=== Comparing models for instance: " << instance_name << " ===" << std::endl;
-
-  // Problem dimensions
-  std::cout << "\n--- Problem Dimensions ---" << std::endl;
-  std::cout << "CuOpt  - Variables: " << cuopt_model.get_n_variables()
-            << ", Constraints: " << cuopt_model.get_n_constraints()
-            << ", NNZ: " << cuopt_model.get_nnz() << std::endl;
-  std::cout << "Papilo - Variables: " << papilo_model.getNCols()
-            << ", Constraints: " << papilo_model.getNRows() << std::endl;
-
-  // Problem name
-  std::cout << "\n--- Problem Names ---" << std::endl;
-  std::cout << "CuOpt problem name: '" << cuopt_model.get_problem_name() << "'" << std::endl;
-  std::cout << "Papilo problem name: '" << papilo_model.getName() << "'" << std::endl;
-
-  // Objective properties
-  std::cout << "\n--- Objective Properties ---" << std::endl;
-  std::cout << "CuOpt  - Maximize: " << cuopt_model.get_sense()
-            << ", Offset: " << cuopt_model.get_objective_offset()
-            << ", Scaling: " << cuopt_model.get_objective_scaling_factor() << std::endl;
-  std::cout << "Papilo - Offset: " << papilo_model.getObjective().offset << std::endl;
-
-  // Print some vectors using cuopt::print
-  std::cout << "\n--- Objective Coefficients (first 10) ---" << std::endl;
-  auto cuopt_obj  = cuopt_model.get_objective_coefficients();
-  auto papilo_obj = papilo_model.getObjective().coefficients;
-  if (!cuopt_obj.empty()) {
-    std::vector cuopt_obj_subset(cuopt_obj.begin(),
-                                         cuopt_obj.begin() + std::min(10ul, cuopt_obj.size()));
-    cuopt::print("CuOpt objective coefficients", cuopt_obj_subset);
-  }
-  if (!papilo_obj.empty()) {
-    std::vector papilo_obj_subset(papilo_obj.begin(),
-                                          papilo_obj.begin() + std::min(10ul, papilo_obj.size()));
-    cuopt::print("Papilo objective coefficients", papilo_obj_subset);
-  }
+  const raft::handle_t handle_{};
 
-  // Variable bounds
-  std::cout << "\n--- Variable Bounds (first 10) ---" << std::endl;
-  auto cuopt_lb  = cuopt_model.get_variable_lower_bounds();
-  auto cuopt_ub  = cuopt_model.get_variable_upper_bounds();
-  auto papilo_lb = papilo_model.getLowerBounds();
-  auto papilo_ub = papilo_model.getUpperBounds();
-
-  if (!cuopt_lb.empty()) {
-    std::vector cuopt_lb_subset(cuopt_lb.begin(),
-                                        cuopt_lb.begin() + std::min(10ul, cuopt_lb.size()));
-    cuopt::print("CuOpt variable lower bounds", cuopt_lb_subset);
-  }
-  if (!cuopt_ub.empty()) {
-    std::vector cuopt_ub_subset(cuopt_ub.begin(),
-                                        cuopt_ub.begin() + std::min(10ul, cuopt_ub.size()));
-    cuopt::print("CuOpt variable upper bounds", cuopt_ub_subset);
-  }
-  if (!papilo_lb.empty()) {
-    std::vector papilo_lb_subset(papilo_lb.begin(),
-                                         papilo_lb.begin() + std::min(10ul, papilo_lb.size()));
-    cuopt::print("Papilo variable lower bounds", papilo_lb_subset);
-  }
-  if (!papilo_ub.empty()) {
-    std::vector papilo_ub_subset(papilo_ub.begin(),
-                                         papilo_ub.begin() + std::min(10ul, papilo_ub.size()));
-    cuopt::print("Papilo variable upper bounds", papilo_ub_subset);
-  }
+  auto path = make_path_absolute("linear_programming/afiro_original.mps");
+  cuopt::mps_parser::mps_data_model_t op_problem =
+    cuopt::mps_parser::parse_mps(path, true);
 
-  // Constraint bounds
-  std::cout << "\n--- Constraint Bounds (first 10) ---" << std::endl;
-  auto cuopt_clb          = cuopt_model.get_constraint_lower_bounds();
-  auto cuopt_cub          = cuopt_model.get_constraint_upper_bounds();
-  auto& constraint_matrix = papilo_model.getConstraintMatrix();
-  auto papilo_clb         = constraint_matrix.getLeftHandSides();
-  auto papilo_crb         = constraint_matrix.getRightHandSides();
-
-  if (!cuopt_clb.empty()) {
-    std::vector cuopt_clb_subset(cuopt_clb.begin(),
-                                         cuopt_clb.begin() + std::min(10ul, cuopt_clb.size()));
-    cuopt::print("CuOpt constraint lower bounds", cuopt_clb_subset);
-  }
-  if (!cuopt_cub.empty()) {
-    std::vector cuopt_cub_subset(cuopt_cub.begin(),
-                                         cuopt_cub.begin() + std::min(10ul, cuopt_cub.size()));
-    cuopt::print("CuOpt constraint upper bounds", cuopt_cub_subset);
-  }
-  if (!papilo_clb.empty()) {
-    std::vector papilo_clb_subset(papilo_clb.begin(),
-                                          papilo_clb.begin() + std::min(10ul, papilo_clb.size()));
-    cuopt::print("Papilo constraint left hand sides", papilo_clb_subset);
-  }
-  if (!papilo_crb.empty()) {
-    std::vector papilo_crb_subset(papilo_crb.begin(),
-                                          papilo_crb.begin() + std::min(10ul, papilo_crb.size()));
-    cuopt::print("Papilo constraint right hand sides", papilo_crb_subset);
-  }
+  auto solver_settings   = pdlp_solver_settings_t{};
+  solver_settings.method = cuopt::linear_programming::method_t::PDLP;
 
-  // Constraint matrix values
-  std::cout << "\n--- Constraint Matrix Values (first 10) ---" << std::endl;
-  auto cuopt_A         = cuopt_model.get_constraint_matrix_values();
-  auto cuopt_A_indices = cuopt_model.get_constraint_matrix_indices();
-  auto cuopt_A_offsets = cuopt_model.get_constraint_matrix_offsets();
+  optimization_problem_solution_t solution =
+    solve_lp(&handle_, op_problem, solver_settings);
+  EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
+  EXPECT_FALSE(is_incorrect_objective(
+    afiro_primal_objective, solution.get_additional_termination_information().primal_objective));
+}
 
-  if (!cuopt_A.empty()) {
-    std::vector cuopt_A_subset(cuopt_A.begin(),
-                                       cuopt_A.begin() + std::min(10ul, cuopt_A.size()));
-    cuopt::print("CuOpt matrix values", cuopt_A_subset);
-  }
-  if (!cuopt_A_indices.empty()) {
-    std::vector cuopt_indices_subset(
-      cuopt_A_indices.begin(), cuopt_A_indices.begin() + std::min(10ul, cuopt_A_indices.size()));
-    cuopt::print("CuOpt matrix indices", cuopt_indices_subset);
-  }
-  if (!cuopt_A_offsets.empty()) {
-    std::vector cuopt_offsets_subset(
-      cuopt_A_offsets.begin(), cuopt_A_offsets.begin() + std::min(10ul, cuopt_A_offsets.size()));
-    cuopt::print("CuOpt matrix offsets", cuopt_offsets_subset);
-  }
+TEST(pdlp_class, run_double_very_low_accuracy)
+{
+  const raft::handle_t handle_{};
 
-  // Variable names
-  std::cout << "\n--- Variable Names (first 5) ---" << std::endl;
-  auto cuopt_var_names  = cuopt_model.get_variable_names();
-  auto papilo_var_names = papilo_model.getVariableNames();
+  auto path = make_path_absolute("linear_programming/afiro_original.mps");
+  cuopt::mps_parser::mps_data_model_t op_problem =
+    cuopt::mps_parser::parse_mps(path, true);
 
-  std::cout << "CuOpt variable names: ";
-  for (size_t i = 0; i < std::min(5ul, cuopt_var_names.size()); ++i) {
-    std::cout << "'" << cuopt_var_names[i] << "' ";
-  }
-  std::cout << std::endl;
+  cuopt::linear_programming::pdlp_solver_settings_t settings =
+    cuopt::linear_programming::pdlp_solver_settings_t{};
+  // With all 0 afiro with return an error
+  // Setting absolute tolerance to the minimal value of 1e-12 will make it work
+  settings.tolerances.absolute_dual_tolerance   = settings.minimal_absolute_tolerance;
+  settings.tolerances.relative_dual_tolerance   = 0.0;
+  settings.tolerances.absolute_primal_tolerance = settings.minimal_absolute_tolerance;
+  settings.tolerances.relative_primal_tolerance = 0.0;
+  settings.tolerances.absolute_gap_tolerance    = settings.minimal_absolute_tolerance;
+  settings.tolerances.relative_gap_tolerance    = 0.0;
+  settings.method                               = cuopt::linear_programming::method_t::PDLP;
 
-  std::cout << "Papilo variable names: ";
-  for (size_t i = 0; i < std::min(5ul, papilo_var_names.size()); ++i) {
-    std::cout << "'" << papilo_var_names[i] << "' ";
-  }
-  std::cout << std::endl;
+  optimization_problem_solution_t solution = solve_lp(&handle_, op_problem, settings);
+  EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
+  EXPECT_FALSE(is_incorrect_objective(
+    afiro_primal_objective, solution.get_additional_termination_information().primal_objective));
+}
 
-  // Row names
-  std::cout << "\n--- Row Names (first 5) ---" << std::endl;
-  auto cuopt_row_names  = cuopt_model.get_row_names();
-  auto papilo_row_names = papilo_model.getConstraintNames();
+TEST(pdlp_class, run_double_initial_solution)
+{
+  const raft::handle_t handle_{};
 
-  std::cout << "CuOpt row names: ";
-  for (size_t i = 0; i < std::min(5ul, cuopt_row_names.size()); ++i) {
-    std::cout << "'" << cuopt_row_names[i] << "' ";
-  }
-  std::cout << std::endl;
+  auto path = make_path_absolute("linear_programming/afiro_original.mps");
+  cuopt::mps_parser::mps_data_model_t op_problem =
+    cuopt::mps_parser::parse_mps(path, true);
 
-  std::cout << "Papilo row names: ";
-  for (size_t i = 0; i < std::min(5ul, papilo_row_names.size()); ++i) {
-    std::cout << "'" << papilo_row_names[i] << "' ";
-  }
-  std::cout << std::endl;
+  std::vector inital_primal_sol(op_problem.get_n_variables());
+  std::fill(inital_primal_sol.begin(), inital_primal_sol.end(), 1.0);
+  op_problem.set_initial_primal_solution(inital_primal_sol.data(), inital_primal_sol.size());
+
+  auto solver_settings   = pdlp_solver_settings_t{};
+  solver_settings.method = cuopt::linear_programming::method_t::PDLP;
 
-  std::cout << "\n=== End comparison for " << instance_name << " ===\n" << std::endl;
+  optimization_problem_solution_t solution =
+    solve_lp(&handle_, op_problem, solver_settings);
+  EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
+  EXPECT_FALSE(is_incorrect_objective(
+    afiro_primal_objective, solution.get_additional_termination_information().primal_objective));
 }
 
-// TEST(pdlp_class, run_double)
-// {
-//   const raft::handle_t handle_{};
-
-//   auto path = make_path_absolute("linear_programming/afiro_original.mps");
-//   cuopt::mps_parser::mps_data_model_t op_problem =
-//     cuopt::mps_parser::parse_mps(path, true);
-
-//   auto solver_settings   = pdlp_solver_settings_t{};
-//   solver_settings.method = cuopt::linear_programming::method_t::PDLP;
-
-//   optimization_problem_solution_t solution =
-//     solve_lp(&handle_, op_problem, solver_settings);
-//   EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
-//   EXPECT_FALSE(is_incorrect_objective(
-//     afiro_primal_objective, solution.get_additional_termination_information().primal_objective));
-// }
-
-// TEST(pdlp_class, run_double_very_low_accuracy)
-// {
-//   const raft::handle_t handle_{};
-
-//   auto path = make_path_absolute("linear_programming/afiro_original.mps");
-//   cuopt::mps_parser::mps_data_model_t op_problem =
-//     cuopt::mps_parser::parse_mps(path, true);
-
-//   cuopt::linear_programming::pdlp_solver_settings_t settings =
-//     cuopt::linear_programming::pdlp_solver_settings_t{};
-//   // With all 0 afiro with return an error
-//   // Setting absolute tolerance to the minimal value of 1e-12 will make it work
-//   settings.tolerances.absolute_dual_tolerance   = settings.minimal_absolute_tolerance;
-//   settings.tolerances.relative_dual_tolerance   = 0.0;
-//   settings.tolerances.absolute_primal_tolerance = settings.minimal_absolute_tolerance;
-//   settings.tolerances.relative_primal_tolerance = 0.0;
-//   settings.tolerances.absolute_gap_tolerance    = settings.minimal_absolute_tolerance;
-//   settings.tolerances.relative_gap_tolerance    = 0.0;
-//   settings.method                               = cuopt::linear_programming::method_t::PDLP;
-
-//   optimization_problem_solution_t solution = solve_lp(&handle_, op_problem,
-//   settings); EXPECT_EQ((int)solution.get_termination_status(),
-//   CUOPT_TERIMINATION_STATUS_OPTIMAL); EXPECT_FALSE(is_incorrect_objective(
-//     afiro_primal_objective, solution.get_additional_termination_information().primal_objective));
-// }
-
-// TEST(pdlp_class, run_double_initial_solution)
-// {
-//   const raft::handle_t handle_{};
-
-//   auto path = make_path_absolute("linear_programming/afiro_original.mps");
-//   cuopt::mps_parser::mps_data_model_t op_problem =
-//     cuopt::mps_parser::parse_mps(path, true);
-
-//   std::vector inital_primal_sol(op_problem.get_n_variables());
-//   std::fill(inital_primal_sol.begin(), inital_primal_sol.end(), 1.0);
-//   op_problem.set_initial_primal_solution(inital_primal_sol.data(), inital_primal_sol.size());
-
-//   auto solver_settings   = pdlp_solver_settings_t{};
-//   solver_settings.method = cuopt::linear_programming::method_t::PDLP;
-
-//   optimization_problem_solution_t solution =
-//     solve_lp(&handle_, op_problem, solver_settings);
-//   EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
-//   EXPECT_FALSE(is_incorrect_objective(
-//     afiro_primal_objective, solution.get_additional_termination_information().primal_objective));
-// }
-
-// TEST(pdlp_class, run_iteration_limit)
-// {
-//   const raft::handle_t handle_{};
-
-//   auto path = make_path_absolute("linear_programming/afiro_original.mps");
-//   cuopt::mps_parser::mps_data_model_t op_problem =
-//     cuopt::mps_parser::parse_mps(path, true);
-
-//   cuopt::linear_programming::pdlp_solver_settings_t settings =
-//     cuopt::linear_programming::pdlp_solver_settings_t{};
-
-//   settings.iteration_limit = 10;
-//   // To make sure it doesn't return before the iteration limit
-//   settings.set_optimality_tolerance(0);
-//   settings.method = cuopt::linear_programming::method_t::PDLP;
-
-//   optimization_problem_solution_t solution = solve_lp(&handle_, op_problem,
-//   settings); EXPECT_EQ((int)solution.get_termination_status(),
-//   CUOPT_TERIMINATION_STATUS_ITERATION_LIMIT);
-//   // By default we would return all 0, we now return what we currently have so not all 0
-//   EXPECT_FALSE(thrust::all_of(handle_.get_thrust_policy(),
-//                               solution.get_primal_solution().begin(),
-//                               solution.get_primal_solution().end(),
-//                               thrust::placeholders::_1 == 0.0));
-// }
-
-// TEST(pdlp_class, run_time_limit)
-// {
-//   const raft::handle_t handle_{};
-//   auto path = make_path_absolute("linear_programming/savsched1/savsched1.mps");
-//   cuopt::mps_parser::mps_data_model_t op_problem =
-//     cuopt::mps_parser::parse_mps(path);
-
-//   cuopt::linear_programming::pdlp_solver_settings_t settings =
-//     cuopt::linear_programming::pdlp_solver_settings_t{};
-
-//   // 200 ms
-//   constexpr double time_limit_seconds = 0.2;
-//   settings.time_limit                 = time_limit_seconds;
-//   // To make sure it doesn't return before the time limit
-//   settings.set_optimality_tolerance(0);
-//   settings.method = cuopt::linear_programming::method_t::PDLP;
-
-//   optimization_problem_solution_t solution = solve_lp(&handle_, op_problem,
-//   settings);
-
-//   EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_TIME_LIMIT);
-//   // By default we would return all 0, we now return what we currently have so not all 0
-//   EXPECT_FALSE(thrust::all_of(handle_.get_thrust_policy(),
-//                               solution.get_primal_solution().begin(),
-//                               solution.get_primal_solution().end(),
-//                               thrust::placeholders::_1 == 0.0));
-//   // Check that indeed it didn't run for more than x time
-//   EXPECT_TRUE(solution.get_additional_termination_information().solve_time <
-//               (time_limit_seconds * 5) * 1000);
-// }
+TEST(pdlp_class, run_iteration_limit)
+{
+  const raft::handle_t handle_{};
+
+  auto path = make_path_absolute("linear_programming/afiro_original.mps");
+  cuopt::mps_parser::mps_data_model_t op_problem =
+    cuopt::mps_parser::parse_mps(path, true);
+
+  cuopt::linear_programming::pdlp_solver_settings_t settings =
+    cuopt::linear_programming::pdlp_solver_settings_t{};
+
+  settings.iteration_limit = 10;
+  // To make sure it doesn't return before the iteration limit
+  settings.set_optimality_tolerance(0);
+  settings.method = cuopt::linear_programming::method_t::PDLP;
+
+  optimization_problem_solution_t solution = solve_lp(&handle_, op_problem, settings);
+  EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_ITERATION_LIMIT);
+  // By default we would return all 0, we now return what we currently have so not all 0
+  EXPECT_FALSE(thrust::all_of(handle_.get_thrust_policy(),
+                              solution.get_primal_solution().begin(),
+                              solution.get_primal_solution().end(),
+                              thrust::placeholders::_1 == 0.0));
+}
+
+TEST(pdlp_class, run_time_limit)
+{
+  const raft::handle_t handle_{};
+  auto path = make_path_absolute("linear_programming/savsched1/savsched1.mps");
+  cuopt::mps_parser::mps_data_model_t op_problem =
+    cuopt::mps_parser::parse_mps(path);
+
+  cuopt::linear_programming::pdlp_solver_settings_t settings =
+    cuopt::linear_programming::pdlp_solver_settings_t{};
+
+  // 200 ms
+  constexpr double time_limit_seconds = 0.2;
+  settings.time_limit                 = time_limit_seconds;
+  // To make sure it doesn't return before the time limit
+  settings.set_optimality_tolerance(0);
+  settings.method = cuopt::linear_programming::method_t::PDLP;
+
+  optimization_problem_solution_t solution = solve_lp(&handle_, op_problem, settings);
+
+  EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_TIME_LIMIT);
+  // By default we would return all 0, we now return what we currently have so not all 0
+  EXPECT_FALSE(thrust::all_of(handle_.get_thrust_policy(),
+                              solution.get_primal_solution().begin(),
+                              solution.get_primal_solution().end(),
+                              thrust::placeholders::_1 == 0.0));
+  // Check that indeed it didn't run for more than x time
+  EXPECT_TRUE(solution.get_additional_termination_information().solve_time <
+              (time_limit_seconds * 5) * 1000);
+}
 
 TEST(pdlp_class, run_sub_mittleman)
 {
   std::vector>      // Expected objective value
-    instances{
-      // {"graph40-40", -300.0},
-      //{"ex10", 100.0003411893773},
-      {"datt256_lp", 255.9992298290425},
-      // {"woodlands09", 0.0},
-      // {"savsched1", 217.4054085795689},
-      // {"nug08-3rd", 214.0141488989151},
-      // {"qap15", 1040.999546647414},
-      // {"scpm1", 413.7787723060584},
-      // {"neos3", 27773.54059633068},
-      // {"a2864", -282.9962521965164}
-    };
+    instances{                        // {"graph40-40", -300.0},
+              {"ex10", 100.0003411893773},
+              {"datt256_lp", 255.9992298290425},
+              {"woodlands09", 0.0},
+              {"savsched1", 217.4054085795689},
+              {"nug08-3rd", 214.0141488989151},
+              {"qap15", 1040.999546647414},
+              {"scpm1", 413.7787723060584},
+              {"neos3", 27773.54059633068},
+              {"a2864", -282.9962521965164}};
 
   for (const auto& entry : instances) {
     const auto& name                    = entry.first;
@@ -374,80 +207,8 @@ TEST(pdlp_class, run_sub_mittleman)
 
     std::cout << "Running " << name << std::endl;
     auto path = make_path_absolute("linear_programming/" + name + "/" + name + ".mps");
-
-    // Parse with CuOpt
-    cuopt::mps_parser::mps_data_model_t cuopt_op_problem =
-      cuopt::mps_parser::parse_mps(path);
-
-    // Parse with Papilo
-    auto papilo_problem_opt = papilo::MpsParser::loadProblem(path);
-    if (!papilo_problem_opt) {
-      std::cout << "Failed to parse " << name << " with Papilo parser" << std::endl;
-      continue;
-    }
-    auto papilo_problem = papilo_problem_opt.value();
-
-    // Presolve the Papilo problem using cuOpt's configuration
-    std::cout << "Original Papilo problem - Variables: " << papilo_problem.getNCols()
-              << ", Constraints: " << papilo_problem.getNRows() << std::endl;
-
-    papilo::Presolve presolve;
-
-    // Add cuOpt's specific presolve methods (same as in third_party_presolve.cu)
-    using uptr = std::unique_ptr>;
-
-    // fast presolvers
-    presolve.addPresolveMethod(uptr(new papilo::SingletonCols()));
-    presolve.addPresolveMethod(uptr(new papilo::CoefficientStrengthening()));
-    presolve.addPresolveMethod(uptr(new papilo::ConstraintPropagation()));
-
-    // medium presolvers
-    presolve.addPresolveMethod(uptr(new papilo::FixContinuous()));
-    presolve.addPresolveMethod(uptr(new papilo::SimpleProbing()));
-    presolve.addPresolveMethod(uptr(new papilo::ParallelRowDetection()));
-    presolve.addPresolveMethod(uptr(new papilo::ParallelColDetection()));
-    // Note: SingletonStuffing excluded due to postsolve issues in cuOpt
-    presolve.addPresolveMethod(uptr(new papilo::DualFix()));
-    presolve.addPresolveMethod(uptr(new papilo::SimplifyInequalities()));
-
-    // exhaustive presolvers
-    presolve.addPresolveMethod(uptr(new papilo::ImplIntDetection()));
-    presolve.addPresolveMethod(uptr(new papilo::DominatedCols()));
-    presolve.addPresolveMethod(uptr(new papilo::Probing()));
-
-    // Set cuOpt's presolve options for LP problems
-    constexpr double absolute_tolerance            = 1e-4;  // typical LP tolerance
-    constexpr double time_limit                    = 10.0;  // 10 seconds default
-    presolve.getPresolveOptions().tlim             = time_limit;
-    presolve.getPresolveOptions().epsilon          = absolute_tolerance;
-    presolve.getPresolveOptions().feastol          = absolute_tolerance;
-    presolve.getPresolveOptions().componentsmaxint = -1;  // for LP problems
-    presolve.getPresolveOptions().detectlindep     = 0;   // for LP problems
-
-    papilo::PresolveResult presolve_result = presolve.apply(papilo_problem);
-
-    std::cout << "Presolve status: ";
-    switch (presolve_result.status) {
-      case papilo::PresolveStatus::kUnchanged: std::cout << "Unchanged"; break;
-      case papilo::PresolveStatus::kReduced: std::cout << "Reduced"; break;
-      case papilo::PresolveStatus::kUnbndOrInfeas: std::cout << "Unbounded or Infeasible"; break;
-      case papilo::PresolveStatus::kUnbounded: std::cout << "Unbounded"; break;
-      case papilo::PresolveStatus::kInfeasible: std::cout << "Infeasible"; break;
-    }
-    std::cout << std::endl;
-
-    std::cout << "Presolved Papilo problem - Variables: " << papilo_problem.getNCols()
-              << ", Constraints: " << papilo_problem.getNRows() << std::endl;
-
-    // Check if presolving detected infeasibility/unboundedness
-    if (presolve_result.status == papilo::PresolveStatus::kInfeasible ||
-        presolve_result.status == papilo::PresolveStatus::kUnbounded ||
-        presolve_result.status == papilo::PresolveStatus::kUnbndOrInfeas) {
-      std::cout << "Skipping " << name << " due to presolve status" << std::endl;
-      continue;
-    }
-
-    // Compare the two models
+    cuopt::mps_parser::mps_data_model_t op_problem =
+      cuopt::mps_parser::parse_mps(path
     compare_models(cuopt_op_problem, papilo_problem, name);
 
     // Testing for each solver_mode is ok as it's parsing that is the bottleneck here, not
@@ -460,18 +221,16 @@ TEST(pdlp_class, run_sub_mittleman)
     for (auto solver_mode : solver_mode_list) {
       auto settings             = pdlp_solver_settings_t{};
       settings.pdlp_solver_mode = solver_mode;
-      settings.method           = cuopt::linear_programming::method_t::PDLP;
       settings.presolve         = true;
       const raft::handle_t handle_{};
-      optimization_problem_solution_t solution =
+      optimization_problem_on_t solution =
         solve_lp(&handle_, cuopt_op_problem, settings);
       EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
-      EXPECT_FALSE(
-        is_incorrect_objective(expected_objective_value,
-                               solution.get_additional_termination_information().primal_objective));
+      EXPECT_FALSE(is_incorrect_objective(
+        expected_objective_value, ution.get_additional_termination_information().primal_objective));
       test_objective_sanity(cuopt_op_problem,
                             solution.get_primal_solution(),
-                            solution.get_additional_termination_information().primal_objective);
+                            sn.get_additional_termination_information().primal_objective);
       test_constraint_sanity(cuopt_op_problem, solution);
     }
   }

From 32414f404d264ab999f73a437cfa2498b867ce26 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Thu, 31 Jul 2025 11:47:03 -0700
Subject: [PATCH 054/108] Disable papilo logs

---
 cpp/src/mip/presolve/third_party_presolve.cu |  3 +++
 cpp/tests/linear_programming/pdlp_test.cu    | 21 ++++++++++----------
 2 files changed, 13 insertions(+), 11 deletions(-)

diff --git a/cpp/src/mip/presolve/third_party_presolve.cu b/cpp/src/mip/presolve/third_party_presolve.cu
index 4041f97598..72a2551365 100644
--- a/cpp/src/mip/presolve/third_party_presolve.cu
+++ b/cpp/src/mip/presolve/third_party_presolve.cu
@@ -283,6 +283,9 @@ optimization_problem_t third_party_presolve_t::apply(
   set_presolve_methods(presolver, category);
   set_presolve_options(presolver, category, absolute_tolerance, time_limit);
 
+  // Disable papilo logs
+  presolver.setVerbosityLevel(papilo::VerbosityLevel::kQuiet);
+
   auto result = presolver.apply(papilo_problem);
   if (result.status == papilo::PresolveStatus::kInfeasible) {
     return optimization_problem_t(op_problem.get_handle_ptr());
diff --git a/cpp/tests/linear_programming/pdlp_test.cu b/cpp/tests/linear_programming/pdlp_test.cu
index a3a9cf30b5..ec4edb80a0 100644
--- a/cpp/tests/linear_programming/pdlp_test.cu
+++ b/cpp/tests/linear_programming/pdlp_test.cu
@@ -190,7 +190,7 @@ TEST(pdlp_class, run_sub_mittleman)
 {
   std::vector>      // Expected objective value
-    instances{                        // {"graph40-40", -300.0},
+    instances{{"graph40-40", -300.0},
               {"ex10", 100.0003411893773},
               {"datt256_lp", 255.9992298290425},
               {"woodlands09", 0.0},
@@ -208,8 +208,7 @@ TEST(pdlp_class, run_sub_mittleman)
     std::cout << "Running " << name << std::endl;
     auto path = make_path_absolute("linear_programming/" + name + "/" + name + ".mps");
     cuopt::mps_parser::mps_data_model_t op_problem =
-      cuopt::mps_parser::parse_mps(path
-    compare_models(cuopt_op_problem, papilo_problem, name);
+      cuopt::mps_parser::parse_mps(path);
 
     // Testing for each solver_mode is ok as it's parsing that is the bottleneck here, not
     // solving
@@ -221,17 +220,17 @@ TEST(pdlp_class, run_sub_mittleman)
     for (auto solver_mode : solver_mode_list) {
       auto settings             = pdlp_solver_settings_t{};
       settings.pdlp_solver_mode = solver_mode;
-      settings.presolve         = true;
       const raft::handle_t handle_{};
-      optimization_problem_on_t solution =
-        solve_lp(&handle_, cuopt_op_problem, settings);
+      optimization_problem_solution_t solution =
+        solve_lp(&handle_, op_problem, settings);
       EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
-      EXPECT_FALSE(is_incorrect_objective(
-        expected_objective_value, ution.get_additional_termination_information().primal_objective));
-      test_objective_sanity(cuopt_op_problem,
+      EXPECT_FALSE(
+        is_incorrect_objective(expected_objective_value,
+                               solution.get_additional_termination_information().primal_objective));
+      test_objective_sanity(op_problem,
                             solution.get_primal_solution(),
-                            sn.get_additional_termination_information().primal_objective);
-      test_constraint_sanity(cuopt_op_problem, solution);
+                            solution.get_additional_termination_information().primal_objective);
+      test_constraint_sanity(op_problem, solution);
     }
   }
 }

From e981151f1f7db081fda924bde5d9f8a105d9dace Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Thu, 31 Jul 2025 13:16:33 -0700
Subject: [PATCH 055/108] Update mps bench script

---
 .../linear_programming/run_mps_files.sh       | 31 ++++++++++++++-----
 1 file changed, 24 insertions(+), 7 deletions(-)

diff --git a/benchmarks/linear_programming/run_mps_files.sh b/benchmarks/linear_programming/run_mps_files.sh
index 7e423f8072..72d3a78dd1 100755
--- a/benchmarks/linear_programming/run_mps_files.sh
+++ b/benchmarks/linear_programming/run_mps_files.sh
@@ -31,6 +31,7 @@
 #   --mip-heuristics-only : Run mip heuristics only
 #   --write-log-file : Write log file
 #   --num-cpu-threads : Number of CPU threads to use
+#   --presolve : Enable presolve (default: true)
 #   --batch-num : Batch number.  This allows to split the work across multiple batches uniformly when resources are limited.
 #   --n-batches : Number of batches
 #   --log-to-console : Log to console
@@ -74,6 +75,7 @@ Optional Arguments:
     --mip-heuristics-only  Run mip heuristics only
     --write-log-file   Write log file
     --num-cpu-threads  Number of CPU threads to use
+    --presolve         Enable presolve (default: true)
     --batch-num        Batch number
     --n-batches        Number of batches
     --log-to-console   Log to console
@@ -107,47 +109,62 @@ fi
 while [[ $# -gt 0 ]]; do
     case $1 in
         --path)
+            echo "MPS_DIR: $2"
             MPS_DIR="$2"
             shift 2
             ;;
         --ngpus)
+            echo "GPU_COUNT: $2"
             GPU_COUNT="$2"
             shift 2
             ;;
         --time-limit)
+            echo "TIME_LIMIT: $2"
             TIME_LIMIT="$2"
             shift 2
             ;;
         --output-dir)
+            echo "OUTPUT_DIR: $2"
             OUTPUT_DIR="$2"
             shift 2
             ;;
         --relaxation)
-            echo "Running relaxation"
+            echo "RELAXATION: true"
             RELAXATION=true
             shift
             ;;
         --mip-heuristics-only)
+            echo "MIP_HEURISTICS_ONLY: true"
             MIP_HEURISTICS_ONLY=true
             shift
             ;;
         --write-log-file)
-            WRITE_LOG_FILE="$2"
-            shift 2
+            echo "WRITE_LOG_FILE: true"
+            WRITE_LOG_FILE=true
+            shift
             ;;
         --num-cpu-threads)
+            echo "NUM_CPU_THREADS: $2"
             NUM_CPU_THREADS="$2"
             shift 2
             ;;
+        --presolve)
+            echo "PRESOLVE: $2"
+            PRESOLVE="$2"
+            shift 2
+            ;;
         --batch-num)
+            echo "BATCH_NUM: $2"
             BATCH_NUM="$2"
             shift 2
             ;;
         --n-batches)
+            echo "N_BATCHES: $2"
             N_BATCHES="$2"
             shift 2
             ;;
         --log-to-console)
+            echo "LOG_TO_CONSOLE: $2"
             LOG_TO_CONSOLE="$2"
             shift 2
             ;;
@@ -173,6 +190,7 @@ RELAXATION=${RELAXATION:-false}
 MIP_HEURISTICS_ONLY=${MIP_HEURISTICS_ONLY:-false}
 WRITE_LOG_FILE=${WRITE_LOG_FILE:-false}
 NUM_CPU_THREADS=${NUM_CPU_THREADS:-1}
+PRESOLVE=${PRESOLVE:-true}
 BATCH_NUM=${BATCH_NUM:-0}
 N_BATCHES=${N_BATCHES:-1}
 LOG_TO_CONSOLE=${LOG_TO_CONSOLE:-true}
@@ -261,9 +279,8 @@ worker() {
         if [ "$RELAXATION" = true ]; then
             args="$args --relaxation"
         fi
-        if [ "$LOG_TO_CONSOLE" = true ]; then
-            args="$args --log-to-console $LOG_TO_CONSOLE"
-        fi
+        args="$args --log-to-console $LOG_TO_CONSOLE"
+        args="$args --presolve $PRESOLVE"
 
         CUDA_VISIBLE_DEVICES=$gpu_id cuopt_cli "$mps_file" --time-limit $TIME_LIMIT $args
     done
@@ -277,4 +294,4 @@ done
 wait
 
 # Remove the index file
-rm -f "$INDEX_FILE"
\ No newline at end of file
+rm -f "$INDEX_FILE"

From 0f0aae858d662a15124908cf54b75982fe95a759 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Thu, 31 Jul 2025 14:28:49 -0700
Subject: [PATCH 056/108] Add tbb to python cmake

---
 python/cuopt/CMakeLists.txt | 1 +
 1 file changed, 1 insertion(+)

diff --git a/python/cuopt/CMakeLists.txt b/python/cuopt/CMakeLists.txt
index 8ea79d784c..84e38129d5 100644
--- a/python/cuopt/CMakeLists.txt
+++ b/python/cuopt/CMakeLists.txt
@@ -33,6 +33,7 @@ project(
 
 find_package(cuopt ${cuopt_version})
 find_package(mps_parser ${cuopt_version})
+find_package(TBB REQUIRED)
 
 include(rapids-cython-core)
 rapids_cython_init()

From e3023a9d93317c60f42c3372dc3b798dba92d14f Mon Sep 17 00:00:00 2001
From: Ramakrishna Prabhu 
Date: Thu, 31 Jul 2025 17:54:14 -0500
Subject: [PATCH 057/108] add permissive

---
 cpp/CMakeLists.txt | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt
index db802ca8a5..0326d1f033 100644
--- a/cpp/CMakeLists.txt
+++ b/cpp/CMakeLists.txt
@@ -73,7 +73,7 @@ set(CUOPT_CXX_FLAGS "")
 set(CUOPT_CUDA_FLAGS "")
 
 if(CMAKE_COMPILER_IS_GNUCXX)
-  list(APPEND CUOPT_CXX_FLAGS -Werror -Wno-error=deprecated-declarations)
+  list(APPEND CUOPT_CXX_FLAGS -Werror -Wno-error=deprecated-declarations -fpermissive)
 endif(CMAKE_COMPILER_IS_GNUCXX)
 
 if(DEFINE_ASSERT)
@@ -108,7 +108,7 @@ if(${CMAKE_CUDA_COMPILER_VERSION} VERSION_GREATER_EQUAL 12.8 AND CMAKE_CUDA_COMP
   set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -static-global-template-stub=false")
 endif()
 list(APPEND CUOPT_CUDA_FLAGS -Werror=cross-execution-space-call -Wno-deprecated-declarations -Xcompiler=-Werror)
-list(APPEND CUOPT_CUDA_FLAGS -Xcompiler=-Wall -Wno-error=non-template-friend)
+list(APPEND CUOPT_CUDA_FLAGS -Xcompiler=-Wall -Wno-error=non-template-friend -Xcompiler=-fpermissive)
 list(APPEND CUOPT_CUDA_FLAGS -Xfatbin=-compress-all)
 if(CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL 12.9 AND CMAKE_CUDA_COMPILER_VERSION VERSION_LESS 13.0)
   list(APPEND CUOPT_CUDA_FLAGS -Xfatbin=--compress-level=3)

From 18ab83aab21f94a96a1f203ef740c98b66760621 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Thu, 31 Jul 2025 15:32:14 -0700
Subject: [PATCH 058/108] Fix empty solution fed to postsolve

---
 .../optimization_problem.cu                   |  3 ++-
 cpp/src/linear_programming/solve.cu           | 13 ++++-----
 cpp/src/mip/presolve/third_party_presolve.cu  | 27 ++++++++++---------
 cpp/src/mip/presolve/third_party_presolve.cuh |  9 ++++---
 cpp/src/mip/solve.cu                          | 19 ++++++++-----
 5 files changed, 41 insertions(+), 30 deletions(-)

diff --git a/cpp/src/linear_programming/optimization_problem.cu b/cpp/src/linear_programming/optimization_problem.cu
index cdd6cf2931..a6b9248629 100644
--- a/cpp/src/linear_programming/optimization_problem.cu
+++ b/cpp/src/linear_programming/optimization_problem.cu
@@ -42,7 +42,7 @@ optimization_problem_t::optimization_problem_t(raft::handle_t const* h
     n_constraints_{0},
     A_{0, stream_view_},
     A_indices_{0, stream_view_},
-    A_offsets_{0, stream_view_},
+    A_offsets_{1, stream_view_},
     b_{0, stream_view_},
     c_{0, stream_view_},
     variable_lower_bounds_{0, stream_view_},
@@ -55,6 +55,7 @@ optimization_problem_t::optimization_problem_t(raft::handle_t const* h
     row_names_{}
 {
   raft::common::nvtx::range fun_scope("optimization problem construction");
+  A_offsets_.set_element_to_zero_async(0, stream_view_);
 }
 
 template 
diff --git a/cpp/src/linear_programming/solve.cu b/cpp/src/linear_programming/solve.cu
index fb0e992010..2904dd8d96 100644
--- a/cpp/src/linear_programming/solve.cu
+++ b/cpp/src/linear_programming/solve.cu
@@ -603,12 +603,13 @@ optimization_problem_solution_t solve_lp(optimization_problem_t>();
-      auto reduced_problem = presolver->apply(op_problem,
-                                              cuopt::linear_programming::problem_category_t::LP,
-                                              settings.tolerances.absolute_primal_tolerance,
-                                              presolve_time_limit);
-      if (reduced_problem.empty()) {
+      presolver = std::make_unique>();
+      auto [reduced_problem, postsolve_status] =
+        presolver->apply(op_problem,
+                         cuopt::linear_programming::problem_category_t::LP,
+                         settings.tolerances.absolute_primal_tolerance,
+                         presolve_time_limit);
+      if (postsolve_status == papilo::PresolveStatus::kInfeasible) {
         return optimization_problem_solution_t(
           pdlp_termination_status_t::PrimalInfeasible, op_problem.get_handle_ptr()->get_stream());
       }
diff --git a/cpp/src/mip/presolve/third_party_presolve.cu b/cpp/src/mip/presolve/third_party_presolve.cu
index 72a2551365..abf42ba253 100644
--- a/cpp/src/mip/presolve/third_party_presolve.cu
+++ b/cpp/src/mip/presolve/third_party_presolve.cu
@@ -130,12 +130,14 @@ optimization_problem_t build_optimization_problem(
   papilo::Problem const& papilo_problem, raft::handle_t const* handle_ptr)
 {
   optimization_problem_t op_problem(handle_ptr);
-  if (papilo_problem.getNRows() == 0 && papilo_problem.getNCols() == 0) { return op_problem; }
 
   auto obj = papilo_problem.getObjective();
-  op_problem.set_objective_coefficients(obj.coefficients.data(), obj.coefficients.size());
   op_problem.set_objective_offset(obj.offset);
 
+  if (papilo_problem.getNRows() == 0 && papilo_problem.getNCols() == 0) { return op_problem; }
+
+  op_problem.set_objective_coefficients(obj.coefficients.data(), obj.coefficients.size());
+
   auto& constraint_matrix = papilo_problem.getConstraintMatrix();
   auto row_lower          = constraint_matrix.getLeftHandSides();
   auto row_upper          = constraint_matrix.getRightHandSides();
@@ -266,11 +268,11 @@ void set_presolve_options(papilo::Presolve& presolver,
 }
 
 template 
-optimization_problem_t third_party_presolve_t::apply(
-  optimization_problem_t const& op_problem,
-  problem_category_t category,
-  f_t absolute_tolerance,
-  double time_limit)
+std::pair, papilo::PresolveStatus>
+third_party_presolve_t::apply(optimization_problem_t const& op_problem,
+                                        problem_category_t category,
+                                        f_t absolute_tolerance,
+                                        double time_limit)
 {
   papilo::Problem papilo_problem = build_papilo_problem(op_problem);
 
@@ -287,19 +289,20 @@ optimization_problem_t third_party_presolve_t::apply(
   presolver.setVerbosityLevel(papilo::VerbosityLevel::kQuiet);
 
   auto result = presolver.apply(papilo_problem);
+  check_presolve_status(result.status);
   if (result.status == papilo::PresolveStatus::kInfeasible) {
-    return optimization_problem_t(op_problem.get_handle_ptr());
+    return std::make_pair(optimization_problem_t(op_problem.get_handle_ptr()),
+                          papilo::PresolveStatus::kInfeasible);
   }
-
   post_solve_storage_ = result.postsolve;
-
-  check_presolve_status(result.status);
   CUOPT_LOG_INFO("Presolved problem:: Num variables: %d Num constraints: %d, NNZ: %d",
                  papilo_problem.getNCols(),
                  papilo_problem.getNRows(),
                  papilo_problem.getConstraintMatrix().getNnz());
 
-  return build_optimization_problem(papilo_problem, op_problem.get_handle_ptr());
+  return std::make_pair(
+    build_optimization_problem(papilo_problem, op_problem.get_handle_ptr()),
+    result.status);
 }
 
 template 
diff --git a/cpp/src/mip/presolve/third_party_presolve.cuh b/cpp/src/mip/presolve/third_party_presolve.cuh
index 2207075fe4..0b40fb5973 100644
--- a/cpp/src/mip/presolve/third_party_presolve.cuh
+++ b/cpp/src/mip/presolve/third_party_presolve.cuh
@@ -30,10 +30,11 @@ class third_party_presolve_t {
  public:
   third_party_presolve_t() = default;
 
-  optimization_problem_t apply(optimization_problem_t const& op_problem,
-                                         problem_category_t category,
-                                         f_t absolute_tolerance,
-                                         double time_limit);
+  std::pair, papilo::PresolveStatus> apply(
+    optimization_problem_t const& op_problem,
+    problem_category_t category,
+    f_t absolute_tolerance,
+    double time_limit);
 
   void undo(rmm::device_uvector& primal_solution,
             rmm::device_uvector& dual_solution,
diff --git a/cpp/src/mip/solve.cu b/cpp/src/mip/solve.cu
index 796c681682..0154c8e94d 100644
--- a/cpp/src/mip/solve.cu
+++ b/cpp/src/mip/solve.cu
@@ -192,16 +192,18 @@ mip_solution_t solve_mip(optimization_problem_t& op_problem,
       // allocate not more than 10% of the time limit to presolve.
       // Note that this is not the presolve time, but the time limit for presolve.
       const double presolve_time_limit = 0.1 * time_limit;
-      presolver               = std::make_unique>();
-      auto reduced_op_problem = presolver->apply(op_problem,
-                                                 cuopt::linear_programming::problem_category_t::MIP,
-                                                 settings.tolerances.absolute_tolerance,
-                                                 presolve_time_limit);
-      if (reduced_op_problem.empty()) {
+      presolver = std::make_unique>();
+      auto [reduced_op_problem, postsolve_status] =
+        presolver->apply(op_problem,
+                         cuopt::linear_programming::problem_category_t::MIP,
+                         settings.tolerances.absolute_tolerance,
+                         presolve_time_limit);
+      if (postsolve_status == papilo::PresolveStatus::kInfeasible) {
         return mip_solution_t(mip_termination_status_t::Infeasible,
                                         solver_stats_t{},
                                         op_problem.get_handle_ptr()->get_stream());
       }
+
       problem       = detail::problem_t(reduced_op_problem);
       presolve_time = timer.elapsed_time();
       CUOPT_LOG_INFO("Third party presolve time: %f", presolve_time);
@@ -216,7 +218,9 @@ mip_solution_t solve_mip(optimization_problem_t& op_problem,
 
     auto sol = run_mip(problem, settings, timer);
 
-    if (run_presolve) {
+    auto status_to_skip = sol.get_termination_status() == mip_termination_status_t::TimeLimit ||
+                          sol.get_termination_status() == mip_termination_status_t::Infeasible;
+    if (run_presolve && !status_to_skip) {
       auto primal_solution =
         cuopt::device_copy(sol.get_solution(), op_problem.get_handle_ptr()->get_stream());
       rmm::device_uvector dual_solution(0, op_problem.get_handle_ptr()->get_stream());
@@ -226,6 +230,7 @@ mip_solution_t solve_mip(optimization_problem_t& op_problem,
                       reduced_costs,
                       cuopt::linear_programming::problem_category_t::MIP,
                       op_problem.get_handle_ptr()->get_stream());
+
       detail::problem_t full_problem(op_problem);
       detail::solution_t full_sol(full_problem);
       full_sol.copy_new_assignment(cuopt::host_copy(primal_solution));

From a2b124dd7af152e85a4796dd4c7a22087398b299 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Thu, 31 Jul 2025 20:14:39 -0700
Subject: [PATCH 059/108] Disable presolve by default for LP in server

---
 .../utils/linear_programming/solver.py            | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

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 10ec011acc..ee68958586 100644
--- a/python/cuopt_server/cuopt_server/utils/linear_programming/solver.py
+++ b/python/cuopt_server/cuopt_server/utils/linear_programming/solver.py
@@ -379,6 +379,21 @@ def create_solver(LP_data, warmstart_data):
                 CUOPT_CROSSOVER, solver_config.crossover
             )
         if solver_config.presolve is not None:
+
+            def is_mip(var_types):
+                if len(var_types) == 0:
+                    return False
+                elif "I" in var_types:
+                    return True
+
+                return False
+
+            # Disable presolve for LP problems by default
+            if solver_config.presolve is None and not is_mip(
+                LP_data.variable_types
+            ):
+                solver_config.presolve = False
+
             solver_settings.set_parameter(
                 CUOPT_PRESOLVE, solver_config.presolve
             )

From 7a3e7b24cd0e113f590d2359b4f2222363b1e8f1 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Thu, 31 Jul 2025 23:00:12 -0700
Subject: [PATCH 060/108] Disable presolve by default for LP in server

---
 cpp/src/linear_programming/solve.cu           |  1 -
 .../linear_programming/data_definition.py     |  5 ++--
 .../utils/linear_programming/solver.py        | 23 ++++++++++---------
 3 files changed, 15 insertions(+), 14 deletions(-)

diff --git a/cpp/src/linear_programming/solve.cu b/cpp/src/linear_programming/solve.cu
index 2904dd8d96..88395d48ec 100644
--- a/cpp/src/linear_programming/solve.cu
+++ b/cpp/src/linear_programming/solve.cu
@@ -355,7 +355,6 @@ static optimization_problem_solution_t run_pdlp_solver(
   const std::chrono::high_resolution_clock::time_point& start_time,
   bool is_batch_mode)
 {
-  std::cout << "problem.n_constraints: " << problem.n_constraints << std::endl;
   if (problem.n_constraints == 0) {
     CUOPT_LOG_INFO("No constraints in the problem: PDLP can't be run, use Dual Simplex instead.");
     return optimization_problem_solution_t{pdlp_termination_status_t::NumericalError,
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 6eb6df1fee..78b3c2efe6 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
@@ -536,9 +536,10 @@ class SolverConfig(StrictModel):
         description="Set True to use crossover, False to not use crossover.",
     )
     presolve: Optional[bool] = Field(
-        default=True,
+        default=None,
         description="Set True to enable presolve, False to disable presolve. "
-        "Presolve can reduce problem size and improve solve time.",
+        "Presolve can reduce problem size and improve solve time. "
+        "Default is True for MIP problems and False for LP problems.",
     )
     log_to_console: Optional[bool] = Field(
         default=True,
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 ee68958586..c11429f4a1 100644
--- a/python/cuopt_server/cuopt_server/utils/linear_programming/solver.py
+++ b/python/cuopt_server/cuopt_server/utils/linear_programming/solver.py
@@ -378,25 +378,26 @@ def create_solver(LP_data, warmstart_data):
             solver_settings.set_parameter(
                 CUOPT_CROSSOVER, solver_config.crossover
             )
-        if solver_config.presolve is not None:
-
-            def is_mip(var_types):
-                if len(var_types) == 0:
-                    return False
-                elif "I" in var_types:
-                    return True
 
+        def is_mip(var_types):
+            if len(var_types) == 0:
                 return False
+            elif "I" in var_types:
+                return True
+
+            return False
 
-            # Disable presolve for LP problems by default
-            if solver_config.presolve is None and not is_mip(
-                LP_data.variable_types
-            ):
+        if solver_config.presolve is None:
+            if is_mip(LP_data.variable_types):
+                solver_config.presolve = True
+            else:
                 solver_config.presolve = False
 
+        if solver_config.presolve is not None:
             solver_settings.set_parameter(
                 CUOPT_PRESOLVE, solver_config.presolve
             )
+
         if solver_config.log_to_console is not None:
             solver_settings.set_parameter(
                 CUOPT_LOG_TO_CONSOLE, solver_config.log_to_console

From f8811da98eaf0ec5b3cbf12c59ec881951283e0a Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Thu, 31 Jul 2025 23:39:04 -0700
Subject: [PATCH 061/108] Set empty csr matrix to copy a problem reduced to 0

---
 .../linear_programming/optimization_problem.cu    |  3 +--
 cpp/src/mip/presolve/third_party_presolve.cu      | 15 ++++++++++++++-
 2 files changed, 15 insertions(+), 3 deletions(-)

diff --git a/cpp/src/linear_programming/optimization_problem.cu b/cpp/src/linear_programming/optimization_problem.cu
index a6b9248629..cdd6cf2931 100644
--- a/cpp/src/linear_programming/optimization_problem.cu
+++ b/cpp/src/linear_programming/optimization_problem.cu
@@ -42,7 +42,7 @@ optimization_problem_t::optimization_problem_t(raft::handle_t const* h
     n_constraints_{0},
     A_{0, stream_view_},
     A_indices_{0, stream_view_},
-    A_offsets_{1, stream_view_},
+    A_offsets_{0, stream_view_},
     b_{0, stream_view_},
     c_{0, stream_view_},
     variable_lower_bounds_{0, stream_view_},
@@ -55,7 +55,6 @@ optimization_problem_t::optimization_problem_t(raft::handle_t const* h
     row_names_{}
 {
   raft::common::nvtx::range fun_scope("optimization problem construction");
-  A_offsets_.set_element_to_zero_async(0, stream_view_);
 }
 
 template 
diff --git a/cpp/src/mip/presolve/third_party_presolve.cu b/cpp/src/mip/presolve/third_party_presolve.cu
index abf42ba253..69ba8710d7 100644
--- a/cpp/src/mip/presolve/third_party_presolve.cu
+++ b/cpp/src/mip/presolve/third_party_presolve.cu
@@ -134,7 +134,20 @@ optimization_problem_t build_optimization_problem(
   auto obj = papilo_problem.getObjective();
   op_problem.set_objective_offset(obj.offset);
 
-  if (papilo_problem.getNRows() == 0 && papilo_problem.getNCols() == 0) { return op_problem; }
+  if (papilo_problem.getNRows() == 0 && papilo_problem.getNCols() == 0) {
+    // FIXME: Shouldn't need to set offsets
+    std::vector h_offsets{0};
+    std::vector h_indices{};
+    std::vector h_values{};
+    op_problem.set_csr_constraint_matrix(h_values.data(),
+                                         h_values.size(),
+                                         h_indices.data(),
+                                         h_indices.size(),
+                                         h_offsets.data(),
+                                         h_offsets.size());
+
+    return op_problem;
+  }
 
   op_problem.set_objective_coefficients(obj.coefficients.data(), obj.coefficients.size());
 

From 31cdbfe8684deb8ffc882935ad87ae3b5864109f Mon Sep 17 00:00:00 2001
From: Ramakrishna Prabhu 
Date: Fri, 1 Aug 2025 09:16:56 -0500
Subject: [PATCH 062/108] remove find tbb

---
 cpp/cmake/thirdparty/FindTBB.cmake | 90 ------------------------------
 1 file changed, 90 deletions(-)
 delete mode 100644 cpp/cmake/thirdparty/FindTBB.cmake

diff --git a/cpp/cmake/thirdparty/FindTBB.cmake b/cpp/cmake/thirdparty/FindTBB.cmake
deleted file mode 100644
index 61917fcc86..0000000000
--- a/cpp/cmake/thirdparty/FindTBB.cmake
+++ /dev/null
@@ -1,90 +0,0 @@
-# SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
-# SPDX-License-Identifier: Apache-2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# FindTBB.cmake - Find TBB (Threading Building Blocks) library
-#
-# This module defines the following variables:
-#   TBB_FOUND        - True if TBB is found
-#   TBB_INCLUDE_DIRS - TBB include directories
-#   TBB_LIBRARIES    - TBB libraries
-#   TBB::tbb         - Imported target for TBB
-
-# Try pkg-config first
-find_package(PkgConfig QUIET)
-if(PkgConfig_FOUND)
-  pkg_check_modules(PC_TBB QUIET tbb)
-endif()
-
-find_path(TBB_INCLUDE_DIR
-  NAMES tbb/tbb.h
-  PATHS
-    ${PC_TBB_INCLUDE_DIRS}
-    /usr/include
-    /usr/local/include
-    /opt/intel/tbb/include
-    /opt/intel/oneapi/tbb/latest/include
-)
-
-find_library(TBB_LIBRARY
-  NAMES tbb
-  PATHS
-    ${PC_TBB_LIBRARY_DIRS}
-    /usr/lib
-    /usr/lib64
-    /usr/local/lib
-    /usr/local/lib64
-    /opt/intel/tbb/lib
-    /opt/intel/oneapi/tbb/latest/lib
-)
-
-find_library(TBB_MALLOC_LIBRARY
-  NAMES tbbmalloc
-  PATHS
-    ${PC_TBB_LIBRARY_DIRS}
-    /usr/lib
-    /usr/lib64
-    /usr/local/lib
-    /usr/local/lib64
-    /opt/intel/tbb/lib
-    /opt/intel/oneapi/tbb/latest/lib
-)
-
-include(FindPackageHandleStandardArgs)
-find_package_handle_standard_args(TBB
-  REQUIRED_VARS TBB_INCLUDE_DIR TBB_LIBRARY
-)
-
-if(TBB_FOUND AND NOT TARGET TBB::tbb)
-  add_library(TBB::tbb UNKNOWN IMPORTED)
-  set_target_properties(TBB::tbb PROPERTIES
-    IMPORTED_LOCATION "${TBB_LIBRARY}"
-    INTERFACE_INCLUDE_DIRECTORIES "${TBB_INCLUDE_DIR}"
-  )
-  
-  if(TBB_MALLOC_LIBRARY)
-    set_target_properties(TBB::tbb PROPERTIES
-      INTERFACE_LINK_LIBRARIES "${TBB_MALLOC_LIBRARY}"
-    )
-  endif()
-  
-  # Add compile definitions from pkg-config if available
-  if(PC_TBB_CFLAGS_OTHER)
-    set_target_properties(TBB::tbb PROPERTIES
-      INTERFACE_COMPILE_OPTIONS "${PC_TBB_CFLAGS_OTHER}"
-    )
-  endif()
-endif()
-
-mark_as_advanced(TBB_INCLUDE_DIR TBB_LIBRARY TBB_MALLOC_LIBRARY) 
\ No newline at end of file

From 8ad328005af0c3c47eb394350c2bb47bd1016f5d Mon Sep 17 00:00:00 2001
From: Ramakrishna Prabhu 
Date: Fri, 1 Aug 2025 09:23:52 -0500
Subject: [PATCH 063/108] Try to build tbb and boost

---
 cpp/CMakeLists.txt             | 10 +++-------
 python/libcuopt/CMakeLists.txt | 17 ++++++++++++++---
 2 files changed, 17 insertions(+), 10 deletions(-)

diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt
index 0326d1f033..a66f58880c 100644
--- a/cpp/CMakeLists.txt
+++ b/cpp/CMakeLists.txt
@@ -73,7 +73,7 @@ set(CUOPT_CXX_FLAGS "")
 set(CUOPT_CUDA_FLAGS "")
 
 if(CMAKE_COMPILER_IS_GNUCXX)
-  list(APPEND CUOPT_CXX_FLAGS -Werror -Wno-error=deprecated-declarations -fpermissive)
+  list(APPEND CUOPT_CXX_FLAGS -Werror -Wno-error=deprecated-declarations)
 endif(CMAKE_COMPILER_IS_GNUCXX)
 
 if(DEFINE_ASSERT)
@@ -108,7 +108,7 @@ if(${CMAKE_CUDA_COMPILER_VERSION} VERSION_GREATER_EQUAL 12.8 AND CMAKE_CUDA_COMP
   set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -static-global-template-stub=false")
 endif()
 list(APPEND CUOPT_CUDA_FLAGS -Werror=cross-execution-space-call -Wno-deprecated-declarations -Xcompiler=-Werror)
-list(APPEND CUOPT_CUDA_FLAGS -Xcompiler=-Wall -Wno-error=non-template-friend -Xcompiler=-fpermissive)
+list(APPEND CUOPT_CUDA_FLAGS -Xcompiler=-Wall -Wno-error=non-template-friend)
 list(APPEND CUOPT_CUDA_FLAGS -Xfatbin=-compress-all)
 if(CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL 12.9 AND CMAKE_CUDA_COMPILER_VERSION VERSION_LESS 13.0)
   list(APPEND CUOPT_CUDA_FLAGS -Xfatbin=--compress-level=3)
@@ -161,11 +161,7 @@ else()
   find_package(RAFT REQUIRED)
 endif()
 
-find_package(TBB QUIET)
-
-if(NOT TBB_FOUND)
-  message(FATAL_ERROR "TBB is required but was not found. Please install the TBB development package (e.g., libtbb-dev or tbb-devel) and try again.")
-endif()
+# find_package(TBB REQUIRED)
 
 FetchContent_Declare(
   papilo
diff --git a/python/libcuopt/CMakeLists.txt b/python/libcuopt/CMakeLists.txt
index 06b9a8cefb..7194e05784 100644
--- a/python/libcuopt/CMakeLists.txt
+++ b/python/libcuopt/CMakeLists.txt
@@ -45,6 +45,20 @@ FetchContent_Declare(
   GIT_TAG v3.2
 )
 FetchContent_MakeAvailable(argparse)
+FetchContent_Declare(
+  tbb
+  GIT_REPOSITORY https://github.com/oneapi-src/oneTBB.git
+  GIT_TAG v2022.2.0
+)
+FetchContent_MakeAvailable(tbb)
+
+FetchContent_Declare(
+  boost
+  GIT_REPOSITORY https://github.com/boostorg/boost.git
+  GIT_TAG boost-1.85.0
+)
+FetchContent_MakeAvailable(boost)
+
 
 set(BUILD_TESTS OFF)
 set(BUILD_BENCHMARKS OFF)
@@ -54,9 +68,6 @@ set(CUDA_STATIC_RUNTIME ON)
 add_subdirectory(../../cpp cuopt-cpp)
 
 target_link_libraries(cuopt PRIVATE argparse)
-if(Boost_FOUND)
-  target_link_libraries(cuopt PRIVATE Boost::system Boost::filesystem Boost::thread)
-endif()
 target_link_libraries(cuopt_cli PRIVATE argparse)
 
 set(rpaths

From e60be55b4cd73f008d351b20802c4fd1b806b882 Mon Sep 17 00:00:00 2001
From: Ramakrishna Prabhu 
Date: Fri, 1 Aug 2025 09:35:09 -0500
Subject: [PATCH 064/108] Add find tbb back

---
 cpp/CMakeLists.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt
index a66f58880c..be730cd72b 100644
--- a/cpp/CMakeLists.txt
+++ b/cpp/CMakeLists.txt
@@ -161,7 +161,7 @@ else()
   find_package(RAFT REQUIRED)
 endif()
 
-# find_package(TBB REQUIRED)
+find_package(TBB REQUIRED)
 
 FetchContent_Declare(
   papilo

From ff091661017f311d3a7f065e5c32ee2046d28fa3 Mon Sep 17 00:00:00 2001
From: Ramakrishna Prabhu 
Date: Fri, 1 Aug 2025 09:55:43 -0500
Subject: [PATCH 065/108] comment install boost and tbb

---
 ci/build_wheel_libcuopt.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/ci/build_wheel_libcuopt.sh b/ci/build_wheel_libcuopt.sh
index 2b3a8b0fb5..d4234156a0 100755
--- a/ci/build_wheel_libcuopt.sh
+++ b/ci/build_wheel_libcuopt.sh
@@ -22,7 +22,7 @@ package_name="libcuopt"
 package_dir="python/libcuopt"
 
 # Install Boost and TBB
-bash ci/utils/install_boost_tbb.sh
+#bash ci/utils/install_boost_tbb.sh
 
 export SKBUILD_CMAKE_ARGS="-DCUOPT_BUILD_WHEELS=ON;-DDISABLE_DEPRECATION_WARNING=ON"
 

From a60e51e575b639e04e76a7ea35cd49086addf7ec Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Fri, 1 Aug 2025 08:32:08 -0700
Subject: [PATCH 066/108] Fix var types check

---
 .../cuopt_server/utils/linear_programming/solver.py             | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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 c11429f4a1..fe2d840d98 100644
--- a/python/cuopt_server/cuopt_server/utils/linear_programming/solver.py
+++ b/python/cuopt_server/cuopt_server/utils/linear_programming/solver.py
@@ -380,7 +380,7 @@ def create_solver(LP_data, warmstart_data):
             )
 
         def is_mip(var_types):
-            if len(var_types) == 0:
+            if not var_types:
                 return False
             elif "I" in var_types:
                 return True

From 31e13492f6f1579b382afc0579d12dd103590e3f Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Fri, 1 Aug 2025 10:45:34 -0700
Subject: [PATCH 067/108] Remove output flag option

---
 cpp/src/mip/solution/solution.cu  | 35 ++++++++++++++-----------------
 cpp/src/mip/solution/solution.cuh |  4 +---
 cpp/src/mip/solve.cu              |  8 +++----
 3 files changed, 21 insertions(+), 26 deletions(-)

diff --git a/cpp/src/mip/solution/solution.cu b/cpp/src/mip/solution/solution.cu
index 05b2985a12..b3a7f6dbbf 100644
--- a/cpp/src/mip/solution/solution.cu
+++ b/cpp/src/mip/solution/solution.cu
@@ -562,8 +562,7 @@ f_t solution_t::compute_max_variable_violation()
 // returns the solution after applying the conversions
 template 
 mip_solution_t solution_t::get_solution(bool output_feasible,
-                                                            solver_stats_t stats,
-                                                            bool log_stats)
+                                                            solver_stats_t stats)
 {
   cuopt::default_logger().flush();
   cuopt_expects(
@@ -582,23 +581,21 @@ mip_solution_t solution_t::get_solution(bool output_feasible
     i_t num_nodes                    = stats.num_nodes;
     i_t num_simplex_iterations       = stats.num_simplex_iterations;
     handle_ptr->sync_stream();
-    if (log_stats) {
-      CUOPT_LOG_INFO(
-        "Solution objective: %f , relative_mip_gap %f solution_bound %f presolve_time %f "
-        "total_solve_time %f "
-        "max constraint violation %f max int violation %f max var bounds violation %f "
-        "nodes %d simplex_iterations %d",
-        h_user_obj,
-        rel_mip_gap,
-        solution_bound,
-        presolve_time,
-        total_solve_time,
-        max_constraint_violation,
-        max_int_violation,
-        max_variable_bound_violation,
-        num_nodes,
-        num_simplex_iterations);
-    }
+    CUOPT_LOG_INFO(
+      "Solution objective: %f , relative_mip_gap %f solution_bound %f presolve_time %f "
+      "total_solve_time %f "
+      "max constraint violation %f max int violation %f max var bounds violation %f "
+      "nodes %d simplex_iterations %d",
+      h_user_obj,
+      rel_mip_gap,
+      solution_bound,
+      presolve_time,
+      total_solve_time,
+      max_constraint_violation,
+      max_int_violation,
+      max_variable_bound_violation,
+      num_nodes,
+      num_simplex_iterations);
 
     const bool not_optimal = rel_mip_gap > problem_ptr->tolerances.relative_mip_gap &&
                              abs_mip_gap > problem_ptr->tolerances.absolute_mip_gap;
diff --git a/cpp/src/mip/solution/solution.cuh b/cpp/src/mip/solution/solution.cuh
index 0774bdef2b..729a5c0e55 100644
--- a/cpp/src/mip/solution/solution.cuh
+++ b/cpp/src/mip/solution/solution.cuh
@@ -100,9 +100,7 @@ class solution_t {
   f_t get_total_excess();
   // brings all vars within bounds
   void clamp_within_bounds();
-  mip_solution_t get_solution(bool output_feasible,
-                                        solver_stats_t stats,
-                                        bool log_stats = true);
+  mip_solution_t get_solution(bool output_feasible, solver_stats_t stats);
   f_t compute_max_constraint_violation();
   f_t compute_max_int_violation();
   f_t compute_max_variable_violation();
diff --git a/cpp/src/mip/solve.cu b/cpp/src/mip/solve.cu
index 796c681682..22a914c4e7 100644
--- a/cpp/src/mip/solve.cu
+++ b/cpp/src/mip/solve.cu
@@ -89,7 +89,7 @@ mip_solution_t run_mip(detail::problem_t& problem,
     solution.compute_objective();  // just to ensure h_user_obj is set
     auto stats           = solver_stats_t{};
     stats.solution_bound = solution.get_user_objective();
-    return solution.get_solution(true, stats, false);
+    return solution.get_solution(true, stats);
   }
   // problem contains unpreprocessed data
   detail::problem_t scaled_problem(problem);
@@ -144,8 +144,8 @@ mip_solution_t run_mip(detail::problem_t& problem,
       "please provide a more numerically stable problem.");
   }
 
-  auto sol = scaled_sol.get_solution(
-    is_feasible_before_scaling || is_feasible_after_unscaling, solver.get_solver_stats(), false);
+  auto sol = scaled_sol.get_solution(is_feasible_before_scaling || is_feasible_after_unscaling,
+                                     solver.get_solver_stats());
   detail::print_solution(scaled_problem.handle_ptr, sol.get_solution());
   return sol;
 }
@@ -241,7 +241,7 @@ mip_solution_t solve_mip(optimization_problem_t& op_problem,
       // FIXME:: reduced_solution.get_stats() is not correct, we need to compute the stats for the
       // full problem
       full_sol.post_process_completed = true;  // hack
-      sol                             = full_sol.get_solution(true, full_stats, true);
+      sol                             = full_sol.get_solution(true, full_stats);
     }
 
     if (settings.sol_file != "") {

From 82e3ac771887d337683130e0dac3d5c71acf2683 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Fri, 1 Aug 2025 11:24:43 -0700
Subject: [PATCH 068/108] Fix var types check

---
 .../cuopt_server/utils/linear_programming/solver.py             | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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 fe2d840d98..05ef2e2ad5 100644
--- a/python/cuopt_server/cuopt_server/utils/linear_programming/solver.py
+++ b/python/cuopt_server/cuopt_server/utils/linear_programming/solver.py
@@ -380,7 +380,7 @@ def create_solver(LP_data, warmstart_data):
             )
 
         def is_mip(var_types):
-            if not var_types:
+            if var_types is None or len(var_types) == 0:
                 return False
             elif "I" in var_types:
                 return True

From 3ab49f527f9ae57d3541bee7a162239fb5c39dc4 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Fri, 1 Aug 2025 12:52:44 -0700
Subject: [PATCH 069/108] Try adding hwloc

---
 dependencies.yaml              | 3 +++
 python/libcuopt/pyproject.toml | 2 ++
 2 files changed, 5 insertions(+)

diff --git a/dependencies.yaml b/dependencies.yaml
index 5800b6659c..3ec6da7df3 100644
--- a/dependencies.yaml
+++ b/dependencies.yaml
@@ -281,6 +281,9 @@ dependencies:
         packages:
           - tbb-devel
           - boost
+      - output_types: [requirements, pyproject]
+        packages:
+          - python3-hwloc
   build_common:
     common:
       - output_types: [conda, requirements, pyproject]
diff --git a/python/libcuopt/pyproject.toml b/python/libcuopt/pyproject.toml
index c9a900caa5..e70b48424b 100644
--- a/python/libcuopt/pyproject.toml
+++ b/python/libcuopt/pyproject.toml
@@ -50,6 +50,7 @@ dependencies = [
     "nvidia-cusolver",
     "nvidia-cusparse",
     "nvidia-nvtx",
+    "python3-hwloc",
     "rapids-logger==0.1.*,>=0.0.0a0",
 ] # This list was generated by `rapids-dependency-file-generator`. To make changes, edit ../../dependencies.yaml and run `rapids-dependency-file-generator`.
 
@@ -97,5 +98,6 @@ requires = [
     "cuopt-mps-parser==25.8.*,>=0.0.0a0",
     "librmm==25.8.*,>=0.0.0a0",
     "ninja",
+    "python3-hwloc",
     "rapids-logger==0.1.*,>=0.0.0a0",
 ] # This list was generated by `rapids-dependency-file-generator`. To make changes, edit ../../dependencies.yaml and run `rapids-dependency-file-generator`.

From c2ba1392f546f25da57a1b8641a0676cd25c814d Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Fri, 1 Aug 2025 13:33:56 -0700
Subject: [PATCH 070/108] Try disabling tbb

---
 cpp/CMakeLists.txt             |  5 +++--
 dependencies.yaml              |  3 ---
 python/libcuopt/CMakeLists.txt | 12 ++++++------
 python/libcuopt/pyproject.toml |  2 --
 4 files changed, 9 insertions(+), 13 deletions(-)

diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt
index be730cd72b..41f8c0e547 100644
--- a/cpp/CMakeLists.txt
+++ b/cpp/CMakeLists.txt
@@ -161,7 +161,7 @@ else()
   find_package(RAFT REQUIRED)
 endif()
 
-find_package(TBB REQUIRED)
+# find_package(TBB REQUIRED)
 
 FetchContent_Declare(
   papilo
@@ -170,6 +170,7 @@ FetchContent_Declare(
 )
 
 set(BUILD_TESTING OFF CACHE BOOL "Disable test build for papilo")
+set(TBB OFF CACHE BOOL "Disable TBB")
 
 FetchContent_MakeAvailable(papilo)
 
@@ -256,7 +257,7 @@ target_link_libraries(cuopt
   CCCL::CCCL
   raft::raft
   cuopt::mps_parser
-  TBB::tbb
+  # TBB::tbb
   PRIVATE
   ${CUOPT_PRIVATE_CUDA_LIBS}
   )
diff --git a/dependencies.yaml b/dependencies.yaml
index 3ec6da7df3..5800b6659c 100644
--- a/dependencies.yaml
+++ b/dependencies.yaml
@@ -281,9 +281,6 @@ dependencies:
         packages:
           - tbb-devel
           - boost
-      - output_types: [requirements, pyproject]
-        packages:
-          - python3-hwloc
   build_common:
     common:
       - output_types: [conda, requirements, pyproject]
diff --git a/python/libcuopt/CMakeLists.txt b/python/libcuopt/CMakeLists.txt
index 7194e05784..0407338644 100644
--- a/python/libcuopt/CMakeLists.txt
+++ b/python/libcuopt/CMakeLists.txt
@@ -45,12 +45,12 @@ FetchContent_Declare(
   GIT_TAG v3.2
 )
 FetchContent_MakeAvailable(argparse)
-FetchContent_Declare(
-  tbb
-  GIT_REPOSITORY https://github.com/oneapi-src/oneTBB.git
-  GIT_TAG v2022.2.0
-)
-FetchContent_MakeAvailable(tbb)
+# FetchContent_Declare(
+#   tbb
+#   GIT_REPOSITORY https://github.com/oneapi-src/oneTBB.git
+#   GIT_TAG v2022.2.0
+# )
+# FetchContent_MakeAvailable(tbb)
 
 FetchContent_Declare(
   boost
diff --git a/python/libcuopt/pyproject.toml b/python/libcuopt/pyproject.toml
index e70b48424b..c9a900caa5 100644
--- a/python/libcuopt/pyproject.toml
+++ b/python/libcuopt/pyproject.toml
@@ -50,7 +50,6 @@ dependencies = [
     "nvidia-cusolver",
     "nvidia-cusparse",
     "nvidia-nvtx",
-    "python3-hwloc",
     "rapids-logger==0.1.*,>=0.0.0a0",
 ] # This list was generated by `rapids-dependency-file-generator`. To make changes, edit ../../dependencies.yaml and run `rapids-dependency-file-generator`.
 
@@ -98,6 +97,5 @@ requires = [
     "cuopt-mps-parser==25.8.*,>=0.0.0a0",
     "librmm==25.8.*,>=0.0.0a0",
     "ninja",
-    "python3-hwloc",
     "rapids-logger==0.1.*,>=0.0.0a0",
 ] # This list was generated by `rapids-dependency-file-generator`. To make changes, edit ../../dependencies.yaml and run `rapids-dependency-file-generator`.

From 7db72244cafdd03d2e95b4cad6dade8f324b101a Mon Sep 17 00:00:00 2001
From: Ramakrishna Prabhu 
Date: Mon, 4 Aug 2025 17:59:31 -0500
Subject: [PATCH 071/108] changes

---
 python/libcuopt/CMakeLists.txt | 126 ++++++++++++++++++++++++++++++---
 1 file changed, 118 insertions(+), 8 deletions(-)

diff --git a/python/libcuopt/CMakeLists.txt b/python/libcuopt/CMakeLists.txt
index 7194e05784..cd3424a068 100644
--- a/python/libcuopt/CMakeLists.txt
+++ b/python/libcuopt/CMakeLists.txt
@@ -38,19 +38,37 @@ endif()
 unset(cuopt_FOUND)
 
 
+# Set up Boost variables globally before any FetchContent operations
+# This ensures any subproject that tries to find_package(Boost) will find our setup
+set(Boost_FOUND TRUE CACHE BOOL "Boost found via FetchContent" FORCE)
+set(Boost_IOSTREAMS_FOUND TRUE CACHE BOOL "Boost iostreams found" FORCE)
+set(Boost_PROGRAM_OPTIONS_FOUND TRUE CACHE BOOL "Boost program_options found" FORCE)
+set(Boost_SERIALIZATION_FOUND TRUE CACHE BOOL "Boost serialization found" FORCE)
+set(Boost_FILESYSTEM_FOUND TRUE CACHE BOOL "Boost filesystem found" FORCE)
+set(Boost_SYSTEM_FOUND TRUE CACHE BOOL "Boost system found" FORCE)
+set(Boost_THREAD_FOUND TRUE CACHE BOOL "Boost thread found" FORCE)
+set(Boost_VERSION "1.85.0" CACHE STRING "Boost version" FORCE)
+set(Boost_VERSION_STRING "1.85.0" CACHE STRING "Boost version string" FORCE)
+set(PAPILO_HAVE_BOOST_IOSTREAMS 1 CACHE STRING "Papilo boost iostreams" FORCE)
+set(PAPILO_COMMAND_LINE_AVAILABLE 1 CACHE STRING "Papilo command line available" FORCE)
+set(PAPILO_SERIALIZATION_AVAILABLE 1 CACHE STRING "Papilo serialization available" FORCE)
+
 include(FetchContent)
+
+# Fetch dependencies
 FetchContent_Declare(
   argparse
   GIT_REPOSITORY https://github.com/p-ranav/argparse.git
   GIT_TAG v3.2
 )
 FetchContent_MakeAvailable(argparse)
-FetchContent_Declare(
-  tbb
-  GIT_REPOSITORY https://github.com/oneapi-src/oneTBB.git
-  GIT_TAG v2022.2.0
-)
-FetchContent_MakeAvailable(tbb)
+
+#FetchContent_Declare(
+#  tbb
+#  GIT_REPOSITORY https://github.com/oneapi-src/oneTBB.git
+#  GIT_TAG v2022.2.0
+#)
+#FetchContent_MakeAvailable(tbb)
 
 FetchContent_Declare(
   boost
@@ -59,6 +77,89 @@ FetchContent_Declare(
 )
 FetchContent_MakeAvailable(boost)
 
+# Path to boost root
+set(BOOST_ROOT "${boost_SOURCE_DIR}")
+
+# Only do this the first time Boost is fetched
+if(NOT EXISTS "${boost_SOURCE_DIR}/boost/version.hpp")
+  message(STATUS "Running Boost bootstrap and b2 headers...")
+
+  execute_process(
+    COMMAND ./bootstrap.sh
+    WORKING_DIRECTORY ${boost_SOURCE_DIR}
+    RESULT_VARIABLE BOOTSTRAP_RESULT
+  )
+  if(NOT BOOTSTRAP_RESULT EQUAL 0)
+    message(FATAL_ERROR "Boost bootstrap.sh failed with code ${BOOTSTRAP_RESULT}")
+  endif()
+
+  execute_process(
+    COMMAND ./b2 headers
+    WORKING_DIRECTORY ${boost_SOURCE_DIR}
+    RESULT_VARIABLE B2_RESULT
+  )
+  if(NOT B2_RESULT EQUAL 0)
+    message(FATAL_ERROR "Boost b2 headers failed with code ${B2_RESULT}")
+  endif()
+ 
+  execute_process(
+    COMMAND ./b2
+      link=shared
+      variant=release
+      --with-regex
+      --with-serialization
+      --with-program_options
+      --with-iostreams
+      threading=multi
+      cxxflags=-fPIC
+    WORKING_DIRECTORY ${boost_SOURCE_DIR}
+    RESULT_VARIABLE B2_BUILD_RESULT
+  )
+
+  if(NOT B2_BUILD_RESULT EQUAL 0)
+    message(FATAL_ERROR "Boost b2 library build failed with code ${B2_BUILD_RESULT}")
+  endif()
+
+endif()
+
+# --- Add include path manually ---
+include_directories(${boost_SOURCE_DIR})
+
+list(APPEND CMAKE_PREFIX_PATH "${boost_SOURCE_DIR}/stage/lib/cmake")
+
+list(APPEND CMAKE_PREFIX_PATH
+  "/repo/python/libcuopt/build/py3-none-linux_x86_64/_deps/boost-src/stage/lib/cmake"
+)
+
+
+# Now include specific components
+find_package(Boost 1.85 REQUIRED COMPONENTS
+  iostreams
+  program_options
+  serialization
+  regex
+)
+
+set(BOOST_STAGE_LIB "${boost_SOURCE_DIR}/stage/lib")
+
+# Manually define Boost paths and targets since boostorg/boost doesn't define them
+# Tell CMake exactly where to find boost/version.hpp
+#set(Boost_INCLUDE_DIR "${boost_SOURCE_DIR}/libs/config/include")
+set(Boost_INCLUDE_DIR "${boost_SOURCE_DIR}")
+#set(BOOST_ROOT "${boost_SOURCE_DIR}")
+#set(Boost_NO_SYSTEM_PATHS ON)
+set(BOOST_ENABLE_CMAKE ON)
+set(BOOST_ENABLE_LIBRARIES serialization program_options iostreams regex)
+
+
+# message(STATUS "TBB_DIR: ${tbb_BINARY_DIR}/")
+# set(TBB_DIR "${tbb_BINARY_DIR}/" CACHE PATH "TBB build directory" FORCE)
+
+
+# Set TBB auto-download ON before Papilo is configured
+# set(TBB_DOWNLOAD ON CACHE BOOL "Enable auto-download of TBB")
+# Setting TBB to OFF in the cache disables TBB for this project and all submodules that use this cache variable.
+set(TBB OFF CACHE BOOL "Disable TBB")
 
 set(BUILD_TESTS OFF)
 set(BUILD_BENCHMARKS OFF)
@@ -67,8 +168,17 @@ set(CUDA_STATIC_RUNTIME ON)
 
 add_subdirectory(../../cpp cuopt-cpp)
 
-target_link_libraries(cuopt PRIVATE argparse)
-target_link_libraries(cuopt_cli PRIVATE argparse)
+target_link_libraries(cuopt PRIVATE 
+    argparse 
+      ${BOOST_STAGE_LIB}/libboost_regex.so
+
+    Boost::regex 
+    Boost::program_options 
+    Boost::iostreams 
+    Boost::serialization
+)
+target_link_libraries(cuopt_cli PRIVATE 
+    argparse Boost::regex Boost::program_options Boost::iostreams Boost::serialization)
 
 set(rpaths
   "$ORIGIN/../lib64"

From 2f5c52970db570c6859e1a4988a34d0f7782759b Mon Sep 17 00:00:00 2001
From: Ramakrishna Prabhu 
Date: Mon, 4 Aug 2025 17:59:44 -0500
Subject: [PATCH 072/108] changes

---
 cpp/CMakeLists.txt | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt
index be730cd72b..29f8ae3f90 100644
--- a/cpp/CMakeLists.txt
+++ b/cpp/CMakeLists.txt
@@ -161,7 +161,7 @@ else()
   find_package(RAFT REQUIRED)
 endif()
 
-find_package(TBB REQUIRED)
+#find_package(TBB REQUIRED)
 
 FetchContent_Declare(
   papilo
@@ -256,7 +256,6 @@ target_link_libraries(cuopt
   CCCL::CCCL
   raft::raft
   cuopt::mps_parser
-  TBB::tbb
   PRIVATE
   ${CUOPT_PRIVATE_CUDA_LIBS}
   )

From aeb50277c7d2440ad2f010b2aab194cdfcbde64a Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Mon, 4 Aug 2025 20:37:35 -0700
Subject: [PATCH 073/108] Fix merge conflicts

---
 cpp/CMakeLists.txt | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt
index 50f4b31501..b58ded314f 100644
--- a/cpp/CMakeLists.txt
+++ b/cpp/CMakeLists.txt
@@ -257,10 +257,7 @@ target_link_libraries(cuopt
   CCCL::CCCL
   raft::raft
   cuopt::mps_parser
-<<<<<<< HEAD
-=======
-  # TBB::tbb
->>>>>>> c2ba1392f546f25da57a1b8641a0676cd25c814d
+  #TBB::tbb
   PRIVATE
   ${CUOPT_PRIVATE_CUDA_LIBS}
   )

From 8badba9fda83e25a748e817fbb79f89f34381249 Mon Sep 17 00:00:00 2001
From: Ramakrishna Prabhu 
Date: Tue, 5 Aug 2025 09:24:16 -0500
Subject: [PATCH 074/108] Address review comments

---
 cpp/CMakeLists.txt             |   6 +-
 python/libcuopt/CMakeLists.txt | 107 ++-------------------------------
 2 files changed, 5 insertions(+), 108 deletions(-)

diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt
index 50f4b31501..e5fd05f045 100644
--- a/cpp/CMakeLists.txt
+++ b/cpp/CMakeLists.txt
@@ -166,7 +166,7 @@ endif()
 FetchContent_Declare(
   papilo
   GIT_REPOSITORY "https://github.com/scipopt/papilo.git"
-  GIT_TAG "main"
+  GIT_TAG "v2.4.3"
 )
 
 set(BUILD_TESTING OFF CACHE BOOL "Disable test build for papilo")
@@ -257,10 +257,6 @@ target_link_libraries(cuopt
   CCCL::CCCL
   raft::raft
   cuopt::mps_parser
-<<<<<<< HEAD
-=======
-  # TBB::tbb
->>>>>>> c2ba1392f546f25da57a1b8641a0676cd25c814d
   PRIVATE
   ${CUOPT_PRIVATE_CUDA_LIBS}
   )
diff --git a/python/libcuopt/CMakeLists.txt b/python/libcuopt/CMakeLists.txt
index 259b54e20d..93a52a92ca 100644
--- a/python/libcuopt/CMakeLists.txt
+++ b/python/libcuopt/CMakeLists.txt
@@ -37,24 +37,11 @@ endif()
 
 unset(cuopt_FOUND)
 
-
-# Set up Boost variables globally before any FetchContent operations
-# This ensures any subproject that tries to find_package(Boost) will find our setup
-set(Boost_FOUND TRUE CACHE BOOL "Boost found via FetchContent" FORCE)
-set(Boost_IOSTREAMS_FOUND TRUE CACHE BOOL "Boost iostreams found" FORCE)
-set(Boost_PROGRAM_OPTIONS_FOUND TRUE CACHE BOOL "Boost program_options found" FORCE)
-set(Boost_SERIALIZATION_FOUND TRUE CACHE BOOL "Boost serialization found" FORCE)
-set(Boost_FILESYSTEM_FOUND TRUE CACHE BOOL "Boost filesystem found" FORCE)
-set(Boost_SYSTEM_FOUND TRUE CACHE BOOL "Boost system found" FORCE)
-set(Boost_THREAD_FOUND TRUE CACHE BOOL "Boost thread found" FORCE)
-set(Boost_VERSION "1.85.0" CACHE STRING "Boost version" FORCE)
-set(Boost_VERSION_STRING "1.85.0" CACHE STRING "Boost version string" FORCE)
-set(PAPILO_HAVE_BOOST_IOSTREAMS 1 CACHE STRING "Papilo boost iostreams" FORCE)
-set(PAPILO_COMMAND_LINE_AVAILABLE 1 CACHE STRING "Papilo command line available" FORCE)
-set(PAPILO_SERIALIZATION_AVAILABLE 1 CACHE STRING "Papilo serialization available" FORCE)
+set(PAPILO_HAVE_BOOST_IOSTREAMS ON)
+set(PAPILO_COMMAND_LINE_AVAILABLE ON)
+set(PAPILO_SERIALIZATION_AVAILABLE ON)
 
 include(FetchContent)
-
 # Fetch dependencies
 FetchContent_Declare(
   argparse
@@ -70,88 +57,8 @@ FetchContent_Declare(
 )
 FetchContent_MakeAvailable(boost)
 
-# Path to boost root
-set(BOOST_ROOT "${boost_SOURCE_DIR}")
-
-# Only do this the first time Boost is fetched
-if(NOT EXISTS "${boost_SOURCE_DIR}/boost/version.hpp")
-  message(STATUS "Running Boost bootstrap and b2 headers...")
-
-  execute_process(
-    COMMAND ./bootstrap.sh
-    WORKING_DIRECTORY ${boost_SOURCE_DIR}
-    RESULT_VARIABLE BOOTSTRAP_RESULT
-  )
-  if(NOT BOOTSTRAP_RESULT EQUAL 0)
-    message(FATAL_ERROR "Boost bootstrap.sh failed with code ${BOOTSTRAP_RESULT}")
-  endif()
-
-  execute_process(
-    COMMAND ./b2 headers
-    WORKING_DIRECTORY ${boost_SOURCE_DIR}
-    RESULT_VARIABLE B2_RESULT
-  )
-  if(NOT B2_RESULT EQUAL 0)
-    message(FATAL_ERROR "Boost b2 headers failed with code ${B2_RESULT}")
-  endif()
- 
-  execute_process(
-    COMMAND ./b2
-      link=shared
-      variant=release
-      --with-regex
-      --with-serialization
-      --with-program_options
-      --with-iostreams
-      threading=multi
-      cxxflags=-fPIC
-    WORKING_DIRECTORY ${boost_SOURCE_DIR}
-    RESULT_VARIABLE B2_BUILD_RESULT
-  )
-
-  if(NOT B2_BUILD_RESULT EQUAL 0)
-    message(FATAL_ERROR "Boost b2 library build failed with code ${B2_BUILD_RESULT}")
-  endif()
-
-endif()
-
-# --- Add include path manually ---
-include_directories(${boost_SOURCE_DIR})
-
-list(APPEND CMAKE_PREFIX_PATH "${boost_SOURCE_DIR}/stage/lib/cmake")
-
-list(APPEND CMAKE_PREFIX_PATH
-  "/repo/python/libcuopt/build/py3-none-linux_x86_64/_deps/boost-src/stage/lib/cmake"
-)
-
+find_package(Boost REQUIRED)
 
-# Now include specific components
-find_package(Boost 1.85 REQUIRED COMPONENTS
-  iostreams
-  program_options
-  serialization
-  regex
-)
-
-set(BOOST_STAGE_LIB "${boost_SOURCE_DIR}/stage/lib")
-
-# Manually define Boost paths and targets since boostorg/boost doesn't define them
-# Tell CMake exactly where to find boost/version.hpp
-#set(Boost_INCLUDE_DIR "${boost_SOURCE_DIR}/libs/config/include")
-set(Boost_INCLUDE_DIR "${boost_SOURCE_DIR}")
-#set(BOOST_ROOT "${boost_SOURCE_DIR}")
-#set(Boost_NO_SYSTEM_PATHS ON)
-set(BOOST_ENABLE_CMAKE ON)
-set(BOOST_ENABLE_LIBRARIES serialization program_options iostreams regex)
-
-
-# message(STATUS "TBB_DIR: ${tbb_BINARY_DIR}/")
-# set(TBB_DIR "${tbb_BINARY_DIR}/" CACHE PATH "TBB build directory" FORCE)
-
-
-# Set TBB auto-download ON before Papilo is configured
-# set(TBB_DOWNLOAD ON CACHE BOOL "Enable auto-download of TBB")
-# Setting TBB to OFF in the cache disables TBB for this project and all submodules that use this cache variable.
 set(TBB OFF CACHE BOOL "Disable TBB")
 
 set(BUILD_TESTS OFF)
@@ -163,12 +70,6 @@ add_subdirectory(../../cpp cuopt-cpp)
 
 target_link_libraries(cuopt PRIVATE 
     argparse 
-      ${BOOST_STAGE_LIB}/libboost_regex.so
-
-    Boost::regex 
-    Boost::program_options 
-    Boost::iostreams 
-    Boost::serialization
 )
 target_link_libraries(cuopt_cli PRIVATE 
     argparse Boost::regex Boost::program_options Boost::iostreams Boost::serialization)

From 9855c1e50184457244a18d7968feccf31571871a Mon Sep 17 00:00:00 2001
From: Ramakrishna Prabhu 
Date: Thu, 7 Aug 2025 14:16:23 -0500
Subject: [PATCH 075/108] changes

---
 ci/build_wheel_libcuopt.sh     | 40 ++++++++++++++++++++++++++++++++++
 python/libcuopt/CMakeLists.txt | 37 ++++++++++++++++++++++++-------
 2 files changed, 69 insertions(+), 8 deletions(-)

diff --git a/ci/build_wheel_libcuopt.sh b/ci/build_wheel_libcuopt.sh
index d4234156a0..a62af9e984 100755
--- a/ci/build_wheel_libcuopt.sh
+++ b/ci/build_wheel_libcuopt.sh
@@ -24,6 +24,46 @@ package_dir="python/libcuopt"
 # Install Boost and TBB
 #bash ci/utils/install_boost_tbb.sh
 
+BOOST_VERSION="1.85"
+BOOST_INSTALL_DIR="/opt/boost-${BOOST_VERSION}"
+BOOST_SRC_DIR="/tmp/boost"
+
+# Remove any existing Boost installation at the target location
+if [ -d "${BOOST_INSTALL_DIR}" ]; then
+    echo "Removing existing Boost at ${BOOST_INSTALL_DIR}"
+    rm -rf "${BOOST_INSTALL_DIR}"
+fi
+
+# Clean up any previous source directory
+if [ -d "${BOOST_SRC_DIR}" ]; then
+    echo "Removing previous Boost source at ${BOOST_SRC_DIR}"
+    rm -rf "${BOOST_SRC_DIR}"
+fi
+
+echo "Cloning Boost ${BOOST_VERSION}..."
+git clone --branch "boost-${BOOST_VERSION}.0" --depth 1 --recurse-submodules https://github.com/boostorg/boost.git "${BOOST_SRC_DIR}"
+
+pushd "${BOOST_SRC_DIR}"
+
+echo "Bootstrapping Boost..."
+./bootstrap.sh
+
+echo "Installing Boost to ${BOOST_INSTALL_DIR}..."
+./b2 install --prefix="${BOOST_INSTALL_DIR}" -j"$(nproc)" \
+    --with-filesystem \
+    --with-regex \
+    --with-log \
+    --with-thread \
+    --with-system \
+    --with-iostreams \
+    --with-serialization \
+    --with-program_options
+
+popd
+
+echo "Cleaning up Boost source directory..."
+rm -rf "${BOOST_SRC_DIR}"
+
 export SKBUILD_CMAKE_ARGS="-DCUOPT_BUILD_WHEELS=ON;-DDISABLE_DEPRECATION_WARNING=ON"
 
 # For pull requests we are enabling assert mode.
diff --git a/python/libcuopt/CMakeLists.txt b/python/libcuopt/CMakeLists.txt
index 93a52a92ca..f027b2449a 100644
--- a/python/libcuopt/CMakeLists.txt
+++ b/python/libcuopt/CMakeLists.txt
@@ -1,3 +1,4 @@
+
 # SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 # SPDX-License-Identifier: Apache-2.0
 #
@@ -50,14 +51,33 @@ FetchContent_Declare(
 )
 FetchContent_MakeAvailable(argparse)
 
-FetchContent_Declare(
-  boost
-  GIT_REPOSITORY https://github.com/boostorg/boost.git
-  GIT_TAG boost-1.85.0
-)
-FetchContent_MakeAvailable(boost)
+set(BOOST_ROOT "/opt/boost-1.85")
+find_package(Boost REQUIRED COMPONENTS filesystem regex log thread system iostreams serialization program_options)
+include_directories(${Boost_INCLUDE_DIRS})
+
+#FetchContent_Declare(
+#  boost
+#  GIT_REPOSITORY https://github.com/boostorg/boost.git
+#  GIT_TAG boost-1.85.0
+#)
+#FetchContent_MakeAvailable(boost)
+
+#set(Boost_DIR "${boost_SOURCE_DIR}/tools/boost_install/")
+
+#find_package(Boost REQUIRED)
+
+#include(FetchContent)
+
+#FetchContent_Declare(
+#  boost_cmake
+#  GIT_REPOSITORY https://github.com/boostorg/boost-cmake.git
+#  GIT_TAG boost-1.85.0
+#)
+#FetchContent_MakeAvailable(boost_cmake)
+
+# Then use target_link_libraries with Boost::X targets
+
 
-find_package(Boost REQUIRED)
 
 set(TBB OFF CACHE BOOL "Disable TBB")
 
@@ -72,7 +92,8 @@ target_link_libraries(cuopt PRIVATE
     argparse 
 )
 target_link_libraries(cuopt_cli PRIVATE 
-    argparse Boost::regex Boost::program_options Boost::iostreams Boost::serialization)
+    argparse
+)
 
 set(rpaths
   "$ORIGIN/../lib64"

From ec717a873936b2cb5faf69c1edaae9e347f7fac3 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Thu, 7 Aug 2025 17:48:11 -0700
Subject: [PATCH 076/108] Make papilo a system include

---
 cpp/CMakeLists.txt | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt
index b9ba3704d7..5c0b9539c7 100644
--- a/cpp/CMakeLists.txt
+++ b/cpp/CMakeLists.txt
@@ -221,12 +221,13 @@ target_link_options(cuopt PRIVATE "${CUOPT_BINARY_DIR}/fatbin.ld")
 add_library(cuopt::cuopt ALIAS cuopt)
 # ##################################################################################################
 # - include paths ---------------------------------------------------------------------------------
+
+target_include_directories(cuopt SYSTEM PRIVATE "${papilo_SOURCE_DIR}/src" "${papilo_BINARY_DIR}")
+
 target_include_directories(cuopt
   PRIVATE
   "${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty"
   "${CMAKE_CURRENT_SOURCE_DIR}/src"
-  "$"
-  "$"
   PUBLIC
   "$"
   "$"

From d0c9426a49cc77e1ff403f80da847d01dc98f328 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Thu, 7 Aug 2025 21:38:51 -0700
Subject: [PATCH 077/108] Disable test build for papilo

---
 python/libcuopt/CMakeLists.txt | 26 +-------------------------
 1 file changed, 1 insertion(+), 25 deletions(-)

diff --git a/python/libcuopt/CMakeLists.txt b/python/libcuopt/CMakeLists.txt
index 719e9f0442..8f3a625801 100644
--- a/python/libcuopt/CMakeLists.txt
+++ b/python/libcuopt/CMakeLists.txt
@@ -43,7 +43,6 @@ set(PAPILO_COMMAND_LINE_AVAILABLE ON)
 set(PAPILO_SERIALIZATION_AVAILABLE ON)
 
 include(FetchContent)
-# Fetch dependencies
 FetchContent_Declare(
   argparse
   GIT_REPOSITORY https://github.com/p-ranav/argparse.git
@@ -55,31 +54,8 @@ set(BOOST_ROOT "/opt/boost-1.85")
 find_package(Boost REQUIRED COMPONENTS filesystem regex log thread system iostreams serialization program_options)
 include_directories(${Boost_INCLUDE_DIRS})
 
-#FetchContent_Declare(
-#  boost
-#  GIT_REPOSITORY https://github.com/boostorg/boost.git
-#  GIT_TAG boost-1.85.0
-#)
-#FetchContent_MakeAvailable(boost)
-
-#set(Boost_DIR "${boost_SOURCE_DIR}/tools/boost_install/")
-
-#find_package(Boost REQUIRED)
-
-#include(FetchContent)
-
-#FetchContent_Declare(
-#  boost_cmake
-#  GIT_REPOSITORY https://github.com/boostorg/boost-cmake.git
-#  GIT_TAG boost-1.85.0
-#)
-#FetchContent_MakeAvailable(boost_cmake)
-
-# Then use target_link_libraries with Boost::X targets
-
-
-
 set(TBB OFF CACHE BOOL "Disable TBB")
+set(BUILD_TESTING OFF CACHE BOOL "Disable test build for papilo")
 
 set(BUILD_TESTS OFF)
 set(BUILD_BENCHMARKS OFF)

From ca8b11d572ec367f7b1c8ce8afcf2c2eeb039fcf Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Thu, 7 Aug 2025 21:59:46 -0700
Subject: [PATCH 078/108] Do not build papilo binaries

---
 python/libcuopt/CMakeLists.txt | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/python/libcuopt/CMakeLists.txt b/python/libcuopt/CMakeLists.txt
index 8f3a625801..fa369c0d43 100644
--- a/python/libcuopt/CMakeLists.txt
+++ b/python/libcuopt/CMakeLists.txt
@@ -56,6 +56,9 @@ include_directories(${Boost_INCLUDE_DIRS})
 
 set(TBB OFF CACHE BOOL "Disable TBB")
 set(BUILD_TESTING OFF CACHE BOOL "Disable test build for papilo")
+set(PAPILO_HAVE_BOOST_IOSTREAMS OFF)
+set(PAPILO_COMMAND_LINE_AVAILABLE OFF)
+set(PAPILO_SERIALIZATION_AVAILABLE OFF)
 
 set(BUILD_TESTS OFF)
 set(BUILD_BENCHMARKS OFF)

From 480ae87ff92a96df69117fe9dafbb81b173271a6 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Thu, 7 Aug 2025 22:36:29 -0700
Subject: [PATCH 079/108] Disable in main cmake

---
 cpp/CMakeLists.txt | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt
index 628c2fe96c..53762028b1 100644
--- a/cpp/CMakeLists.txt
+++ b/cpp/CMakeLists.txt
@@ -171,6 +171,9 @@ FetchContent_Declare(
 
 set(BUILD_TESTING OFF CACHE BOOL "Disable test build for papilo")
 set(TBB OFF CACHE BOOL "Disable TBB")
+set(PAPILO_HAVE_BOOST_IOSTREAMS OFF)
+set(PAPILO_COMMAND_LINE_AVAILABLE OFF)
+set(PAPILO_SERIALIZATION_AVAILABLE OFF)
 
 FetchContent_MakeAvailable(papilo)
 

From b9e84924178ccf3fbf02e5ebcfccb0eaa603203c Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Thu, 7 Aug 2025 23:35:45 -0700
Subject: [PATCH 080/108] Disable papilo bin build

---
 cpp/CMakeLists.txt             | 1 +
 python/libcuopt/CMakeLists.txt | 4 ----
 2 files changed, 1 insertion(+), 4 deletions(-)

diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt
index 53762028b1..5f64e4cd99 100644
--- a/cpp/CMakeLists.txt
+++ b/cpp/CMakeLists.txt
@@ -174,6 +174,7 @@ set(TBB OFF CACHE BOOL "Disable TBB")
 set(PAPILO_HAVE_BOOST_IOSTREAMS OFF)
 set(PAPILO_COMMAND_LINE_AVAILABLE OFF)
 set(PAPILO_SERIALIZATION_AVAILABLE OFF)
+set(PAPILO_NO_BINARIES ON)
 
 FetchContent_MakeAvailable(papilo)
 
diff --git a/python/libcuopt/CMakeLists.txt b/python/libcuopt/CMakeLists.txt
index fa369c0d43..8ae8c7b445 100644
--- a/python/libcuopt/CMakeLists.txt
+++ b/python/libcuopt/CMakeLists.txt
@@ -38,10 +38,6 @@ endif()
 
 unset(cuopt_FOUND)
 
-set(PAPILO_HAVE_BOOST_IOSTREAMS ON)
-set(PAPILO_COMMAND_LINE_AVAILABLE ON)
-set(PAPILO_SERIALIZATION_AVAILABLE ON)
-
 include(FetchContent)
 FetchContent_Declare(
   argparse

From efe1922ab5d163cc1a469e4c234a4d7e9cde3f4d Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Thu, 7 Aug 2025 23:36:46 -0700
Subject: [PATCH 081/108] Disable papilo bin build

---
 python/libcuopt/CMakeLists.txt | 1 +
 1 file changed, 1 insertion(+)

diff --git a/python/libcuopt/CMakeLists.txt b/python/libcuopt/CMakeLists.txt
index 8ae8c7b445..5335e6e8f4 100644
--- a/python/libcuopt/CMakeLists.txt
+++ b/python/libcuopt/CMakeLists.txt
@@ -55,6 +55,7 @@ set(BUILD_TESTING OFF CACHE BOOL "Disable test build for papilo")
 set(PAPILO_HAVE_BOOST_IOSTREAMS OFF)
 set(PAPILO_COMMAND_LINE_AVAILABLE OFF)
 set(PAPILO_SERIALIZATION_AVAILABLE OFF)
+set(PAPILO_NO_BINARIES ON)
 
 set(BUILD_TESTS OFF)
 set(BUILD_BENCHMARKS OFF)

From f968f6174a4e0a3ec40a5a18757e0795015fc4b6 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Fri, 8 Aug 2025 00:27:20 -0700
Subject: [PATCH 082/108] fix warning

---
 cpp/src/linear_programming/solve.cu | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/cpp/src/linear_programming/solve.cu b/cpp/src/linear_programming/solve.cu
index 88395d48ec..8d4c6df7d8 100644
--- a/cpp/src/linear_programming/solve.cu
+++ b/cpp/src/linear_programming/solve.cu
@@ -782,7 +782,7 @@ optimization_problem_solution_t solve_lp(
     const optimization_problem_t& op_problem,                             \
     detail::problem_t& problem,                                           \
     pdlp_solver_settings_t const& settings,                               \
-    bool is_batch_mode = false);                                                       \
+    bool is_batch_mode);                                                               \
                                                                                        \
   template optimization_problem_t mps_data_model_to_optimization_problem( \
     raft::handle_t const* handle_ptr,                                                  \

From 1c2982211ac129a69941fe19350417d40bd12811 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Fri, 8 Aug 2025 00:31:31 -0700
Subject: [PATCH 083/108] Disable lusol and quadmath

---
 python/libcuopt/CMakeLists.txt | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/python/libcuopt/CMakeLists.txt b/python/libcuopt/CMakeLists.txt
index 5335e6e8f4..0735aa1422 100644
--- a/python/libcuopt/CMakeLists.txt
+++ b/python/libcuopt/CMakeLists.txt
@@ -56,6 +56,8 @@ set(PAPILO_HAVE_BOOST_IOSTREAMS OFF)
 set(PAPILO_COMMAND_LINE_AVAILABLE OFF)
 set(PAPILO_SERIALIZATION_AVAILABLE OFF)
 set(PAPILO_NO_BINARIES ON)
+set(LUSOL OFF)
+set(QUADMATH OFF)
 
 set(BUILD_TESTS OFF)
 set(BUILD_BENCHMARKS OFF)

From 25e3883241dba5fe688c41bd7e566172634dab87 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Fri, 8 Aug 2025 13:50:16 -0700
Subject: [PATCH 084/108] Compiles

---
 VERSION                                       |   2 +-
 cpp/CMakeLists.txt                            |   2 +
 cpp/src/linear_programming/solve.cu           |  15 +-
 cpp/src/mip/CMakeLists.txt                    |   2 +-
 cpp/src/mip/presolve/third_party_presolve.cu  | 365 ------------------
 cpp/src/mip/presolve/third_party_presolve.cuh |  49 ---
 cpp/src/mip/solve.cu                          |  14 +-
 python/libcuopt/CMakeLists.txt                |   6 +-
 python/libcuopt/pyproject.toml                |  16 +-
 9 files changed, 38 insertions(+), 433 deletions(-)
 delete mode 100644 cpp/src/mip/presolve/third_party_presolve.cu
 delete mode 100644 cpp/src/mip/presolve/third_party_presolve.cuh

diff --git a/VERSION b/VERSION
index 296e35288d..e9056eb367 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-25.10.00
+25.10.00a134
\ No newline at end of file
diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt
index 5f64e4cd99..6c1cfde140 100644
--- a/cpp/CMakeLists.txt
+++ b/cpp/CMakeLists.txt
@@ -167,6 +167,7 @@ FetchContent_Declare(
   papilo
   GIT_REPOSITORY "https://github.com/scipopt/papilo.git"
   GIT_TAG "v2.4.3"
+  SYSTEM
 )
 
 set(BUILD_TESTING OFF CACHE BOOL "Disable test build for papilo")
@@ -236,6 +237,7 @@ target_include_directories(cuopt
   "$"
   "$"
   "$"
+  INTERFACE
   "$"
 )
 
diff --git a/cpp/src/linear_programming/solve.cu b/cpp/src/linear_programming/solve.cu
index 8d4c6df7d8..94ab9bfce9 100644
--- a/cpp/src/linear_programming/solve.cu
+++ b/cpp/src/linear_programming/solve.cu
@@ -25,7 +25,7 @@
 #include 
 
 #include 
-#include 
+#include 
 #include 
 #include 
 
@@ -603,12 +603,12 @@ optimization_problem_solution_t solve_lp(optimization_problem_t>();
-      auto [reduced_problem, postsolve_status] =
+      auto [reduced_problem, feasible] =
         presolver->apply(op_problem,
                          cuopt::linear_programming::problem_category_t::LP,
                          settings.tolerances.absolute_primal_tolerance,
                          presolve_time_limit);
-      if (postsolve_status == papilo::PresolveStatus::kInfeasible) {
+      if (!feasible) {
         return optimization_problem_solution_t(
           pdlp_termination_status_t::PrimalInfeasible, op_problem.get_handle_ptr()->get_stream());
       }
@@ -648,6 +648,15 @@ optimization_problem_solution_t solve_lp(optimization_problem_tget_stream());
 
+      thrust::fill(rmm::exec_policy(op_problem.get_handle_ptr()->get_stream()),
+                   dual_solution.data(),
+                   dual_solution.data() + dual_solution.size(),
+                   std::numeric_limits::signaling_NaN());
+      thrust::fill(rmm::exec_policy(op_problem.get_handle_ptr()->get_stream()),
+                   reduced_costs.data(),
+                   reduced_costs.data() + reduced_costs.size(),
+                   std::numeric_limits::signaling_NaN());
+
       auto full_stats = solution.get_additional_termination_information();
       // add third party presolve time to cuopt presolve time
       full_stats.solve_time += presolve_time;
diff --git a/cpp/src/mip/CMakeLists.txt b/cpp/src/mip/CMakeLists.txt
index 126aabfcbf..43c0072802 100644
--- a/cpp/src/mip/CMakeLists.txt
+++ b/cpp/src/mip/CMakeLists.txt
@@ -42,7 +42,7 @@ list(PREPEND
   ${CMAKE_CURRENT_SOURCE_DIR}/presolve/load_balanced_bounds_presolve.cu
   ${CMAKE_CURRENT_SOURCE_DIR}/presolve/multi_probe.cu
   ${CMAKE_CURRENT_SOURCE_DIR}/presolve/probing_cache.cu
-  ${CMAKE_CURRENT_SOURCE_DIR}/presolve/third_party_presolve.cu
+  ${CMAKE_CURRENT_SOURCE_DIR}/presolve/third_party_presolve.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/presolve/trivial_presolve.cu
   ${CMAKE_CURRENT_SOURCE_DIR}/problem/load_balanced_problem.cu
   ${CMAKE_CURRENT_SOURCE_DIR}/feasibility_jump/feasibility_jump.cu
diff --git a/cpp/src/mip/presolve/third_party_presolve.cu b/cpp/src/mip/presolve/third_party_presolve.cu
deleted file mode 100644
index 69ba8710d7..0000000000
--- a/cpp/src/mip/presolve/third_party_presolve.cu
+++ /dev/null
@@ -1,365 +0,0 @@
-/*
- * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
- * SPDX-License-Identifier: Apache-2.0
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include 
-#include 
-#include 
-#include 
-#include 
-
-namespace cuopt::linear_programming::detail {
-
-template 
-papilo::Problem build_papilo_problem(const optimization_problem_t& op_problem)
-{
-  // Build papilo problem from optimization problem
-  papilo::ProblemBuilder builder;
-
-  // Get problem dimensions
-  const i_t num_cols = op_problem.get_n_variables();
-  const i_t num_rows = op_problem.get_n_constraints();
-  const i_t nnz      = op_problem.get_nnz();
-
-  cuopt_expects(op_problem.get_sense() == false,
-                error_type_t::ValidationError,
-                "Papilo does not support maximization problems");
-
-  builder.reserve(nnz, num_rows, num_cols);
-
-  // Get problem data from optimization problem
-  const auto& coefficients = op_problem.get_constraint_matrix_values();
-  const auto& offsets      = op_problem.get_constraint_matrix_offsets();
-  const auto& variables    = op_problem.get_constraint_matrix_indices();
-  const auto& obj_coeffs   = op_problem.get_objective_coefficients();
-  const auto& var_lb       = op_problem.get_variable_lower_bounds();
-  const auto& var_ub       = op_problem.get_variable_upper_bounds();
-  const auto& bounds       = op_problem.get_constraint_bounds();
-  const auto& row_types    = op_problem.get_row_types();
-  const auto& constr_lb    = op_problem.get_constraint_lower_bounds();
-  const auto& constr_ub    = op_problem.get_constraint_upper_bounds();
-  const auto& var_types    = op_problem.get_variable_types();
-
-  // Copy data to host
-  std::vector h_coefficients = cuopt::host_copy(coefficients);
-  std::vector h_offsets      = cuopt::host_copy(offsets);
-  std::vector h_variables    = cuopt::host_copy(variables);
-  std::vector h_obj_coeffs   = cuopt::host_copy(obj_coeffs);
-  std::vector h_var_lb       = cuopt::host_copy(var_lb);
-  std::vector h_var_ub       = cuopt::host_copy(var_ub);
-  std::vector h_bounds       = cuopt::host_copy(bounds);
-  std::vector h_row_types   = cuopt::host_copy(row_types);
-  std::vector h_constr_lb    = cuopt::host_copy(constr_lb);
-  std::vector h_constr_ub    = cuopt::host_copy(constr_ub);
-  std::vector h_var_types  = cuopt::host_copy(var_types);
-
-  auto constr_bounds_empty = h_constr_lb.empty() && h_constr_ub.empty();
-  if (constr_bounds_empty) {
-    for (size_t i = 0; i < h_row_types.size(); ++i) {
-      if (h_row_types[i] == 'L') {
-        h_constr_lb.push_back(-std::numeric_limits::infinity());
-        h_constr_ub.push_back(h_bounds[i]);
-      } else if (h_row_types[i] == 'G') {
-        h_constr_lb.push_back(h_bounds[i]);
-        h_constr_ub.push_back(std::numeric_limits::infinity());
-      } else if (h_row_types[i] == 'E') {
-        h_constr_lb.push_back(h_bounds[i]);
-        h_constr_ub.push_back(h_bounds[i]);
-      }
-    }
-  }
-
-  builder.setNumCols(num_cols);
-  builder.setNumRows(num_rows);
-
-  builder.setObjAll(h_obj_coeffs);
-  builder.setObjOffset(op_problem.get_objective_offset());
-
-  if (!h_var_lb.empty() && !h_var_ub.empty()) {
-    builder.setColLbAll(h_var_lb);
-    builder.setColUbAll(h_var_ub);
-  }
-
-  for (size_t i = 0; i < h_var_types.size(); ++i) {
-    builder.setColIntegral(i, h_var_types[i] == var_t::INTEGER);
-  }
-
-  if (!h_constr_lb.empty() && !h_constr_ub.empty()) {
-    builder.setRowLhsAll(h_constr_lb);
-    builder.setRowRhsAll(h_constr_ub);
-  }
-
-  // Add constraints row by row
-  for (size_t i = 0; i < h_constr_lb.size(); ++i) {
-    // Get row entries
-    i_t row_start   = h_offsets[i];
-    i_t row_end     = h_offsets[i + 1];
-    i_t num_entries = row_end - row_start;
-    builder.addRowEntries(
-      i, num_entries, h_variables.data() + row_start, h_coefficients.data() + row_start);
-    builder.setRowLhsInf(i, h_constr_lb[i] == -std::numeric_limits::infinity());
-    builder.setRowRhsInf(i, h_constr_ub[i] == std::numeric_limits::infinity());
-    if (h_constr_lb[i] == -std::numeric_limits::infinity()) { builder.setRowLhs(i, 0); }
-    if (h_constr_ub[i] == std::numeric_limits::infinity()) { builder.setRowRhs(i, 0); }
-  }
-
-  for (size_t i = 0; i < h_var_lb.size(); ++i) {
-    builder.setColLbInf(i, h_var_lb[i] == -std::numeric_limits::infinity());
-    builder.setColUbInf(i, h_var_ub[i] == std::numeric_limits::infinity());
-    if (h_var_lb[i] == -std::numeric_limits::infinity()) { builder.setColLb(i, 0); }
-    if (h_var_ub[i] == std::numeric_limits::infinity()) { builder.setColUb(i, 0); }
-  }
-  return builder.build();
-}
-
-template 
-optimization_problem_t build_optimization_problem(
-  papilo::Problem const& papilo_problem, raft::handle_t const* handle_ptr)
-{
-  optimization_problem_t op_problem(handle_ptr);
-
-  auto obj = papilo_problem.getObjective();
-  op_problem.set_objective_offset(obj.offset);
-
-  if (papilo_problem.getNRows() == 0 && papilo_problem.getNCols() == 0) {
-    // FIXME: Shouldn't need to set offsets
-    std::vector h_offsets{0};
-    std::vector h_indices{};
-    std::vector h_values{};
-    op_problem.set_csr_constraint_matrix(h_values.data(),
-                                         h_values.size(),
-                                         h_indices.data(),
-                                         h_indices.size(),
-                                         h_offsets.data(),
-                                         h_offsets.size());
-
-    return op_problem;
-  }
-
-  op_problem.set_objective_coefficients(obj.coefficients.data(), obj.coefficients.size());
-
-  auto& constraint_matrix = papilo_problem.getConstraintMatrix();
-  auto row_lower          = constraint_matrix.getLeftHandSides();
-  auto row_upper          = constraint_matrix.getRightHandSides();
-  auto col_lower          = papilo_problem.getLowerBounds();
-  auto col_upper          = papilo_problem.getUpperBounds();
-
-  auto row_flags = constraint_matrix.getRowFlags();
-  for (size_t i = 0; i < row_flags.size(); i++) {
-    if (row_flags[i].test(papilo::RowFlag::kLhsInf)) {
-      row_lower[i] = -std::numeric_limits::infinity();
-    }
-    if (row_flags[i].test(papilo::RowFlag::kRhsInf)) {
-      row_upper[i] = std::numeric_limits::infinity();
-    }
-  }
-
-  op_problem.set_constraint_lower_bounds(row_lower.data(), row_lower.size());
-  op_problem.set_constraint_upper_bounds(row_upper.data(), row_upper.size());
-
-  auto [index_range, nrows] = constraint_matrix.getRangeInfo();
-
-  std::vector offsets(nrows + 1);
-  // papilo indices do not start from 0 after presolve
-  size_t start = index_range[0].start;
-  for (i_t i = 0; i < nrows; i++) {
-    offsets[i] = index_range[i].start - start;
-  }
-  offsets[nrows] = index_range[nrows - 1].end - start;
-
-  i_t nnz = constraint_matrix.getNnz();
-  assert(offsets[nrows] == nnz);
-
-  const int* cols   = constraint_matrix.getConstraintMatrix().getColumns();
-  const f_t* coeffs = constraint_matrix.getConstraintMatrix().getValues();
-  op_problem.set_csr_constraint_matrix(
-    &(coeffs[start]), nnz, &(cols[start]), nnz, offsets.data(), nrows + 1);
-
-  auto col_flags = papilo_problem.getColFlags();
-  std::vector var_types(col_flags.size());
-  for (size_t i = 0; i < col_flags.size(); i++) {
-    var_types[i] =
-      col_flags[i].test(papilo::ColFlag::kIntegral) ? var_t::INTEGER : var_t::CONTINUOUS;
-    if (col_flags[i].test(papilo::ColFlag::kLbInf)) {
-      col_lower[i] = -std::numeric_limits::infinity();
-    }
-    if (col_flags[i].test(papilo::ColFlag::kUbInf)) {
-      col_upper[i] = std::numeric_limits::infinity();
-    }
-  }
-
-  op_problem.set_variable_lower_bounds(col_lower.data(), col_lower.size());
-  op_problem.set_variable_upper_bounds(col_upper.data(), col_upper.size());
-  op_problem.set_variable_types(var_types.data(), var_types.size());
-
-  return op_problem;
-}
-
-void check_presolve_status(const papilo::PresolveStatus& status)
-{
-  switch (status) {
-    case papilo::PresolveStatus::kUnchanged:
-      CUOPT_LOG_INFO("Presolve did not result in any changes");
-      break;
-    case papilo::PresolveStatus::kReduced: CUOPT_LOG_INFO("Presolve reduced the problem"); break;
-    case papilo::PresolveStatus::kUnbndOrInfeas:
-      CUOPT_LOG_INFO("Presolve found an unbounded or infeasible problem");
-      break;
-    case papilo::PresolveStatus::kInfeasible:
-      CUOPT_LOG_INFO("Presolve found an infeasible problem");
-      break;
-    case papilo::PresolveStatus::kUnbounded:
-      CUOPT_LOG_INFO("Presolve found an unbounded problem");
-      break;
-  }
-}
-
-void check_postsolve_status(const papilo::PostsolveStatus& status)
-{
-  switch (status) {
-    case papilo::PostsolveStatus::kOk:
-      CUOPT_LOG_INFO("Post-solve succeeded");
-      break;
-      // This occurs when the solution is not feasible
-    case papilo::PostsolveStatus::kFailed: CUOPT_LOG_INFO("Post-solve failed"); break;
-  }
-}
-
-template 
-void set_presolve_methods(papilo::Presolve& presolver, problem_category_t category)
-{
-  using uptr = std::unique_ptr>;
-
-  // fast presolvers
-  presolver.addPresolveMethod(uptr(new papilo::SingletonCols()));
-  presolver.addPresolveMethod(uptr(new papilo::CoefficientStrengthening()));
-  presolver.addPresolveMethod(uptr(new papilo::ConstraintPropagation()));
-
-  // medium presolvers
-  presolver.addPresolveMethod(uptr(new papilo::FixContinuous()));
-  presolver.addPresolveMethod(uptr(new papilo::SimpleProbing()));
-  presolver.addPresolveMethod(uptr(new papilo::ParallelRowDetection()));
-  presolver.addPresolveMethod(uptr(new papilo::ParallelColDetection()));
-  // FIXME: Postsolve fails with this method
-  // presolver.addPresolveMethod(uptr(new papilo::SingletonStuffing()));
-  presolver.addPresolveMethod(uptr(new papilo::DualFix()));
-  presolver.addPresolveMethod(uptr(new papilo::SimplifyInequalities()));
-
-  // exhaustive presolvers
-  presolver.addPresolveMethod(uptr(new papilo::ImplIntDetection()));
-  presolver.addPresolveMethod(uptr(new papilo::DominatedCols()));
-  presolver.addPresolveMethod(uptr(new papilo::Probing()));
-
-  presolver.addPresolveMethod(uptr(new papilo::DualInfer));
-  presolver.addPresolveMethod(uptr(new papilo::SimpleSubstitution()));
-  presolver.addPresolveMethod(uptr(new papilo::Sparsify()));
-  presolver.addPresolveMethod(uptr(new papilo::Substitution()));
-}
-
-template 
-void set_presolve_options(papilo::Presolve& presolver,
-                          problem_category_t category,
-                          f_t absolute_tolerance,
-                          double time_limit)
-{
-  presolver.getPresolveOptions().tlim    = time_limit;
-  presolver.getPresolveOptions().epsilon = absolute_tolerance;
-  presolver.getPresolveOptions().feastol = absolute_tolerance;
-}
-
-template 
-std::pair, papilo::PresolveStatus>
-third_party_presolve_t::apply(optimization_problem_t const& op_problem,
-                                        problem_category_t category,
-                                        f_t absolute_tolerance,
-                                        double time_limit)
-{
-  papilo::Problem papilo_problem = build_papilo_problem(op_problem);
-
-  CUOPT_LOG_INFO("Unpresolved problem:: Num variables: %d Num constraints: %d, NNZ: %d",
-                 papilo_problem.getNCols(),
-                 papilo_problem.getNRows(),
-                 papilo_problem.getConstraintMatrix().getNnz());
-
-  papilo::Presolve presolver;
-  set_presolve_methods(presolver, category);
-  set_presolve_options(presolver, category, absolute_tolerance, time_limit);
-
-  // Disable papilo logs
-  presolver.setVerbosityLevel(papilo::VerbosityLevel::kQuiet);
-
-  auto result = presolver.apply(papilo_problem);
-  check_presolve_status(result.status);
-  if (result.status == papilo::PresolveStatus::kInfeasible) {
-    return std::make_pair(optimization_problem_t(op_problem.get_handle_ptr()),
-                          papilo::PresolveStatus::kInfeasible);
-  }
-  post_solve_storage_ = result.postsolve;
-  CUOPT_LOG_INFO("Presolved problem:: Num variables: %d Num constraints: %d, NNZ: %d",
-                 papilo_problem.getNCols(),
-                 papilo_problem.getNRows(),
-                 papilo_problem.getConstraintMatrix().getNnz());
-
-  return std::make_pair(
-    build_optimization_problem(papilo_problem, op_problem.get_handle_ptr()),
-    result.status);
-}
-
-template 
-void third_party_presolve_t::undo(rmm::device_uvector& primal_solution,
-                                            rmm::device_uvector& dual_solution,
-                                            rmm::device_uvector& reduced_costs,
-                                            problem_category_t category,
-                                            rmm::cuda_stream_view stream_view)
-{
-  auto primal_sol_vec_h    = cuopt::host_copy(primal_solution, stream_view);
-  auto dual_sol_vec_h      = cuopt::host_copy(dual_solution, stream_view);
-  auto reduced_costs_vec_h = cuopt::host_copy(reduced_costs, stream_view);
-
-  papilo::Solution reduced_sol(primal_sol_vec_h);
-  papilo::Solution full_sol;
-
-  papilo::Message Msg{};
-  Msg.setVerbosityLevel(papilo::VerbosityLevel::kQuiet);
-  papilo::Postsolve post_solver{Msg, post_solve_storage_.getNum()};
-
-  bool is_optimal = false;
-  auto status     = post_solver.undo(reduced_sol, full_sol, post_solve_storage_, is_optimal);
-  check_postsolve_status(status);
-
-  primal_solution.resize(full_sol.primal.size(), stream_view);
-  dual_solution.resize(full_sol.primal.size(), stream_view);
-  reduced_costs.resize(full_sol.primal.size(), stream_view);
-  raft::copy(primal_solution.data(), full_sol.primal.data(), full_sol.primal.size(), stream_view);
-  thrust::fill(rmm::exec_policy(stream_view),
-               dual_solution.data(),
-               dual_solution.data() + dual_solution.size(),
-               std::numeric_limits::signaling_NaN());
-  thrust::fill(rmm::exec_policy(stream_view),
-               reduced_costs.data(),
-               reduced_costs.data() + reduced_costs.size(),
-               std::numeric_limits::signaling_NaN());
-}
-
-#if MIP_INSTANTIATE_FLOAT
-template class third_party_presolve_t;
-#endif
-
-#if MIP_INSTANTIATE_DOUBLE
-template class third_party_presolve_t;
-#endif
-
-}  // namespace cuopt::linear_programming::detail
diff --git a/cpp/src/mip/presolve/third_party_presolve.cuh b/cpp/src/mip/presolve/third_party_presolve.cuh
deleted file mode 100644
index 0b40fb5973..0000000000
--- a/cpp/src/mip/presolve/third_party_presolve.cuh
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
- * SPDX-License-Identifier: Apache-2.0
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include 
-#include 
-#include 
-
-#include 
-
-namespace cuopt::linear_programming::detail {
-
-template 
-class third_party_presolve_t {
- public:
-  third_party_presolve_t() = default;
-
-  std::pair, papilo::PresolveStatus> apply(
-    optimization_problem_t const& op_problem,
-    problem_category_t category,
-    f_t absolute_tolerance,
-    double time_limit);
-
-  void undo(rmm::device_uvector& primal_solution,
-            rmm::device_uvector& dual_solution,
-            rmm::device_uvector& reduced_costs,
-            problem_category_t category,
-            rmm::cuda_stream_view stream_view);
-
- private:
-  papilo::PostsolveStorage post_solve_storage_;
-};
-
-}  // namespace cuopt::linear_programming::detail
diff --git a/cpp/src/mip/solve.cu b/cpp/src/mip/solve.cu
index 3f035e7905..3835dadbc2 100644
--- a/cpp/src/mip/solve.cu
+++ b/cpp/src/mip/solve.cu
@@ -18,7 +18,7 @@
 #include 
 
 #include 
-#include 
+#include 
 #include 
 #include 
 #include 
@@ -193,12 +193,12 @@ mip_solution_t solve_mip(optimization_problem_t& op_problem,
       // Note that this is not the presolve time, but the time limit for presolve.
       const double presolve_time_limit = 0.1 * time_limit;
       presolver = std::make_unique>();
-      auto [reduced_op_problem, postsolve_status] =
+      auto [reduced_op_problem, feasible] =
         presolver->apply(op_problem,
                          cuopt::linear_programming::problem_category_t::MIP,
                          settings.tolerances.absolute_tolerance,
                          presolve_time_limit);
-      if (postsolve_status == papilo::PresolveStatus::kInfeasible) {
+      if (!feasible) {
         return mip_solution_t(mip_termination_status_t::Infeasible,
                                         solver_stats_t{},
                                         op_problem.get_handle_ptr()->get_stream());
@@ -231,6 +231,14 @@ mip_solution_t solve_mip(optimization_problem_t& op_problem,
                       cuopt::linear_programming::problem_category_t::MIP,
                       op_problem.get_handle_ptr()->get_stream());
 
+      thrust::fill(rmm::exec_policy(op_problem.get_handle_ptr()->get_stream()),
+                   dual_solution.data(),
+                   dual_solution.data() + dual_solution.size(),
+                   std::numeric_limits::signaling_NaN());
+      thrust::fill(rmm::exec_policy(op_problem.get_handle_ptr()->get_stream()),
+                   reduced_costs.data(),
+                   reduced_costs.data() + reduced_costs.size(),
+                   std::numeric_limits::signaling_NaN());
       detail::problem_t full_problem(op_problem);
       detail::solution_t full_sol(full_problem);
       full_sol.copy_new_assignment(cuopt::host_copy(primal_solution));
diff --git a/python/libcuopt/CMakeLists.txt b/python/libcuopt/CMakeLists.txt
index 0735aa1422..c6d82c3422 100644
--- a/python/libcuopt/CMakeLists.txt
+++ b/python/libcuopt/CMakeLists.txt
@@ -46,9 +46,9 @@ FetchContent_Declare(
 )
 FetchContent_MakeAvailable(argparse)
 
-set(BOOST_ROOT "/opt/boost-1.85")
-find_package(Boost REQUIRED COMPONENTS filesystem regex log thread system iostreams serialization program_options)
-include_directories(${Boost_INCLUDE_DIRS})
+# set(BOOST_ROOT "/opt/boost-1.85")
+# find_package(Boost REQUIRED COMPONENTS filesystem regex log thread system iostreams serialization program_options)
+# include_directories(${Boost_INCLUDE_DIRS})
 
 set(TBB OFF CACHE BOOL "Disable TBB")
 set(BUILD_TESTING OFF CACHE BOOL "Disable test build for papilo")
diff --git a/python/libcuopt/pyproject.toml b/python/libcuopt/pyproject.toml
index af79471e20..fadfb181bc 100644
--- a/python/libcuopt/pyproject.toml
+++ b/python/libcuopt/pyproject.toml
@@ -21,7 +21,7 @@ requires = [
 ] # This list was generated by `rapids-dependency-file-generator`. To make changes, edit ../../dependencies.yaml and run `rapids-dependency-file-generator`.
 
 [project]
-name = "libcuopt"
+name = "libcuopt-cu12"
 dynamic = ["version"]
 description = "cuOpt - GPU Optimizer (C++)"
 readme = { file = "README.md", content-type = "text/markdown" }
@@ -44,12 +44,12 @@ classifiers = [
 ]
 dependencies = [
     "cuopt-mps-parser==25.10.*,>=0.0.0a0",
-    "librmm==25.10.*,>=0.0.0a0",
-    "nvidia-cublas",
-    "nvidia-curand",
-    "nvidia-cusolver",
-    "nvidia-cusparse",
-    "nvidia-nvtx",
+    "librmm-cu12==25.10.*,>=0.0.0a0",
+    "nvidia-cublas-cu12",
+    "nvidia-curand-cu12",
+    "nvidia-cusolver-cu12",
+    "nvidia-cusparse-cu12",
+    "nvidia-nvtx-cu12",
     "rapids-logger==0.1.*,>=0.0.0a0",
 ] # This list was generated by `rapids-dependency-file-generator`. To make changes, edit ../../dependencies.yaml and run `rapids-dependency-file-generator`.
 
@@ -95,7 +95,7 @@ matrix-entry = "cuda_suffixed=true;use_cuda_wheels=true"
 requires = [
     "cmake>=3.30.4",
     "cuopt-mps-parser==25.10.*,>=0.0.0a0",
-    "librmm==25.10.*,>=0.0.0a0",
+    "librmm-cu12==25.10.*,>=0.0.0a0",
     "ninja",
     "rapids-logger==0.1.*,>=0.0.0a0",
 ] # This list was generated by `rapids-dependency-file-generator`. To make changes, edit ../../dependencies.yaml and run `rapids-dependency-file-generator`.

From d42cd209e65729d357e49800a58d158e08c72a51 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Mon, 11 Aug 2025 08:38:04 -0700
Subject: [PATCH 085/108] Revert to compile install

---
 cpp/CMakeLists.txt             |  6 +++---
 python/libcuopt/CMakeLists.txt | 16 ++++++++--------
 2 files changed, 11 insertions(+), 11 deletions(-)

diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt
index 6c1cfde140..fddbe2b25a 100644
--- a/cpp/CMakeLists.txt
+++ b/cpp/CMakeLists.txt
@@ -172,9 +172,9 @@ FetchContent_Declare(
 
 set(BUILD_TESTING OFF CACHE BOOL "Disable test build for papilo")
 set(TBB OFF CACHE BOOL "Disable TBB")
-set(PAPILO_HAVE_BOOST_IOSTREAMS OFF)
-set(PAPILO_COMMAND_LINE_AVAILABLE OFF)
-set(PAPILO_SERIALIZATION_AVAILABLE OFF)
+# set(PAPILO_HAVE_BOOST_IOSTREAMS OFF)
+# set(PAPILO_COMMAND_LINE_AVAILABLE OFF)
+# set(PAPILO_SERIALIZATION_AVAILABLE OFF)
 set(PAPILO_NO_BINARIES ON)
 
 FetchContent_MakeAvailable(papilo)
diff --git a/python/libcuopt/CMakeLists.txt b/python/libcuopt/CMakeLists.txt
index c6d82c3422..531ba8505c 100644
--- a/python/libcuopt/CMakeLists.txt
+++ b/python/libcuopt/CMakeLists.txt
@@ -46,18 +46,18 @@ FetchContent_Declare(
 )
 FetchContent_MakeAvailable(argparse)
 
-# set(BOOST_ROOT "/opt/boost-1.85")
-# find_package(Boost REQUIRED COMPONENTS filesystem regex log thread system iostreams serialization program_options)
-# include_directories(${Boost_INCLUDE_DIRS})
+set(BOOST_ROOT "/opt/boost-1.85")
+find_package(Boost REQUIRED COMPONENTS filesystem regex log thread system iostreams serialization program_options)
+include_directories(${Boost_INCLUDE_DIRS})
 
 set(TBB OFF CACHE BOOL "Disable TBB")
 set(BUILD_TESTING OFF CACHE BOOL "Disable test build for papilo")
-set(PAPILO_HAVE_BOOST_IOSTREAMS OFF)
-set(PAPILO_COMMAND_LINE_AVAILABLE OFF)
-set(PAPILO_SERIALIZATION_AVAILABLE OFF)
+# set(PAPILO_HAVE_BOOST_IOSTREAMS OFF)
+# set(PAPILO_COMMAND_LINE_AVAILABLE OFF)
+# set(PAPILO_SERIALIZATION_AVAILABLE OFF)
 set(PAPILO_NO_BINARIES ON)
-set(LUSOL OFF)
-set(QUADMATH OFF)
+# option(LUSOL OFF)
+# option(QUADMATH OFF)
 
 set(BUILD_TESTS OFF)
 set(BUILD_BENCHMARKS OFF)

From 7b59fe3fc09822ff76cac96f9819f53b3dad7908 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Mon, 11 Aug 2025 09:24:13 -0700
Subject: [PATCH 086/108] Fix style

---
 python/libcuopt/pyproject.toml | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/python/libcuopt/pyproject.toml b/python/libcuopt/pyproject.toml
index fadfb181bc..af79471e20 100644
--- a/python/libcuopt/pyproject.toml
+++ b/python/libcuopt/pyproject.toml
@@ -21,7 +21,7 @@ requires = [
 ] # This list was generated by `rapids-dependency-file-generator`. To make changes, edit ../../dependencies.yaml and run `rapids-dependency-file-generator`.
 
 [project]
-name = "libcuopt-cu12"
+name = "libcuopt"
 dynamic = ["version"]
 description = "cuOpt - GPU Optimizer (C++)"
 readme = { file = "README.md", content-type = "text/markdown" }
@@ -44,12 +44,12 @@ classifiers = [
 ]
 dependencies = [
     "cuopt-mps-parser==25.10.*,>=0.0.0a0",
-    "librmm-cu12==25.10.*,>=0.0.0a0",
-    "nvidia-cublas-cu12",
-    "nvidia-curand-cu12",
-    "nvidia-cusolver-cu12",
-    "nvidia-cusparse-cu12",
-    "nvidia-nvtx-cu12",
+    "librmm==25.10.*,>=0.0.0a0",
+    "nvidia-cublas",
+    "nvidia-curand",
+    "nvidia-cusolver",
+    "nvidia-cusparse",
+    "nvidia-nvtx",
     "rapids-logger==0.1.*,>=0.0.0a0",
 ] # This list was generated by `rapids-dependency-file-generator`. To make changes, edit ../../dependencies.yaml and run `rapids-dependency-file-generator`.
 
@@ -95,7 +95,7 @@ matrix-entry = "cuda_suffixed=true;use_cuda_wheels=true"
 requires = [
     "cmake>=3.30.4",
     "cuopt-mps-parser==25.10.*,>=0.0.0a0",
-    "librmm-cu12==25.10.*,>=0.0.0a0",
+    "librmm==25.10.*,>=0.0.0a0",
     "ninja",
     "rapids-logger==0.1.*,>=0.0.0a0",
 ] # This list was generated by `rapids-dependency-file-generator`. To make changes, edit ../../dependencies.yaml and run `rapids-dependency-file-generator`.

From cd893868879823c7082f085323d173129396948a Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Mon, 11 Aug 2025 09:40:28 -0700
Subject: [PATCH 087/108] Revert to compile install

---
 cpp/src/linear_programming/solve.cu           |   2 +-
 cpp/src/mip/CMakeLists.txt                    |   2 +-
 cpp/src/mip/presolve/third_party_presolve.cu  | 363 ++++++++++++++++++
 cpp/src/mip/presolve/third_party_presolve.cuh |  49 +++
 cpp/src/mip/solve.cu                          |   2 +-
 5 files changed, 415 insertions(+), 3 deletions(-)
 create mode 100644 cpp/src/mip/presolve/third_party_presolve.cu
 create mode 100644 cpp/src/mip/presolve/third_party_presolve.cuh

diff --git a/cpp/src/linear_programming/solve.cu b/cpp/src/linear_programming/solve.cu
index 94ab9bfce9..3bdf1b3b08 100644
--- a/cpp/src/linear_programming/solve.cu
+++ b/cpp/src/linear_programming/solve.cu
@@ -25,7 +25,7 @@
 #include 
 
 #include 
-#include 
+#include 
 #include 
 #include 
 
diff --git a/cpp/src/mip/CMakeLists.txt b/cpp/src/mip/CMakeLists.txt
index 43c0072802..126aabfcbf 100644
--- a/cpp/src/mip/CMakeLists.txt
+++ b/cpp/src/mip/CMakeLists.txt
@@ -42,7 +42,7 @@ list(PREPEND
   ${CMAKE_CURRENT_SOURCE_DIR}/presolve/load_balanced_bounds_presolve.cu
   ${CMAKE_CURRENT_SOURCE_DIR}/presolve/multi_probe.cu
   ${CMAKE_CURRENT_SOURCE_DIR}/presolve/probing_cache.cu
-  ${CMAKE_CURRENT_SOURCE_DIR}/presolve/third_party_presolve.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/presolve/third_party_presolve.cu
   ${CMAKE_CURRENT_SOURCE_DIR}/presolve/trivial_presolve.cu
   ${CMAKE_CURRENT_SOURCE_DIR}/problem/load_balanced_problem.cu
   ${CMAKE_CURRENT_SOURCE_DIR}/feasibility_jump/feasibility_jump.cu
diff --git a/cpp/src/mip/presolve/third_party_presolve.cu b/cpp/src/mip/presolve/third_party_presolve.cu
new file mode 100644
index 0000000000..4069c59cbc
--- /dev/null
+++ b/cpp/src/mip/presolve/third_party_presolve.cu
@@ -0,0 +1,363 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+namespace cuopt::linear_programming::detail {
+
+template 
+papilo::Problem build_papilo_problem(const optimization_problem_t& op_problem)
+{
+  // Build papilo problem from optimization problem
+  papilo::ProblemBuilder builder;
+
+  // Get problem dimensions
+  const i_t num_cols = op_problem.get_n_variables();
+  const i_t num_rows = op_problem.get_n_constraints();
+  const i_t nnz      = op_problem.get_nnz();
+
+  cuopt_expects(op_problem.get_sense() == false,
+                error_type_t::ValidationError,
+                "Papilo does not support maximization problems");
+
+  builder.reserve(nnz, num_rows, num_cols);
+
+  // Get problem data from optimization problem
+  const auto& coefficients = op_problem.get_constraint_matrix_values();
+  const auto& offsets      = op_problem.get_constraint_matrix_offsets();
+  const auto& variables    = op_problem.get_constraint_matrix_indices();
+  const auto& obj_coeffs   = op_problem.get_objective_coefficients();
+  const auto& var_lb       = op_problem.get_variable_lower_bounds();
+  const auto& var_ub       = op_problem.get_variable_upper_bounds();
+  const auto& bounds       = op_problem.get_constraint_bounds();
+  const auto& row_types    = op_problem.get_row_types();
+  const auto& constr_lb    = op_problem.get_constraint_lower_bounds();
+  const auto& constr_ub    = op_problem.get_constraint_upper_bounds();
+  const auto& var_types    = op_problem.get_variable_types();
+
+  // Copy data to host
+  std::vector h_coefficients = cuopt::host_copy(coefficients);
+  std::vector h_offsets      = cuopt::host_copy(offsets);
+  std::vector h_variables    = cuopt::host_copy(variables);
+  std::vector h_obj_coeffs   = cuopt::host_copy(obj_coeffs);
+  std::vector h_var_lb       = cuopt::host_copy(var_lb);
+  std::vector h_var_ub       = cuopt::host_copy(var_ub);
+  std::vector h_bounds       = cuopt::host_copy(bounds);
+  std::vector h_row_types   = cuopt::host_copy(row_types);
+  std::vector h_constr_lb    = cuopt::host_copy(constr_lb);
+  std::vector h_constr_ub    = cuopt::host_copy(constr_ub);
+  std::vector h_var_types  = cuopt::host_copy(var_types);
+
+  auto constr_bounds_empty = h_constr_lb.empty() && h_constr_ub.empty();
+  if (constr_bounds_empty) {
+    for (size_t i = 0; i < h_row_types.size(); ++i) {
+      if (h_row_types[i] == 'L') {
+        h_constr_lb.push_back(-std::numeric_limits::infinity());
+        h_constr_ub.push_back(h_bounds[i]);
+      } else if (h_row_types[i] == 'G') {
+        h_constr_lb.push_back(h_bounds[i]);
+        h_constr_ub.push_back(std::numeric_limits::infinity());
+      } else if (h_row_types[i] == 'E') {
+        h_constr_lb.push_back(h_bounds[i]);
+        h_constr_ub.push_back(h_bounds[i]);
+      }
+    }
+  }
+
+  builder.setNumCols(num_cols);
+  builder.setNumRows(num_rows);
+
+  builder.setObjAll(h_obj_coeffs);
+  builder.setObjOffset(op_problem.get_objective_offset());
+
+  if (!h_var_lb.empty() && !h_var_ub.empty()) {
+    builder.setColLbAll(h_var_lb);
+    builder.setColUbAll(h_var_ub);
+  }
+
+  for (size_t i = 0; i < h_var_types.size(); ++i) {
+    builder.setColIntegral(i, h_var_types[i] == var_t::INTEGER);
+  }
+
+  if (!h_constr_lb.empty() && !h_constr_ub.empty()) {
+    builder.setRowLhsAll(h_constr_lb);
+    builder.setRowRhsAll(h_constr_ub);
+  }
+
+  // Add constraints row by row
+  for (size_t i = 0; i < h_constr_lb.size(); ++i) {
+    // Get row entries
+    i_t row_start   = h_offsets[i];
+    i_t row_end     = h_offsets[i + 1];
+    i_t num_entries = row_end - row_start;
+    builder.addRowEntries(
+      i, num_entries, h_variables.data() + row_start, h_coefficients.data() + row_start);
+    builder.setRowLhsInf(i, h_constr_lb[i] == -std::numeric_limits::infinity());
+    builder.setRowRhsInf(i, h_constr_ub[i] == std::numeric_limits::infinity());
+    if (h_constr_lb[i] == -std::numeric_limits::infinity()) { builder.setRowLhs(i, 0); }
+    if (h_constr_ub[i] == std::numeric_limits::infinity()) { builder.setRowRhs(i, 0); }
+  }
+
+  for (size_t i = 0; i < h_var_lb.size(); ++i) {
+    builder.setColLbInf(i, h_var_lb[i] == -std::numeric_limits::infinity());
+    builder.setColUbInf(i, h_var_ub[i] == std::numeric_limits::infinity());
+    if (h_var_lb[i] == -std::numeric_limits::infinity()) { builder.setColLb(i, 0); }
+    if (h_var_ub[i] == std::numeric_limits::infinity()) { builder.setColUb(i, 0); }
+  }
+  return builder.build();
+}
+
+template 
+optimization_problem_t build_optimization_problem(
+  papilo::Problem const& papilo_problem, raft::handle_t const* handle_ptr)
+{
+  optimization_problem_t op_problem(handle_ptr);
+
+  auto obj = papilo_problem.getObjective();
+  op_problem.set_objective_offset(obj.offset);
+
+  if (papilo_problem.getNRows() == 0 && papilo_problem.getNCols() == 0) {
+    // FIXME: Shouldn't need to set offsets
+    std::vector h_offsets{0};
+    std::vector h_indices{};
+    std::vector h_values{};
+    op_problem.set_csr_constraint_matrix(h_values.data(),
+                                         h_values.size(),
+                                         h_indices.data(),
+                                         h_indices.size(),
+                                         h_offsets.data(),
+                                         h_offsets.size());
+
+    return op_problem;
+  }
+
+  op_problem.set_objective_coefficients(obj.coefficients.data(), obj.coefficients.size());
+
+  auto& constraint_matrix = papilo_problem.getConstraintMatrix();
+  auto row_lower          = constraint_matrix.getLeftHandSides();
+  auto row_upper          = constraint_matrix.getRightHandSides();
+  auto col_lower          = papilo_problem.getLowerBounds();
+  auto col_upper          = papilo_problem.getUpperBounds();
+
+  auto row_flags = constraint_matrix.getRowFlags();
+  for (size_t i = 0; i < row_flags.size(); i++) {
+    if (row_flags[i].test(papilo::RowFlag::kLhsInf)) {
+      row_lower[i] = -std::numeric_limits::infinity();
+    }
+    if (row_flags[i].test(papilo::RowFlag::kRhsInf)) {
+      row_upper[i] = std::numeric_limits::infinity();
+    }
+  }
+
+  op_problem.set_constraint_lower_bounds(row_lower.data(), row_lower.size());
+  op_problem.set_constraint_upper_bounds(row_upper.data(), row_upper.size());
+
+  auto [index_range, nrows] = constraint_matrix.getRangeInfo();
+
+  std::vector offsets(nrows + 1);
+  // papilo indices do not start from 0 after presolve
+  size_t start = index_range[0].start;
+  for (i_t i = 0; i < nrows; i++) {
+    offsets[i] = index_range[i].start - start;
+  }
+  offsets[nrows] = index_range[nrows - 1].end - start;
+
+  i_t nnz = constraint_matrix.getNnz();
+  assert(offsets[nrows] == nnz);
+
+  const int* cols   = constraint_matrix.getConstraintMatrix().getColumns();
+  const f_t* coeffs = constraint_matrix.getConstraintMatrix().getValues();
+  op_problem.set_csr_constraint_matrix(
+    &(coeffs[start]), nnz, &(cols[start]), nnz, offsets.data(), nrows + 1);
+
+  auto col_flags = papilo_problem.getColFlags();
+  std::vector var_types(col_flags.size());
+  for (size_t i = 0; i < col_flags.size(); i++) {
+    var_types[i] =
+      col_flags[i].test(papilo::ColFlag::kIntegral) ? var_t::INTEGER : var_t::CONTINUOUS;
+    if (col_flags[i].test(papilo::ColFlag::kLbInf)) {
+      col_lower[i] = -std::numeric_limits::infinity();
+    }
+    if (col_flags[i].test(papilo::ColFlag::kUbInf)) {
+      col_upper[i] = std::numeric_limits::infinity();
+    }
+  }
+
+  op_problem.set_variable_lower_bounds(col_lower.data(), col_lower.size());
+  op_problem.set_variable_upper_bounds(col_upper.data(), col_upper.size());
+  op_problem.set_variable_types(var_types.data(), var_types.size());
+
+  return op_problem;
+}
+
+void check_presolve_status(const papilo::PresolveStatus& status)
+{
+  switch (status) {
+    case papilo::PresolveStatus::kUnchanged:
+      CUOPT_LOG_INFO("Presolve did not result in any changes");
+      break;
+    case papilo::PresolveStatus::kReduced: CUOPT_LOG_INFO("Presolve reduced the problem"); break;
+    case papilo::PresolveStatus::kUnbndOrInfeas:
+      CUOPT_LOG_INFO("Presolve found an unbounded or infeasible problem");
+      break;
+    case papilo::PresolveStatus::kInfeasible:
+      CUOPT_LOG_INFO("Presolve found an infeasible problem");
+      break;
+    case papilo::PresolveStatus::kUnbounded:
+      CUOPT_LOG_INFO("Presolve found an unbounded problem");
+      break;
+  }
+}
+
+void check_postsolve_status(const papilo::PostsolveStatus& status)
+{
+  switch (status) {
+    case papilo::PostsolveStatus::kOk:
+      CUOPT_LOG_INFO("Post-solve succeeded");
+      break;
+      // This occurs when the solution is not feasible
+    case papilo::PostsolveStatus::kFailed: CUOPT_LOG_INFO("Post-solve failed"); break;
+  }
+}
+
+template 
+void set_presolve_methods(papilo::Presolve& presolver, problem_category_t category)
+{
+  using uptr = std::unique_ptr>;
+
+  // fast presolvers
+  presolver.addPresolveMethod(uptr(new papilo::SingletonCols()));
+  presolver.addPresolveMethod(uptr(new papilo::CoefficientStrengthening()));
+  presolver.addPresolveMethod(uptr(new papilo::ConstraintPropagation()));
+
+  // medium presolvers
+  presolver.addPresolveMethod(uptr(new papilo::FixContinuous()));
+  presolver.addPresolveMethod(uptr(new papilo::SimpleProbing()));
+  presolver.addPresolveMethod(uptr(new papilo::ParallelRowDetection()));
+  presolver.addPresolveMethod(uptr(new papilo::ParallelColDetection()));
+  // FIXME: Postsolve fails with this method
+  // presolver.addPresolveMethod(uptr(new papilo::SingletonStuffing()));
+  presolver.addPresolveMethod(uptr(new papilo::DualFix()));
+  presolver.addPresolveMethod(uptr(new papilo::SimplifyInequalities()));
+
+  // exhaustive presolvers
+  presolver.addPresolveMethod(uptr(new papilo::ImplIntDetection()));
+  presolver.addPresolveMethod(uptr(new papilo::DominatedCols()));
+  presolver.addPresolveMethod(uptr(new papilo::Probing()));
+
+  presolver.addPresolveMethod(uptr(new papilo::DualInfer));
+  presolver.addPresolveMethod(uptr(new papilo::SimpleSubstitution()));
+  presolver.addPresolveMethod(uptr(new papilo::Sparsify()));
+  presolver.addPresolveMethod(uptr(new papilo::Substitution()));
+}
+
+template 
+void set_presolve_options(papilo::Presolve& presolver,
+                          problem_category_t category,
+                          f_t absolute_tolerance,
+                          double time_limit)
+{
+  presolver.getPresolveOptions().tlim    = time_limit;
+  presolver.getPresolveOptions().epsilon = absolute_tolerance;
+  presolver.getPresolveOptions().feastol = absolute_tolerance;
+}
+
+template 
+std::pair, bool> third_party_presolve_t::apply(
+  optimization_problem_t const& op_problem,
+  problem_category_t category,
+  f_t absolute_tolerance,
+  double time_limit)
+{
+  papilo::Problem papilo_problem = build_papilo_problem(op_problem);
+
+  CUOPT_LOG_INFO("Unpresolved problem:: Num variables: %d Num constraints: %d, NNZ: %d",
+                 papilo_problem.getNCols(),
+                 papilo_problem.getNRows(),
+                 papilo_problem.getConstraintMatrix().getNnz());
+
+  papilo::Presolve presolver;
+  set_presolve_methods(presolver, category);
+  set_presolve_options(presolver, category, absolute_tolerance, time_limit);
+
+  // Disable papilo logs
+  presolver.setVerbosityLevel(papilo::VerbosityLevel::kQuiet);
+
+  auto result = presolver.apply(papilo_problem);
+  check_presolve_status(result.status);
+  if (result.status == papilo::PresolveStatus::kInfeasible ||
+      result.status == papilo::PresolveStatus::kUnbndOrInfeas) {
+    return std::make_pair(optimization_problem_t(op_problem.get_handle_ptr()), false);
+  }
+  post_solve_storage_ = result.postsolve;
+  CUOPT_LOG_INFO("Presolved problem:: Num variables: %d Num constraints: %d, NNZ: %d",
+                 papilo_problem.getNCols(),
+                 papilo_problem.getNRows(),
+                 papilo_problem.getConstraintMatrix().getNnz());
+
+  return std::make_pair(build_optimization_problem(papilo_problem, true), result.status);
+}
+
+template 
+void third_party_presolve_t::undo(rmm::device_uvector& primal_solution,
+                                            rmm::device_uvector& dual_solution,
+                                            rmm::device_uvector& reduced_costs,
+                                            problem_category_t category,
+                                            rmm::cuda_stream_view stream_view)
+{
+  auto primal_sol_vec_h    = cuopt::host_copy(primal_solution, stream_view);
+  auto dual_sol_vec_h      = cuopt::host_copy(dual_solution, stream_view);
+  auto reduced_costs_vec_h = cuopt::host_copy(reduced_costs, stream_view);
+
+  papilo::Solution reduced_sol(primal_sol_vec_h);
+  papilo::Solution full_sol;
+
+  papilo::Message Msg{};
+  Msg.setVerbosityLevel(papilo::VerbosityLevel::kQuiet);
+  papilo::Postsolve post_solver{Msg, post_solve_storage_.getNum()};
+
+  bool is_optimal = false;
+  auto status     = post_solver.undo(reduced_sol, full_sol, post_solve_storage_, is_optimal);
+  check_postsolve_status(status);
+
+  primal_solution.resize(full_sol.primal.size(), stream_view);
+  dual_solution.resize(full_sol.primal.size(), stream_view);
+  reduced_costs.resize(full_sol.primal.size(), stream_view);
+  raft::copy(primal_solution.data(), full_sol.primal.data(), full_sol.primal.size(), stream_view);
+  thrust::fill(rmm::exec_policy(stream_view),
+               dual_solution.data(),
+               dual_solution.data() + dual_solution.size(),
+               std::numeric_limits::signaling_NaN());
+  thrust::fill(rmm::exec_policy(stream_view),
+               reduced_costs.data(),
+               reduced_costs.data() + reduced_costs.size(),
+               std::numeric_limits::signaling_NaN());
+}
+
+#if MIP_INSTANTIATE_FLOAT
+template class third_party_presolve_t;
+#endif
+
+#if MIP_INSTANTIATE_DOUBLE
+template class third_party_presolve_t;
+#endif
+
+}  // namespace cuopt::linear_programming::detail
diff --git a/cpp/src/mip/presolve/third_party_presolve.cuh b/cpp/src/mip/presolve/third_party_presolve.cuh
new file mode 100644
index 0000000000..9eb9559618
--- /dev/null
+++ b/cpp/src/mip/presolve/third_party_presolve.cuh
@@ -0,0 +1,49 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include 
+#include 
+#include 
+
+#include 
+
+namespace cuopt::linear_programming::detail {
+
+template 
+class third_party_presolve_t {
+ public:
+  third_party_presolve_t() = default;
+
+  std::pair, bool> apply(
+    optimization_problem_t const& op_problem,
+    problem_category_t category,
+    f_t absolute_tolerance,
+    double time_limit);
+
+  void undo(rmm::device_uvector& primal_solution,
+            rmm::device_uvector& dual_solution,
+            rmm::device_uvector& reduced_costs,
+            problem_category_t category,
+            rmm::cuda_stream_view stream_view);
+
+ private:
+  papilo::PostsolveStorage post_solve_storage_;
+};
+
+}  // namespace cuopt::linear_programming::detail
diff --git a/cpp/src/mip/solve.cu b/cpp/src/mip/solve.cu
index 3835dadbc2..dfa80093ec 100644
--- a/cpp/src/mip/solve.cu
+++ b/cpp/src/mip/solve.cu
@@ -18,7 +18,7 @@
 #include 
 
 #include 
-#include 
+#include 
 #include 
 #include 
 #include 

From 3044d1596b2a8de9da39bc1bdbeb5facf1338fb6 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Mon, 11 Aug 2025 10:05:31 -0700
Subject: [PATCH 088/108] Fix build

---
 cpp/src/mip/presolve/third_party_presolve.cu | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/cpp/src/mip/presolve/third_party_presolve.cu b/cpp/src/mip/presolve/third_party_presolve.cu
index 4069c59cbc..2860a9c229 100644
--- a/cpp/src/mip/presolve/third_party_presolve.cu
+++ b/cpp/src/mip/presolve/third_party_presolve.cu
@@ -313,7 +313,8 @@ std::pair, bool> third_party_presolve_t(papilo_problem, true), result.status);
+  return std::make_pair(
+    build_optimization_problem(papilo_problem, op_problem.get_handle_ptr()), true);
 }
 
 template 

From 39196f6f04ac2641392cb5b421b2c507b7c216a1 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Mon, 11 Aug 2025 11:01:08 -0700
Subject: [PATCH 089/108] Move again to .cpp

---
 VERSION                                       |  2 +-
 ci/build_wheel_libcuopt.sh                    | 82 +++++++++----------
 cpp/src/linear_programming/solve.cu           |  2 +-
 cpp/src/mip/CMakeLists.txt                    |  2 +-
 ...y_presolve.cu => third_party_presolve.cpp} | 76 +++++++++++------
 ..._presolve.cuh => third_party_presolve.hpp} | 10 +--
 cpp/src/mip/solve.cu                          |  2 +-
 python/libcuopt/CMakeLists.txt                |  6 +-
 8 files changed, 105 insertions(+), 77 deletions(-)
 rename cpp/src/mip/presolve/{third_party_presolve.cu => third_party_presolve.cpp} (82%)
 rename cpp/src/mip/presolve/{third_party_presolve.cuh => third_party_presolve.hpp} (88%)

diff --git a/VERSION b/VERSION
index e9056eb367..29ee277606 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-25.10.00a134
\ No newline at end of file
+25.10.00a141
\ No newline at end of file
diff --git a/ci/build_wheel_libcuopt.sh b/ci/build_wheel_libcuopt.sh
index a62af9e984..25d2d47377 100755
--- a/ci/build_wheel_libcuopt.sh
+++ b/ci/build_wheel_libcuopt.sh
@@ -22,47 +22,47 @@ package_name="libcuopt"
 package_dir="python/libcuopt"
 
 # Install Boost and TBB
-#bash ci/utils/install_boost_tbb.sh
-
-BOOST_VERSION="1.85"
-BOOST_INSTALL_DIR="/opt/boost-${BOOST_VERSION}"
-BOOST_SRC_DIR="/tmp/boost"
-
-# Remove any existing Boost installation at the target location
-if [ -d "${BOOST_INSTALL_DIR}" ]; then
-    echo "Removing existing Boost at ${BOOST_INSTALL_DIR}"
-    rm -rf "${BOOST_INSTALL_DIR}"
-fi
-
-# Clean up any previous source directory
-if [ -d "${BOOST_SRC_DIR}" ]; then
-    echo "Removing previous Boost source at ${BOOST_SRC_DIR}"
-    rm -rf "${BOOST_SRC_DIR}"
-fi
-
-echo "Cloning Boost ${BOOST_VERSION}..."
-git clone --branch "boost-${BOOST_VERSION}.0" --depth 1 --recurse-submodules https://github.com/boostorg/boost.git "${BOOST_SRC_DIR}"
-
-pushd "${BOOST_SRC_DIR}"
-
-echo "Bootstrapping Boost..."
-./bootstrap.sh
-
-echo "Installing Boost to ${BOOST_INSTALL_DIR}..."
-./b2 install --prefix="${BOOST_INSTALL_DIR}" -j"$(nproc)" \
-    --with-filesystem \
-    --with-regex \
-    --with-log \
-    --with-thread \
-    --with-system \
-    --with-iostreams \
-    --with-serialization \
-    --with-program_options
-
-popd
-
-echo "Cleaning up Boost source directory..."
-rm -rf "${BOOST_SRC_DIR}"
+bash ci/utils/install_boost_tbb.sh
+
+# BOOST_VERSION="1.85"
+# BOOST_INSTALL_DIR="/opt/boost-${BOOST_VERSION}"
+# BOOST_SRC_DIR="/tmp/boost"
+
+# # Remove any existing Boost installation at the target location
+# if [ -d "${BOOST_INSTALL_DIR}" ]; then
+#     echo "Removing existing Boost at ${BOOST_INSTALL_DIR}"
+#     rm -rf "${BOOST_INSTALL_DIR}"
+# fi
+
+# # Clean up any previous source directory
+# if [ -d "${BOOST_SRC_DIR}" ]; then
+#     echo "Removing previous Boost source at ${BOOST_SRC_DIR}"
+#     rm -rf "${BOOST_SRC_DIR}"
+# fi
+
+# echo "Cloning Boost ${BOOST_VERSION}..."
+# git clone --branch "boost-${BOOST_VERSION}.0" --depth 1 --recurse-submodules https://github.com/boostorg/boost.git "${BOOST_SRC_DIR}"
+
+# pushd "${BOOST_SRC_DIR}"
+
+# echo "Bootstrapping Boost..."
+# ./bootstrap.sh
+
+# echo "Installing Boost to ${BOOST_INSTALL_DIR}..."
+# ./b2 install --prefix="${BOOST_INSTALL_DIR}" -j"$(nproc)" \
+#     --with-filesystem \
+#     --with-regex \
+#     --with-log \
+#     --with-thread \
+#     --with-system \
+#     --with-iostreams \
+#     --with-serialization \
+#     --with-program_options
+
+# popd
+
+# echo "Cleaning up Boost source directory..."
+# rm -rf "${BOOST_SRC_DIR}"
 
 export SKBUILD_CMAKE_ARGS="-DCUOPT_BUILD_WHEELS=ON;-DDISABLE_DEPRECATION_WARNING=ON"
 
diff --git a/cpp/src/linear_programming/solve.cu b/cpp/src/linear_programming/solve.cu
index 3bdf1b3b08..94ab9bfce9 100644
--- a/cpp/src/linear_programming/solve.cu
+++ b/cpp/src/linear_programming/solve.cu
@@ -25,7 +25,7 @@
 #include 
 
 #include 
-#include 
+#include 
 #include 
 #include 
 
diff --git a/cpp/src/mip/CMakeLists.txt b/cpp/src/mip/CMakeLists.txt
index 126aabfcbf..43c0072802 100644
--- a/cpp/src/mip/CMakeLists.txt
+++ b/cpp/src/mip/CMakeLists.txt
@@ -42,7 +42,7 @@ list(PREPEND
   ${CMAKE_CURRENT_SOURCE_DIR}/presolve/load_balanced_bounds_presolve.cu
   ${CMAKE_CURRENT_SOURCE_DIR}/presolve/multi_probe.cu
   ${CMAKE_CURRENT_SOURCE_DIR}/presolve/probing_cache.cu
-  ${CMAKE_CURRENT_SOURCE_DIR}/presolve/third_party_presolve.cu
+  ${CMAKE_CURRENT_SOURCE_DIR}/presolve/third_party_presolve.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/presolve/trivial_presolve.cu
   ${CMAKE_CURRENT_SOURCE_DIR}/problem/load_balanced_problem.cu
   ${CMAKE_CURRENT_SOURCE_DIR}/feasibility_jump/feasibility_jump.cu
diff --git a/cpp/src/mip/presolve/third_party_presolve.cu b/cpp/src/mip/presolve/third_party_presolve.cpp
similarity index 82%
rename from cpp/src/mip/presolve/third_party_presolve.cu
rename to cpp/src/mip/presolve/third_party_presolve.cpp
index 2860a9c229..d5359b1fc2 100644
--- a/cpp/src/mip/presolve/third_party_presolve.cu
+++ b/cpp/src/mip/presolve/third_party_presolve.cpp
@@ -16,13 +16,18 @@
  */
 
 #include 
-#include 
+#include 
 #include 
 #include 
-#include 
+// #include 
+#include 
+#include 
 
 namespace cuopt::linear_programming::detail {
 
+static papilo::PostsolveStorage post_solve_storage_;
+static int presolve_calls = 0;
+
 template 
 papilo::Problem build_papilo_problem(const optimization_problem_t& op_problem)
 {
@@ -54,17 +59,29 @@ papilo::Problem build_papilo_problem(const optimization_problem_t
   const auto& var_types    = op_problem.get_variable_types();
 
   // Copy data to host
-  std::vector h_coefficients = cuopt::host_copy(coefficients);
-  std::vector h_offsets      = cuopt::host_copy(offsets);
-  std::vector h_variables    = cuopt::host_copy(variables);
-  std::vector h_obj_coeffs   = cuopt::host_copy(obj_coeffs);
-  std::vector h_var_lb       = cuopt::host_copy(var_lb);
-  std::vector h_var_ub       = cuopt::host_copy(var_ub);
-  std::vector h_bounds       = cuopt::host_copy(bounds);
-  std::vector h_row_types   = cuopt::host_copy(row_types);
-  std::vector h_constr_lb    = cuopt::host_copy(constr_lb);
-  std::vector h_constr_ub    = cuopt::host_copy(constr_ub);
-  std::vector h_var_types  = cuopt::host_copy(var_types);
+  std::vector h_coefficients(coefficients.size());
+  auto stream_view = op_problem.get_handle_ptr()->get_stream();
+  raft::copy(h_coefficients.data(), coefficients.data(), coefficients.size(), stream_view);
+  std::vector h_offsets(offsets.size());
+  raft::copy(h_offsets.data(), offsets.data(), offsets.size(), stream_view);
+  std::vector h_variables(variables.size());
+  raft::copy(h_variables.data(), variables.data(), variables.size(), stream_view);
+  std::vector h_obj_coeffs(obj_coeffs.size());
+  raft::copy(h_obj_coeffs.data(), obj_coeffs.data(), obj_coeffs.size(), stream_view);
+  std::vector h_var_lb(var_lb.size());
+  raft::copy(h_var_lb.data(), var_lb.data(), var_lb.size(), stream_view);
+  std::vector h_var_ub(var_ub.size());
+  raft::copy(h_var_ub.data(), var_ub.data(), var_ub.size(), stream_view);
+  std::vector h_bounds(bounds.size());
+  raft::copy(h_bounds.data(), bounds.data(), bounds.size(), stream_view);
+  std::vector h_row_types(row_types.size());
+  raft::copy(h_row_types.data(), row_types.data(), row_types.size(), stream_view);
+  std::vector h_constr_lb(constr_lb.size());
+  raft::copy(h_constr_lb.data(), constr_lb.data(), constr_lb.size(), stream_view);
+  std::vector h_constr_ub(constr_ub.size());
+  raft::copy(h_constr_ub.data(), constr_ub.data(), constr_ub.size(), stream_view);
+  std::vector h_var_types(var_types.size());
+  raft::copy(h_var_types.data(), var_types.data(), var_types.size(), stream_view);
 
   auto constr_bounds_empty = h_constr_lb.empty() && h_constr_ub.empty();
   if (constr_bounds_empty) {
@@ -287,6 +304,10 @@ std::pair, bool> third_party_presolve_t papilo_problem = build_papilo_problem(op_problem);
 
   CUOPT_LOG_INFO("Unpresolved problem:: Num variables: %d Num constraints: %d, NNZ: %d",
@@ -305,6 +326,7 @@ std::pair, bool> third_party_presolve_t(op_problem.get_handle_ptr()), false);
   }
   post_solve_storage_ = result.postsolve;
@@ -324,9 +346,15 @@ void third_party_presolve_t::undo(rmm::device_uvector& primal_sol
                                             problem_category_t category,
                                             rmm::cuda_stream_view stream_view)
 {
-  auto primal_sol_vec_h    = cuopt::host_copy(primal_solution, stream_view);
-  auto dual_sol_vec_h      = cuopt::host_copy(dual_solution, stream_view);
-  auto reduced_costs_vec_h = cuopt::host_copy(reduced_costs, stream_view);
+  --presolve_calls;
+  cuopt_expects(
+    presolve_calls == 0, error_type_t::ValidationError, "Postsolve can only be called once");
+  std::vector primal_sol_vec_h(primal_solution.size());
+  raft::copy(primal_sol_vec_h.data(), primal_solution.data(), primal_solution.size(), stream_view);
+  std::vector dual_sol_vec_h(dual_solution.size());
+  raft::copy(dual_sol_vec_h.data(), dual_solution.data(), dual_solution.size(), stream_view);
+  std::vector reduced_costs_vec_h(reduced_costs.size());
+  raft::copy(reduced_costs_vec_h.data(), reduced_costs.data(), reduced_costs.size(), stream_view);
 
   papilo::Solution reduced_sol(primal_sol_vec_h);
   papilo::Solution full_sol;
@@ -343,14 +371,14 @@ void third_party_presolve_t::undo(rmm::device_uvector& primal_sol
   dual_solution.resize(full_sol.primal.size(), stream_view);
   reduced_costs.resize(full_sol.primal.size(), stream_view);
   raft::copy(primal_solution.data(), full_sol.primal.data(), full_sol.primal.size(), stream_view);
-  thrust::fill(rmm::exec_policy(stream_view),
-               dual_solution.data(),
-               dual_solution.data() + dual_solution.size(),
-               std::numeric_limits::signaling_NaN());
-  thrust::fill(rmm::exec_policy(stream_view),
-               reduced_costs.data(),
-               reduced_costs.data() + reduced_costs.size(),
-               std::numeric_limits::signaling_NaN());
+  // thrust::fill(rmm::exec_policy(stream_view),
+  //              dual_solution.data(),
+  //              dual_solution.data() + dual_solution.size(),
+  //              std::numeric_limits::signaling_NaN());
+  // thrust::fill(rmm::exec_policy(stream_view),
+  //              reduced_costs.data(),
+  //              reduced_costs.data() + reduced_costs.size(),
+  //              std::numeric_limits::signaling_NaN());
 }
 
 #if MIP_INSTANTIATE_FLOAT
diff --git a/cpp/src/mip/presolve/third_party_presolve.cuh b/cpp/src/mip/presolve/third_party_presolve.hpp
similarity index 88%
rename from cpp/src/mip/presolve/third_party_presolve.cuh
rename to cpp/src/mip/presolve/third_party_presolve.hpp
index 9eb9559618..a146a213c9 100644
--- a/cpp/src/mip/presolve/third_party_presolve.cuh
+++ b/cpp/src/mip/presolve/third_party_presolve.hpp
@@ -18,10 +18,10 @@
 #pragma once
 
 #include 
-#include 
-#include 
+// #include 
+// #include 
 
-#include 
+// #include 
 
 namespace cuopt::linear_programming::detail {
 
@@ -42,8 +42,8 @@ class third_party_presolve_t {
             problem_category_t category,
             rmm::cuda_stream_view stream_view);
 
- private:
-  papilo::PostsolveStorage post_solve_storage_;
+  //  private:
+  // papilo::PostsolveStorage post_solve_storage_;
 };
 
 }  // namespace cuopt::linear_programming::detail
diff --git a/cpp/src/mip/solve.cu b/cpp/src/mip/solve.cu
index dfa80093ec..3835dadbc2 100644
--- a/cpp/src/mip/solve.cu
+++ b/cpp/src/mip/solve.cu
@@ -18,7 +18,7 @@
 #include 
 
 #include 
-#include 
+#include 
 #include 
 #include 
 #include 
diff --git a/python/libcuopt/CMakeLists.txt b/python/libcuopt/CMakeLists.txt
index 531ba8505c..489a9dde95 100644
--- a/python/libcuopt/CMakeLists.txt
+++ b/python/libcuopt/CMakeLists.txt
@@ -46,9 +46,9 @@ FetchContent_Declare(
 )
 FetchContent_MakeAvailable(argparse)
 
-set(BOOST_ROOT "/opt/boost-1.85")
-find_package(Boost REQUIRED COMPONENTS filesystem regex log thread system iostreams serialization program_options)
-include_directories(${Boost_INCLUDE_DIRS})
+# set(BOOST_ROOT "/opt/boost-1.85")
+# find_package(Boost REQUIRED COMPONENTS filesystem regex log thread system iostreams serialization program_options)
+# include_directories(${Boost_INCLUDE_DIRS})
 
 set(TBB OFF CACHE BOOL "Disable TBB")
 set(BUILD_TESTING OFF CACHE BOOL "Disable test build for papilo")

From 168da19352fdf8b3cb94c1fcadb0ea8e951ee184 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Mon, 11 Aug 2025 13:44:42 -0700
Subject: [PATCH 090/108] Fix cpp tests

---
 cpp/src/mip/presolve/third_party_presolve.cpp | 13 +++--
 cpp/src/mip/solve.cu                          | 55 ++++++++++---------
 2 files changed, 35 insertions(+), 33 deletions(-)

diff --git a/cpp/src/mip/presolve/third_party_presolve.cpp b/cpp/src/mip/presolve/third_party_presolve.cpp
index d5359b1fc2..588cfe1982 100644
--- a/cpp/src/mip/presolve/third_party_presolve.cpp
+++ b/cpp/src/mip/presolve/third_party_presolve.cpp
@@ -26,7 +26,7 @@
 namespace cuopt::linear_programming::detail {
 
 static papilo::PostsolveStorage post_solve_storage_;
-static int presolve_calls = 0;
+static int presolve_calls_ = 0;
 
 template 
 papilo::Problem build_papilo_problem(const optimization_problem_t& op_problem)
@@ -305,8 +305,8 @@ std::pair, bool> third_party_presolve_t papilo_problem = build_papilo_problem(op_problem);
 
@@ -326,7 +326,7 @@ std::pair, bool> third_party_presolve_t(op_problem.get_handle_ptr()), false);
   }
   post_solve_storage_ = result.postsolve;
@@ -346,9 +346,10 @@ void third_party_presolve_t::undo(rmm::device_uvector& primal_sol
                                             problem_category_t category,
                                             rmm::cuda_stream_view stream_view)
 {
-  --presolve_calls;
+  --presolve_calls_;
   cuopt_expects(
-    presolve_calls == 0, error_type_t::ValidationError, "Postsolve can only be called once");
+    presolve_calls_ == 0, error_type_t::ValidationError, "Postsolve can only be called once");
+  if (primal_solution.is_empty()) { return; }
   std::vector primal_sol_vec_h(primal_solution.size());
   raft::copy(primal_sol_vec_h.data(), primal_solution.data(), primal_solution.size(), stream_view);
   std::vector dual_sol_vec_h(dual_solution.size());
diff --git a/cpp/src/mip/solve.cu b/cpp/src/mip/solve.cu
index 3835dadbc2..a852dc9d26 100644
--- a/cpp/src/mip/solve.cu
+++ b/cpp/src/mip/solve.cu
@@ -218,9 +218,9 @@ mip_solution_t solve_mip(optimization_problem_t& op_problem,
 
     auto sol = run_mip(problem, settings, timer);
 
-    auto status_to_skip = sol.get_termination_status() == mip_termination_status_t::TimeLimit ||
-                          sol.get_termination_status() == mip_termination_status_t::Infeasible;
-    if (run_presolve && !status_to_skip) {
+    if (run_presolve) {
+      auto status_to_skip = sol.get_termination_status() == mip_termination_status_t::TimeLimit ||
+                            sol.get_termination_status() == mip_termination_status_t::Infeasible;
       auto primal_solution =
         cuopt::device_copy(sol.get_solution(), op_problem.get_handle_ptr()->get_stream());
       rmm::device_uvector dual_solution(0, op_problem.get_handle_ptr()->get_stream());
@@ -230,31 +230,32 @@ mip_solution_t solve_mip(optimization_problem_t& op_problem,
                       reduced_costs,
                       cuopt::linear_programming::problem_category_t::MIP,
                       op_problem.get_handle_ptr()->get_stream());
-
-      thrust::fill(rmm::exec_policy(op_problem.get_handle_ptr()->get_stream()),
-                   dual_solution.data(),
-                   dual_solution.data() + dual_solution.size(),
-                   std::numeric_limits::signaling_NaN());
-      thrust::fill(rmm::exec_policy(op_problem.get_handle_ptr()->get_stream()),
-                   reduced_costs.data(),
-                   reduced_costs.data() + reduced_costs.size(),
-                   std::numeric_limits::signaling_NaN());
-      detail::problem_t full_problem(op_problem);
-      detail::solution_t full_sol(full_problem);
-      full_sol.copy_new_assignment(cuopt::host_copy(primal_solution));
-      full_sol.compute_feasibility();
-      if (!full_sol.get_feasible()) {
-        CUOPT_LOG_WARN("The solution is not feasible after post solve");
+      if (!status_to_skip) {
+        thrust::fill(rmm::exec_policy(op_problem.get_handle_ptr()->get_stream()),
+                     dual_solution.data(),
+                     dual_solution.data() + dual_solution.size(),
+                     std::numeric_limits::signaling_NaN());
+        thrust::fill(rmm::exec_policy(op_problem.get_handle_ptr()->get_stream()),
+                     reduced_costs.data(),
+                     reduced_costs.data() + reduced_costs.size(),
+                     std::numeric_limits::signaling_NaN());
+        detail::problem_t full_problem(op_problem);
+        detail::solution_t full_sol(full_problem);
+        full_sol.copy_new_assignment(cuopt::host_copy(primal_solution));
+        full_sol.compute_feasibility();
+        if (!full_sol.get_feasible()) {
+          CUOPT_LOG_WARN("The solution is not feasible after post solve");
+        }
+
+        auto full_stats = sol.get_stats();
+        // add third party presolve time to cuopt presolve time
+        full_stats.presolve_time += presolve_time;
+
+        // FIXME:: reduced_solution.get_stats() is not correct, we need to compute the stats for the
+        // full problem
+        full_sol.post_process_completed = true;  // hack
+        sol                             = full_sol.get_solution(true, full_stats);
       }
-
-      auto full_stats = sol.get_stats();
-      // add third party presolve time to cuopt presolve time
-      full_stats.presolve_time += presolve_time;
-
-      // FIXME:: reduced_solution.get_stats() is not correct, we need to compute the stats for the
-      // full problem
-      full_sol.post_process_completed = true;  // hack
-      sol                             = full_sol.get_solution(true, full_stats);
     }
 
     if (settings.sol_file != "") {

From 0f29fe5329749d78f236e426768f832a81d01695 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Mon, 11 Aug 2025 13:49:04 -0700
Subject: [PATCH 091/108] Install quadmath

---
 VERSION                        |  2 +-
 ci/build_wheel_libcuopt.sh     | 40 ----------------------------------
 ci/utils/install_boost_tbb.sh  |  4 ++--
 python/libcuopt/CMakeLists.txt | 10 +--------
 4 files changed, 4 insertions(+), 52 deletions(-)

diff --git a/VERSION b/VERSION
index 29ee277606..0612b27e6c 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-25.10.00a141
\ No newline at end of file
+25.10.00a143
\ No newline at end of file
diff --git a/ci/build_wheel_libcuopt.sh b/ci/build_wheel_libcuopt.sh
index 25d2d47377..2b3a8b0fb5 100755
--- a/ci/build_wheel_libcuopt.sh
+++ b/ci/build_wheel_libcuopt.sh
@@ -24,46 +24,6 @@ package_dir="python/libcuopt"
 # Install Boost and TBB
 bash ci/utils/install_boost_tbb.sh
 
-# BOOST_VERSION="1.85"
-# BOOST_INSTALL_DIR="/opt/boost-${BOOST_VERSION}"
-# BOOST_SRC_DIR="/tmp/boost"
-
-# # Remove any existing Boost installation at the target location
-# if [ -d "${BOOST_INSTALL_DIR}" ]; then
-#     echo "Removing existing Boost at ${BOOST_INSTALL_DIR}"
-#     rm -rf "${BOOST_INSTALL_DIR}"
-# fi
-
-# # Clean up any previous source directory
-# if [ -d "${BOOST_SRC_DIR}" ]; then
-#     echo "Removing previous Boost source at ${BOOST_SRC_DIR}"
-#     rm -rf "${BOOST_SRC_DIR}"
-# fi
-
-# echo "Cloning Boost ${BOOST_VERSION}..."
-# git clone --branch "boost-${BOOST_VERSION}.0" --depth 1 --recurse-submodules https://github.com/boostorg/boost.git "${BOOST_SRC_DIR}"
-
-# pushd "${BOOST_SRC_DIR}"
-
-# echo "Bootstrapping Boost..."
-# ./bootstrap.sh
-
-# echo "Installing Boost to ${BOOST_INSTALL_DIR}..."
-# ./b2 install --prefix="${BOOST_INSTALL_DIR}" -j"$(nproc)" \
-#     --with-filesystem \
-#     --with-regex \
-#     --with-log \
-#     --with-thread \
-#     --with-system \
-#     --with-iostreams \
-#     --with-serialization \
-#     --with-program_options
-
-# popd
-
-# echo "Cleaning up Boost source directory..."
-# rm -rf "${BOOST_SRC_DIR}"
-
 export SKBUILD_CMAKE_ARGS="-DCUOPT_BUILD_WHEELS=ON;-DDISABLE_DEPRECATION_WARNING=ON"
 
 # For pull requests we are enabling assert mode.
diff --git a/ci/utils/install_boost_tbb.sh b/ci/utils/install_boost_tbb.sh
index adbfcdf46d..a4833e3cbf 100644
--- a/ci/utils/install_boost_tbb.sh
+++ b/ci/utils/install_boost_tbb.sh
@@ -22,11 +22,11 @@ if [ -f /etc/os-release ]; then
     . /etc/os-release
     if [[ "$ID" == "rocky" ]]; then
         echo "Detected Rocky Linux. Installing Boost and TBB via dnf..."
-        dnf install -y boost-devel tbb-devel
+        dnf install -y boost-devel tbb-devel gcc-toolset-14-libquadmath-devel
     elif [[ "$ID" == "ubuntu" ]]; then
         echo "Detected Ubuntu. Installing Boost and TBB via apt..."
         apt-get update
-        apt-get install -y libboost-dev libtbb-dev
+        apt-get install -y libboost-dev libtbb-dev gcc-14 libquadmath-dev
     else
         echo "Unknown OS: $ID. Please install Boost development libraries manually."
         exit 1
diff --git a/python/libcuopt/CMakeLists.txt b/python/libcuopt/CMakeLists.txt
index 489a9dde95..a944000809 100644
--- a/python/libcuopt/CMakeLists.txt
+++ b/python/libcuopt/CMakeLists.txt
@@ -46,18 +46,10 @@ FetchContent_Declare(
 )
 FetchContent_MakeAvailable(argparse)
 
-# set(BOOST_ROOT "/opt/boost-1.85")
-# find_package(Boost REQUIRED COMPONENTS filesystem regex log thread system iostreams serialization program_options)
-# include_directories(${Boost_INCLUDE_DIRS})
-
 set(TBB OFF CACHE BOOL "Disable TBB")
 set(BUILD_TESTING OFF CACHE BOOL "Disable test build for papilo")
-# set(PAPILO_HAVE_BOOST_IOSTREAMS OFF)
-# set(PAPILO_COMMAND_LINE_AVAILABLE OFF)
-# set(PAPILO_SERIALIZATION_AVAILABLE OFF)
 set(PAPILO_NO_BINARIES ON)
-# option(LUSOL OFF)
-# option(QUADMATH OFF)
+option(LUSOL "Disable LUSOL" OFF)
 
 set(BUILD_TESTS OFF)
 set(BUILD_BENCHMARKS OFF)

From 0045358578df61a79dc0d0f65851bc9ffdf38c1c Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Mon, 11 Aug 2025 15:17:10 -0700
Subject: [PATCH 092/108] Only fetch for x86_64

---
 ci/utils/install_boost_tbb.sh | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/ci/utils/install_boost_tbb.sh b/ci/utils/install_boost_tbb.sh
index a4833e3cbf..4679d04143 100644
--- a/ci/utils/install_boost_tbb.sh
+++ b/ci/utils/install_boost_tbb.sh
@@ -22,7 +22,10 @@ if [ -f /etc/os-release ]; then
     . /etc/os-release
     if [[ "$ID" == "rocky" ]]; then
         echo "Detected Rocky Linux. Installing Boost and TBB via dnf..."
-        dnf install -y boost-devel tbb-devel gcc-toolset-14-libquadmath-devel
+        dnf install -y boost-devel tbb-devel
+        if [[ "$(uname -m)" == "x86_64" ]]; then
+            dnf install -y gcc-toolset-14-libquadmath-devel
+        fi
     elif [[ "$ID" == "ubuntu" ]]; then
         echo "Detected Ubuntu. Installing Boost and TBB via apt..."
         apt-get update

From 7d353f2b6a6c641dc9f2815809867e59f94eeb5c Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Mon, 11 Aug 2025 15:35:03 -0700
Subject: [PATCH 093/108] Only pull for rocky linux

---
 ci/utils/install_boost_tbb.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/ci/utils/install_boost_tbb.sh b/ci/utils/install_boost_tbb.sh
index 4679d04143..c3e1eb0bdc 100644
--- a/ci/utils/install_boost_tbb.sh
+++ b/ci/utils/install_boost_tbb.sh
@@ -29,7 +29,7 @@ if [ -f /etc/os-release ]; then
     elif [[ "$ID" == "ubuntu" ]]; then
         echo "Detected Ubuntu. Installing Boost and TBB via apt..."
         apt-get update
-        apt-get install -y libboost-dev libtbb-dev gcc-14 libquadmath-dev
+        apt-get install -y libboost-dev libtbb-dev
     else
         echo "Unknown OS: $ID. Please install Boost development libraries manually."
         exit 1

From 3d507dc9e62807cf834206231ce2d6103e9e480d Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Mon, 11 Aug 2025 16:04:04 -0700
Subject: [PATCH 094/108] Remove Tbb

---
 cpp/CMakeLists.txt          | 4 +---
 python/cuopt/CMakeLists.txt | 1 -
 2 files changed, 1 insertion(+), 4 deletions(-)

diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt
index fddbe2b25a..c7f6f50b47 100644
--- a/cpp/CMakeLists.txt
+++ b/cpp/CMakeLists.txt
@@ -172,10 +172,8 @@ FetchContent_Declare(
 
 set(BUILD_TESTING OFF CACHE BOOL "Disable test build for papilo")
 set(TBB OFF CACHE BOOL "Disable TBB")
-# set(PAPILO_HAVE_BOOST_IOSTREAMS OFF)
-# set(PAPILO_COMMAND_LINE_AVAILABLE OFF)
-# set(PAPILO_SERIALIZATION_AVAILABLE OFF)
 set(PAPILO_NO_BINARIES ON)
+option(LUSOL "Disable LUSOL" OFF)
 
 FetchContent_MakeAvailable(papilo)
 
diff --git a/python/cuopt/CMakeLists.txt b/python/cuopt/CMakeLists.txt
index ba19771726..097082d709 100644
--- a/python/cuopt/CMakeLists.txt
+++ b/python/cuopt/CMakeLists.txt
@@ -33,7 +33,6 @@ project(
 
 find_package(cuopt ${cuopt_version})
 find_package(mps_parser ${cuopt_version})
-find_package(TBB REQUIRED)
 
 include(rapids-cython-core)
 rapids_cython_init()

From 6c6d5e66f176789e6cf7620eab6a887fda905ec3 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Mon, 11 Aug 2025 16:45:58 -0700
Subject: [PATCH 095/108] Use bash for install

---
 ci/test_wheel_cuopt_server.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/ci/test_wheel_cuopt_server.sh b/ci/test_wheel_cuopt_server.sh
index 32144b63fc..21f78e0e81 100755
--- a/ci/test_wheel_cuopt_server.sh
+++ b/ci/test_wheel_cuopt_server.sh
@@ -20,7 +20,7 @@ set -eou pipefail
 source rapids-init-pip
 
 # Install Boost and TBB
-ci/utils/install_boost_tbb.sh
+bash ci/utils/install_boost_tbb.sh
 
 # Download the packages built in the previous step
 RAPIDS_PY_CUDA_SUFFIX="$(rapids-wheel-ctk-name-gen "${RAPIDS_CUDA_VERSION}")"

From ebf4b15250f5160a94fb75e2e57726e7ca114efc Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Mon, 11 Aug 2025 17:05:36 -0700
Subject: [PATCH 096/108] Fix cpp tests

---
 cpp/src/linear_programming/solve.cu           |  2 ++
 cpp/src/mip/presolve/third_party_presolve.cpp | 11 ++---------
 cpp/src/mip/presolve/third_party_presolve.hpp |  1 +
 cpp/src/mip/solve.cu                          |  1 +
 4 files changed, 6 insertions(+), 9 deletions(-)

diff --git a/cpp/src/linear_programming/solve.cu b/cpp/src/linear_programming/solve.cu
index 94ab9bfce9..efac90af76 100644
--- a/cpp/src/linear_programming/solve.cu
+++ b/cpp/src/linear_programming/solve.cu
@@ -641,11 +641,13 @@ optimization_problem_solution_t solve_lp(optimization_problem_tget_stream());
       auto reduced_costs =
         cuopt::device_copy(solution.get_reduced_cost(), op_problem.get_handle_ptr()->get_stream());
+      bool status_to_skip = false;
 
       presolver->undo(primal_solution,
                       dual_solution,
                       reduced_costs,
                       cuopt::linear_programming::problem_category_t::LP,
+                      status_to_skip,
                       op_problem.get_handle_ptr()->get_stream());
 
       thrust::fill(rmm::exec_policy(op_problem.get_handle_ptr()->get_stream()),
diff --git a/cpp/src/mip/presolve/third_party_presolve.cpp b/cpp/src/mip/presolve/third_party_presolve.cpp
index 588cfe1982..5117c64dcd 100644
--- a/cpp/src/mip/presolve/third_party_presolve.cpp
+++ b/cpp/src/mip/presolve/third_party_presolve.cpp
@@ -344,12 +344,13 @@ void third_party_presolve_t::undo(rmm::device_uvector& primal_sol
                                             rmm::device_uvector& dual_solution,
                                             rmm::device_uvector& reduced_costs,
                                             problem_category_t category,
+                                            bool status_to_skip,
                                             rmm::cuda_stream_view stream_view)
 {
   --presolve_calls_;
   cuopt_expects(
     presolve_calls_ == 0, error_type_t::ValidationError, "Postsolve can only be called once");
-  if (primal_solution.is_empty()) { return; }
+  if (status_to_skip) { return; }
   std::vector primal_sol_vec_h(primal_solution.size());
   raft::copy(primal_sol_vec_h.data(), primal_solution.data(), primal_solution.size(), stream_view);
   std::vector dual_sol_vec_h(dual_solution.size());
@@ -372,14 +373,6 @@ void third_party_presolve_t::undo(rmm::device_uvector& primal_sol
   dual_solution.resize(full_sol.primal.size(), stream_view);
   reduced_costs.resize(full_sol.primal.size(), stream_view);
   raft::copy(primal_solution.data(), full_sol.primal.data(), full_sol.primal.size(), stream_view);
-  // thrust::fill(rmm::exec_policy(stream_view),
-  //              dual_solution.data(),
-  //              dual_solution.data() + dual_solution.size(),
-  //              std::numeric_limits::signaling_NaN());
-  // thrust::fill(rmm::exec_policy(stream_view),
-  //              reduced_costs.data(),
-  //              reduced_costs.data() + reduced_costs.size(),
-  //              std::numeric_limits::signaling_NaN());
 }
 
 #if MIP_INSTANTIATE_FLOAT
diff --git a/cpp/src/mip/presolve/third_party_presolve.hpp b/cpp/src/mip/presolve/third_party_presolve.hpp
index a146a213c9..fa4394f2e7 100644
--- a/cpp/src/mip/presolve/third_party_presolve.hpp
+++ b/cpp/src/mip/presolve/third_party_presolve.hpp
@@ -40,6 +40,7 @@ class third_party_presolve_t {
             rmm::device_uvector& dual_solution,
             rmm::device_uvector& reduced_costs,
             problem_category_t category,
+            bool status_to_skip,
             rmm::cuda_stream_view stream_view);
 
   //  private:
diff --git a/cpp/src/mip/solve.cu b/cpp/src/mip/solve.cu
index a852dc9d26..0706c815c1 100644
--- a/cpp/src/mip/solve.cu
+++ b/cpp/src/mip/solve.cu
@@ -229,6 +229,7 @@ mip_solution_t solve_mip(optimization_problem_t& op_problem,
                       dual_solution,
                       reduced_costs,
                       cuopt::linear_programming::problem_category_t::MIP,
+                      status_to_skip,
                       op_problem.get_handle_ptr()->get_stream());
       if (!status_to_skip) {
         thrust::fill(rmm::exec_policy(op_problem.get_handle_ptr()->get_stream()),

From 401f375ea492a13cc38742cf0fc3e8497c9e5685 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Mon, 11 Aug 2025 22:23:01 -0700
Subject: [PATCH 097/108] Re-add logging option for get_solution

---
 cpp/cuopt_cli.cpp                             |  2 --
 cpp/src/mip/presolve/third_party_presolve.cpp |  5 +--
 cpp/src/mip/solution/solution.cu              | 36 ++++++++++---------
 cpp/src/mip/solution/solution.cuh             |  4 ++-
 cpp/src/mip/solve.cu                          |  7 ++--
 5 files changed, 26 insertions(+), 28 deletions(-)

diff --git a/cpp/cuopt_cli.cpp b/cpp/cuopt_cli.cpp
index a247f16db5..c717210a4d 100644
--- a/cpp/cuopt_cli.cpp
+++ b/cpp/cuopt_cli.cpp
@@ -131,14 +131,12 @@ int run_single_file(const std::string& file_path,
   try {
     if (is_mip && !solve_relaxation) {
       auto& mip_settings = settings.get_mip_settings();
-      std::cout << "MIP presolve: " << mip_settings.presolve << std::endl;
       if (initial_solution.size() > 0) {
         mip_settings.add_initial_solution(initial_solution.data(), initial_solution.size());
       }
       auto solution = cuopt::linear_programming::solve_mip(op_problem, mip_settings);
     } else {
       auto& lp_settings = settings.get_pdlp_settings();
-      std::cout << "LP presolve: " << lp_settings.presolve << std::endl;
       if (initial_solution.size() > 0) {
         lp_settings.set_initial_primal_solution(initial_solution.data(), initial_solution.size());
       }
diff --git a/cpp/src/mip/presolve/third_party_presolve.cpp b/cpp/src/mip/presolve/third_party_presolve.cpp
index 5117c64dcd..6f072c3f09 100644
--- a/cpp/src/mip/presolve/third_party_presolve.cpp
+++ b/cpp/src/mip/presolve/third_party_presolve.cpp
@@ -247,10 +247,7 @@ void check_presolve_status(const papilo::PresolveStatus& status)
 void check_postsolve_status(const papilo::PostsolveStatus& status)
 {
   switch (status) {
-    case papilo::PostsolveStatus::kOk:
-      CUOPT_LOG_INFO("Post-solve succeeded");
-      break;
-      // This occurs when the solution is not feasible
+    case papilo::PostsolveStatus::kOk: CUOPT_LOG_INFO("Post-solve succeeded"); break;
     case papilo::PostsolveStatus::kFailed: CUOPT_LOG_INFO("Post-solve failed"); break;
   }
 }
diff --git a/cpp/src/mip/solution/solution.cu b/cpp/src/mip/solution/solution.cu
index b3a7f6dbbf..cabb0edda0 100644
--- a/cpp/src/mip/solution/solution.cu
+++ b/cpp/src/mip/solution/solution.cu
@@ -562,7 +562,8 @@ f_t solution_t::compute_max_variable_violation()
 // returns the solution after applying the conversions
 template 
 mip_solution_t solution_t::get_solution(bool output_feasible,
-                                                            solver_stats_t stats)
+                                                            solver_stats_t stats,
+                                                            bool log_stats)
 {
   cuopt::default_logger().flush();
   cuopt_expects(
@@ -581,22 +582,23 @@ mip_solution_t solution_t::get_solution(bool output_feasible
     i_t num_nodes                    = stats.num_nodes;
     i_t num_simplex_iterations       = stats.num_simplex_iterations;
     handle_ptr->sync_stream();
-    CUOPT_LOG_INFO(
-      "Solution objective: %f , relative_mip_gap %f solution_bound %f presolve_time %f "
-      "total_solve_time %f "
-      "max constraint violation %f max int violation %f max var bounds violation %f "
-      "nodes %d simplex_iterations %d",
-      h_user_obj,
-      rel_mip_gap,
-      solution_bound,
-      presolve_time,
-      total_solve_time,
-      max_constraint_violation,
-      max_int_violation,
-      max_variable_bound_violation,
-      num_nodes,
-      num_simplex_iterations);
-
+    if (log_stats) {
+      CUOPT_LOG_INFO(
+        "Solution objective: %f , relative_mip_gap %f solution_bound %f presolve_time %f "
+        "total_solve_time %f "
+        "max constraint violation %f max int violation %f max var bounds violation %f "
+        "nodes %d simplex_iterations %d",
+        h_user_obj,
+        rel_mip_gap,
+        solution_bound,
+        presolve_time,
+        total_solve_time,
+        max_constraint_violation,
+        max_int_violation,
+        max_variable_bound_violation,
+        num_nodes,
+        num_simplex_iterations);
+    }
     const bool not_optimal = rel_mip_gap > problem_ptr->tolerances.relative_mip_gap &&
                              abs_mip_gap > problem_ptr->tolerances.absolute_mip_gap;
     auto term_reason =
diff --git a/cpp/src/mip/solution/solution.cuh b/cpp/src/mip/solution/solution.cuh
index 729a5c0e55..0774bdef2b 100644
--- a/cpp/src/mip/solution/solution.cuh
+++ b/cpp/src/mip/solution/solution.cuh
@@ -100,7 +100,9 @@ class solution_t {
   f_t get_total_excess();
   // brings all vars within bounds
   void clamp_within_bounds();
-  mip_solution_t get_solution(bool output_feasible, solver_stats_t stats);
+  mip_solution_t get_solution(bool output_feasible,
+                                        solver_stats_t stats,
+                                        bool log_stats = true);
   f_t compute_max_constraint_violation();
   f_t compute_max_int_violation();
   f_t compute_max_variable_violation();
diff --git a/cpp/src/mip/solve.cu b/cpp/src/mip/solve.cu
index 0706c815c1..7071c33fea 100644
--- a/cpp/src/mip/solve.cu
+++ b/cpp/src/mip/solve.cu
@@ -89,7 +89,7 @@ mip_solution_t run_mip(detail::problem_t& problem,
     solution.compute_objective();  // just to ensure h_user_obj is set
     auto stats           = solver_stats_t{};
     stats.solution_bound = solution.get_user_objective();
-    return solution.get_solution(true, stats);
+    return solution.get_solution(true, stats, false);
   }
   // problem contains unpreprocessed data
   detail::problem_t scaled_problem(problem);
@@ -144,8 +144,8 @@ mip_solution_t run_mip(detail::problem_t& problem,
       "please provide a more numerically stable problem.");
   }
 
-  auto sol = scaled_sol.get_solution(is_feasible_before_scaling || is_feasible_after_unscaling,
-                                     solver.get_solver_stats());
+  auto sol = scaled_sol.get_solution(
+    is_feasible_before_scaling || is_feasible_after_unscaling, solver.get_solver_stats(), false);
   detail::print_solution(scaled_problem.handle_ptr, sol.get_solution());
   return sol;
 }
@@ -263,7 +263,6 @@ mip_solution_t solve_mip(optimization_problem_t& op_problem,
       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.log_summary();
     return sol;
   } catch (const cuopt::logic_error& e) {
     CUOPT_LOG_ERROR("Error in solve_mip: %s", e.what());

From bdf09642fe8a1cbbb0c0525d60073da3995df505 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Tue, 12 Aug 2025 10:15:54 -0700
Subject: [PATCH 098/108] Review comments

---
 VERSION                                             | 2 +-
 ci/test_self_hosted_service.sh                      | 2 +-
 ci/test_wheel_cuopt.sh                              | 2 +-
 ci/test_wheel_cuopt_server.sh                       | 2 +-
 cpp/CMakeLists.txt                                  | 2 --
 cpp/src/mip/solver.cu                               | 2 +-
 dependencies.yaml                                   | 8 --------
 docs/cuopt/source/cuopt-c/lp-milp/lp-milp-c-api.rst | 9 ++-------
 python/libcuopt/CMakeLists.txt                      | 1 -
 9 files changed, 7 insertions(+), 23 deletions(-)

diff --git a/VERSION b/VERSION
index 0612b27e6c..296e35288d 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-25.10.00a143
\ No newline at end of file
+25.10.00
diff --git a/ci/test_self_hosted_service.sh b/ci/test_self_hosted_service.sh
index afb585a7c7..cd260d3914 100755
--- a/ci/test_self_hosted_service.sh
+++ b/ci/test_self_hosted_service.sh
@@ -20,7 +20,7 @@ set -euo pipefail
 source rapids-init-pip
 
 # Install Boost and TBB
-bash ci/utils/install_boost_tbb.sh
+# bash ci/utils/install_boost_tbb.sh
 
 # Download the cuopt built in the previous step
 RAPIDS_PY_CUDA_SUFFIX="$(rapids-wheel-ctk-name-gen "${RAPIDS_CUDA_VERSION}")"
diff --git a/ci/test_wheel_cuopt.sh b/ci/test_wheel_cuopt.sh
index 313baaed0d..34c4a85a7e 100755
--- a/ci/test_wheel_cuopt.sh
+++ b/ci/test_wheel_cuopt.sh
@@ -22,7 +22,7 @@ set -euo pipefail
 source rapids-init-pip
 
 # Install Boost and TBB
-bash ci/utils/install_boost_tbb.sh
+# bash ci/utils/install_boost_tbb.sh
 
 # Download the packages built in the previous step
 RAPIDS_PY_CUDA_SUFFIX="$(rapids-wheel-ctk-name-gen "${RAPIDS_CUDA_VERSION}")"
diff --git a/ci/test_wheel_cuopt_server.sh b/ci/test_wheel_cuopt_server.sh
index 21f78e0e81..15b324083e 100755
--- a/ci/test_wheel_cuopt_server.sh
+++ b/ci/test_wheel_cuopt_server.sh
@@ -20,7 +20,7 @@ set -eou pipefail
 source rapids-init-pip
 
 # Install Boost and TBB
-bash ci/utils/install_boost_tbb.sh
+# bash ci/utils/install_boost_tbb.sh
 
 # Download the packages built in the previous step
 RAPIDS_PY_CUDA_SUFFIX="$(rapids-wheel-ctk-name-gen "${RAPIDS_CUDA_VERSION}")"
diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt
index c7f6f50b47..7e9f8ef66a 100644
--- a/cpp/CMakeLists.txt
+++ b/cpp/CMakeLists.txt
@@ -161,8 +161,6 @@ else()
   find_package(RAFT REQUIRED)
 endif()
 
-#find_package(TBB REQUIRED)
-
 FetchContent_Declare(
   papilo
   GIT_REPOSITORY "https://github.com/scipopt/papilo.git"
diff --git a/cpp/src/mip/solver.cu b/cpp/src/mip/solver.cu
index 0f2117991f..0e8901685c 100644
--- a/cpp/src/mip/solver.cu
+++ b/cpp/src/mip/solver.cu
@@ -113,7 +113,7 @@ solution_t mip_solver_t::run_solver()
   dm.timer              = timer_;
   bool presolve_success = dm.run_presolve(timer_.remaining_time());
   if (!presolve_success) {
-    CUOPT_LOG_INFO("Problem proven infeasible in presolve");
+    CUOPT_LOG_INFO("Pdroblem proven infeasible in presolve");
     solution_t sol(*context.problem_ptr);
     sol.set_problem_fully_reduced();
     context.problem_ptr->post_process_solution(sol);
diff --git a/dependencies.yaml b/dependencies.yaml
index e7cae31fb6..25288f6431 100644
--- a/dependencies.yaml
+++ b/dependencies.yaml
@@ -136,7 +136,6 @@ files:
       key: requires
     includes:
       - build_common
-      - depends_on_papilo_deps
       - depends_on_libraft_headers
       - depends_on_librmm
       - depends_on_rapids_logger
@@ -148,7 +147,6 @@ files:
       table: project
     includes:
       - cuda_wheels
-      - depends_on_papilo_deps
       - depends_on_libraft_headers
       - depends_on_librmm
       - depends_on_rapids_logger
@@ -275,12 +273,6 @@ dependencies:
           - *rapids_build_backend
           - setuptools
           - wheel
-  depends_on_papilo_deps:
-    common:
-      - output_types: conda
-        packages:
-          - tbb-devel
-          - boost
   build_common:
     common:
       - output_types: [conda, requirements, pyproject]
diff --git a/docs/cuopt/source/cuopt-c/lp-milp/lp-milp-c-api.rst b/docs/cuopt/source/cuopt-c/lp-milp/lp-milp-c-api.rst
index d9cbe1806d..4743584d9f 100644
--- a/docs/cuopt/source/cuopt-c/lp-milp/lp-milp-c-api.rst
+++ b/docs/cuopt/source/cuopt-c/lp-milp/lp-milp-c-api.rst
@@ -153,21 +153,16 @@ These constants are used as parameter names in the :c:func:`cuOptSetParameter`,
 .. doxygendefine:: CUOPT_SAVE_BEST_PRIMAL_SO_FAR
 .. doxygendefine:: CUOPT_FIRST_PRIMAL_FEASIBLE
 .. doxygendefine:: CUOPT_LOG_FILE
-.. doxygendefine:: CUOPT_LOG_TO_CONSOLE
-.. doxygendefine:: CUOPT_CROSSOVER
-.. doxygendefine:: CUOPT_PRESOLVE
 .. doxygendefine:: CUOPT_MIP_ABSOLUTE_TOLERANCE
 .. doxygendefine:: CUOPT_MIP_RELATIVE_TOLERANCE
 .. doxygendefine:: CUOPT_MIP_INTEGRALITY_TOLERANCE
-.. doxygendefine:: CUOPT_MIP_ABSOLUTE_GAP
-.. doxygendefine:: CUOPT_MIP_RELATIVE_GAP
-.. doxygendefine:: CUOPT_MIP_HEURISTICS_ONLY
 .. doxygendefine:: CUOPT_MIP_SCALING
+.. doxygendefine:: CUOPT_MIP_HEURISTICS_ONLY
+.. doxygendefine:: CUOPT_PRESOLVE
 .. doxygendefine:: CUOPT_SOLUTION_FILE
 .. doxygendefine:: CUOPT_NUM_CPU_THREADS
 .. doxygendefine:: CUOPT_USER_PROBLEM_FILE
 
-
 .. _pdlp-solver-mode-constants:
 
 PDLP Solver Mode Constants
diff --git a/python/libcuopt/CMakeLists.txt b/python/libcuopt/CMakeLists.txt
index a944000809..b997708876 100644
--- a/python/libcuopt/CMakeLists.txt
+++ b/python/libcuopt/CMakeLists.txt
@@ -1,4 +1,3 @@
-
 # SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 # SPDX-License-Identifier: Apache-2.0
 #

From 37855d777fcdb8887aca21059fd21119fa411adf Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Tue, 12 Aug 2025 13:13:13 -0700
Subject: [PATCH 099/108] Review comments

---
 ci/test_self_hosted_service.sh | 3 ---
 ci/test_wheel_cuopt.sh         | 3 ---
 ci/test_wheel_cuopt_server.sh  | 3 ---
 cpp/src/mip/solver.cu          | 2 +-
 4 files changed, 1 insertion(+), 10 deletions(-)

diff --git a/ci/test_self_hosted_service.sh b/ci/test_self_hosted_service.sh
index cd260d3914..a5285ec1ca 100755
--- a/ci/test_self_hosted_service.sh
+++ b/ci/test_self_hosted_service.sh
@@ -19,9 +19,6 @@ set -euo pipefail
 
 source rapids-init-pip
 
-# Install Boost and TBB
-# bash ci/utils/install_boost_tbb.sh
-
 # Download the cuopt built in the previous step
 RAPIDS_PY_CUDA_SUFFIX="$(rapids-wheel-ctk-name-gen "${RAPIDS_CUDA_VERSION}")"
 CUOPT_MPS_PARSER_WHEELHOUSE=$(RAPIDS_PY_WHEEL_NAME="cuopt_mps_parser" rapids-download-wheels-from-github python)
diff --git a/ci/test_wheel_cuopt.sh b/ci/test_wheel_cuopt.sh
index 34c4a85a7e..7a16db43f1 100755
--- a/ci/test_wheel_cuopt.sh
+++ b/ci/test_wheel_cuopt.sh
@@ -21,9 +21,6 @@ set -euo pipefail
 # so those constraints will affect all future 'pip install' calls
 source rapids-init-pip
 
-# Install Boost and TBB
-# bash ci/utils/install_boost_tbb.sh
-
 # Download the packages built in the previous step
 RAPIDS_PY_CUDA_SUFFIX="$(rapids-wheel-ctk-name-gen "${RAPIDS_CUDA_VERSION}")"
 CUOPT_MPS_PARSER_WHEELHOUSE=$(RAPIDS_PY_WHEEL_NAME="cuopt_mps_parser" rapids-download-wheels-from-github python)
diff --git a/ci/test_wheel_cuopt_server.sh b/ci/test_wheel_cuopt_server.sh
index 15b324083e..7f999dba88 100755
--- a/ci/test_wheel_cuopt_server.sh
+++ b/ci/test_wheel_cuopt_server.sh
@@ -19,9 +19,6 @@ set -eou pipefail
 
 source rapids-init-pip
 
-# Install Boost and TBB
-# bash ci/utils/install_boost_tbb.sh
-
 # Download the packages built in the previous step
 RAPIDS_PY_CUDA_SUFFIX="$(rapids-wheel-ctk-name-gen "${RAPIDS_CUDA_VERSION}")"
 CUOPT_MPS_PARSER_WHEELHOUSE=$(RAPIDS_PY_WHEEL_NAME="cuopt_mps_parser" rapids-download-wheels-from-github python)
diff --git a/cpp/src/mip/solver.cu b/cpp/src/mip/solver.cu
index 0e8901685c..0f2117991f 100644
--- a/cpp/src/mip/solver.cu
+++ b/cpp/src/mip/solver.cu
@@ -113,7 +113,7 @@ solution_t mip_solver_t::run_solver()
   dm.timer              = timer_;
   bool presolve_success = dm.run_presolve(timer_.remaining_time());
   if (!presolve_success) {
-    CUOPT_LOG_INFO("Pdroblem proven infeasible in presolve");
+    CUOPT_LOG_INFO("Problem proven infeasible in presolve");
     solution_t sol(*context.problem_ptr);
     sol.set_problem_fully_reduced();
     context.problem_ptr->post_process_solution(sol);

From de006441cbf391ac22fdb28e23b693d64f129c43 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Tue, 12 Aug 2025 13:23:02 -0700
Subject: [PATCH 100/108] Remove dead code

---
 cpp/src/linear_programming/solve.cu           | 3 ---
 cpp/src/mip/presolve/third_party_presolve.hpp | 7 -------
 2 files changed, 10 deletions(-)

diff --git a/cpp/src/linear_programming/solve.cu b/cpp/src/linear_programming/solve.cu
index efac90af76..504d87bd3e 100644
--- a/cpp/src/linear_programming/solve.cu
+++ b/cpp/src/linear_programming/solve.cu
@@ -564,9 +564,6 @@ optimization_problem_solution_t solve_lp(optimization_problem_t::max() : settings.time_limit;
-
     // Create log stream for file logging and add it to default logger
     init_logger_t log(settings.log_file, settings.log_to_console);
 
diff --git a/cpp/src/mip/presolve/third_party_presolve.hpp b/cpp/src/mip/presolve/third_party_presolve.hpp
index fa4394f2e7..19a2c71ccb 100644
--- a/cpp/src/mip/presolve/third_party_presolve.hpp
+++ b/cpp/src/mip/presolve/third_party_presolve.hpp
@@ -18,10 +18,6 @@
 #pragma once
 
 #include 
-// #include 
-// #include 
-
-// #include 
 
 namespace cuopt::linear_programming::detail {
 
@@ -42,9 +38,6 @@ class third_party_presolve_t {
             problem_category_t category,
             bool status_to_skip,
             rmm::cuda_stream_view stream_view);
-
-  //  private:
-  // papilo::PostsolveStorage post_solve_storage_;
 };
 
 }  // namespace cuopt::linear_programming::detail

From 02f03843ac0961be83b3bf2f71897b2ea3642fc8 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Tue, 12 Aug 2025 15:48:39 -0700
Subject: [PATCH 101/108] Remove tbb

---
 ci/build_wheel_libcuopt.sh                          |  4 ++--
 ci/utils/{install_boost_tbb.sh => install_boost.sh} | 10 +++++-----
 conda/environments/all_cuda-128_arch-aarch64.yaml   |  1 -
 conda/environments/all_cuda-128_arch-x86_64.yaml    |  1 -
 conda/recipes/libcuopt/recipe.yaml                  |  5 -----
 dependencies.yaml                                   |  1 -
 6 files changed, 7 insertions(+), 15 deletions(-)
 rename ci/utils/{install_boost_tbb.sh => install_boost.sh} (82%)

diff --git a/ci/build_wheel_libcuopt.sh b/ci/build_wheel_libcuopt.sh
index 2b3a8b0fb5..7651b52f8c 100755
--- a/ci/build_wheel_libcuopt.sh
+++ b/ci/build_wheel_libcuopt.sh
@@ -21,8 +21,8 @@ source rapids-init-pip
 package_name="libcuopt"
 package_dir="python/libcuopt"
 
-# Install Boost and TBB
-bash ci/utils/install_boost_tbb.sh
+# Install Boost
+bash ci/utils/install_boost.sh
 
 export SKBUILD_CMAKE_ARGS="-DCUOPT_BUILD_WHEELS=ON;-DDISABLE_DEPRECATION_WARNING=ON"
 
diff --git a/ci/utils/install_boost_tbb.sh b/ci/utils/install_boost.sh
similarity index 82%
rename from ci/utils/install_boost_tbb.sh
rename to ci/utils/install_boost.sh
index c3e1eb0bdc..bd85d41e90 100644
--- a/ci/utils/install_boost_tbb.sh
+++ b/ci/utils/install_boost.sh
@@ -17,19 +17,19 @@
 
 set -euo pipefail
 
-# Install Boost and TBB
+# Install Boost
 if [ -f /etc/os-release ]; then
     . /etc/os-release
     if [[ "$ID" == "rocky" ]]; then
-        echo "Detected Rocky Linux. Installing Boost and TBB via dnf..."
-        dnf install -y boost-devel tbb-devel
+        echo "Detected Rocky Linux. Installing Boost via dnf..."
+        dnf install -y boost-devel
         if [[ "$(uname -m)" == "x86_64" ]]; then
             dnf install -y gcc-toolset-14-libquadmath-devel
         fi
     elif [[ "$ID" == "ubuntu" ]]; then
-        echo "Detected Ubuntu. Installing Boost and TBB via apt..."
+        echo "Detected Ubuntu. Installing Boost via apt..."
         apt-get update
-        apt-get install -y libboost-dev libtbb-dev
+        apt-get install -y libboost-dev
     else
         echo "Unknown OS: $ID. Please install Boost development libraries manually."
         exit 1
diff --git a/conda/environments/all_cuda-128_arch-aarch64.yaml b/conda/environments/all_cuda-128_arch-aarch64.yaml
index b8f8b94c11..ba6b6945e3 100644
--- a/conda/environments/all_cuda-128_arch-aarch64.yaml
+++ b/conda/environments/all_cuda-128_arch-aarch64.yaml
@@ -80,7 +80,6 @@ dependencies:
 - sphinxcontrib-openapi
 - sphinxcontrib-websupport
 - sysroot_linux-aarch64==2.28
-- tbb-devel
 - uvicorn==0.34.*
 - pip:
   - nvidia_sphinx_theme
diff --git a/conda/environments/all_cuda-128_arch-x86_64.yaml b/conda/environments/all_cuda-128_arch-x86_64.yaml
index c59d2ff982..f91c229570 100644
--- a/conda/environments/all_cuda-128_arch-x86_64.yaml
+++ b/conda/environments/all_cuda-128_arch-x86_64.yaml
@@ -80,7 +80,6 @@ dependencies:
 - sphinxcontrib-openapi
 - sphinxcontrib-websupport
 - sysroot_linux-64==2.28
-- tbb-devel
 - uvicorn==0.34.*
 - pip:
   - nvidia_sphinx_theme
diff --git a/conda/recipes/libcuopt/recipe.yaml b/conda/recipes/libcuopt/recipe.yaml
index 388d4aa87c..9924107d27 100644
--- a/conda/recipes/libcuopt/recipe.yaml
+++ b/conda/recipes/libcuopt/recipe.yaml
@@ -57,7 +57,6 @@ cache:
       - cuda-version =${{ cuda_version }}
       - cmake ${{ cmake_version }}
       - ninja
-      - tbb-devel
     host:
       - cpp-argparse
       - cuda-version =${{ cuda_version }}
@@ -71,7 +70,6 @@ cache:
       - libcusparse-dev
       - cuda-cudart-dev
       - boost
-      - tbb-devel
 
 outputs:
   - package:
@@ -137,14 +135,12 @@ outputs:
         - cuda-cudart-dev
         - libcublas
         - libcusparse-dev
-        - tbb-devel
       run:
         - ${{ pin_compatible("cuda-version", upper_bound="x", lower_bound="x") }}
         - ${{ pin_subpackage("libmps-parser", exact=True) }}
         - boost
         - librmm =${{ dep_minor_version }}
         - cuda-nvrtc
-        - tbb-devel
       ignore_run_exports:
         by_name:
           - cuda-cudart
@@ -189,7 +185,6 @@ outputs:
         - cuda-cudart-dev
         - libcublas
         - libcusparse-dev
-        - tbb-devel
       run:
         - gmock ${{ gtest_version }}
         - gtest ${{ gtest_version }}
diff --git a/dependencies.yaml b/dependencies.yaml
index 25288f6431..07de525333 100644
--- a/dependencies.yaml
+++ b/dependencies.yaml
@@ -313,7 +313,6 @@ dependencies:
           - cpp-argparse
           - librmm==25.10.*
           - libraft-headers==25.10.*
-          - tbb-devel
   test_cpp:
     common:
       - output_types: [conda]

From 98569defb2c2bb9ac01c6b9651c731d6d345ba6d Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Tue, 12 Aug 2025 16:11:44 -0700
Subject: [PATCH 102/108] Update doc

---
 benchmarks/linear_programming/run_mps_files.sh | 4 ++--
 cpp/cuopt_cli.cpp                              | 2 +-
 cpp/src/mip/solver_solution.cu                 | 4 ++--
 docs/cuopt/source/lp-milp-settings.rst         | 4 +---
 4 files changed, 6 insertions(+), 8 deletions(-)

diff --git a/benchmarks/linear_programming/run_mps_files.sh b/benchmarks/linear_programming/run_mps_files.sh
index 72d3a78dd1..b37882b465 100755
--- a/benchmarks/linear_programming/run_mps_files.sh
+++ b/benchmarks/linear_programming/run_mps_files.sh
@@ -31,7 +31,7 @@
 #   --mip-heuristics-only : Run mip heuristics only
 #   --write-log-file : Write log file
 #   --num-cpu-threads : Number of CPU threads to use
-#   --presolve : Enable presolve (default: true)
+#   --presolve : Enable presolve (default: true for MIP problems, false for LP problems)
 #   --batch-num : Batch number.  This allows to split the work across multiple batches uniformly when resources are limited.
 #   --n-batches : Number of batches
 #   --log-to-console : Log to console
@@ -75,7 +75,7 @@ Optional Arguments:
     --mip-heuristics-only  Run mip heuristics only
     --write-log-file   Write log file
     --num-cpu-threads  Number of CPU threads to use
-    --presolve         Enable presolve (default: true)
+    --presolve         Enable presolve (default: true for MIP problems, false for LP problems)
     --batch-num        Batch number
     --n-batches        Number of batches
     --log-to-console   Log to console
diff --git a/cpp/cuopt_cli.cpp b/cpp/cuopt_cli.cpp
index c717210a4d..ac22cf9fe2 100644
--- a/cpp/cuopt_cli.cpp
+++ b/cpp/cuopt_cli.cpp
@@ -195,7 +195,7 @@ int main(int argc, char* argv[])
     .implicit_value(true);
 
   program.add_argument("--presolve")
-    .help("enable/disable presolve (default: true)")
+    .help("enable/disable presolve (default: true for MIP problems, false for LP problems)")
     .default_value(true)
     .implicit_value(true);
 
diff --git a/cpp/src/mip/solver_solution.cu b/cpp/src/mip/solver_solution.cu
index 1e579d600c..f1f00dc804 100644
--- a/cpp/src/mip/solver_solution.cu
+++ b/cpp/src/mip/solver_solution.cu
@@ -232,15 +232,15 @@ void mip_solution_t::write_to_sol_file(std::string_view filename,
 template 
 void mip_solution_t::log_summary() const
 {
-  // CUOPT_LOG_INFO("Termination Status: {}", get_termination_status_string());
+  CUOPT_LOG_INFO("Termination Status: {}", get_termination_status_string());
   CUOPT_LOG_INFO("Objective Value: %f", get_objective_value());
   CUOPT_LOG_INFO("Max constraint violation: %f", get_max_constraint_violation());
   CUOPT_LOG_INFO("Max integer violation: %f", get_max_int_violation());
   CUOPT_LOG_INFO("Max variable bound violation: %f", get_max_variable_bound_violation());
   CUOPT_LOG_INFO("MIP Gap: %f", get_mip_gap());
   CUOPT_LOG_INFO("Solution Bound: %f", get_solution_bound());
-  CUOPT_LOG_INFO("Total Solve Time: %f", get_total_solve_time());
   CUOPT_LOG_INFO("Presolve Time: %f", get_presolve_time());
+  CUOPT_LOG_INFO("Total Solve Time: %f", get_total_solve_time());
 }
 
 #if MIP_INSTANTIATE_FLOAT
diff --git a/docs/cuopt/source/lp-milp-settings.rst b/docs/cuopt/source/lp-milp-settings.rst
index 64b61e7ff2..bb9541f4ab 100644
--- a/docs/cuopt/source/lp-milp-settings.rst
+++ b/docs/cuopt/source/lp-milp-settings.rst
@@ -63,7 +63,7 @@ Note: by default the number of CPU threads is automatically determined based on
 
 Presolve
 ^^^^^^^^
-``CUOPT_PRESOLVE`` controls whether presolve is enabled. Presolve can reduce problem size and improve solve time. Default is enabled.
+``CUOPT_PRESOLVE`` controls whether presolve is enabled. Presolve can reduce problem size and improve solve time. Enabled by default for MIP, disabled by default for LP.
 
 Linear Programming
 ------------------
@@ -324,5 +324,3 @@ If the Best Objective and the Dual Bound are both zero the gap is zero. If the b
 gap is infinity.
 
 Note: the default value is ``1e-4``.
-
-

From 5a1469cd12f6dc23473935b66cc711889c8e51fc Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Tue, 12 Aug 2025 16:18:52 -0700
Subject: [PATCH 103/108] Update logs

---
 cpp/src/mip/presolve/third_party_presolve.cpp | 28 +++++++++++--------
 1 file changed, 17 insertions(+), 11 deletions(-)

diff --git a/cpp/src/mip/presolve/third_party_presolve.cpp b/cpp/src/mip/presolve/third_party_presolve.cpp
index 6f072c3f09..3d87007564 100644
--- a/cpp/src/mip/presolve/third_party_presolve.cpp
+++ b/cpp/src/mip/presolve/third_party_presolve.cpp
@@ -229,17 +229,19 @@ void check_presolve_status(const papilo::PresolveStatus& status)
 {
   switch (status) {
     case papilo::PresolveStatus::kUnchanged:
-      CUOPT_LOG_INFO("Presolve did not result in any changes");
+      CUOPT_LOG_INFO("Presolve status:: did not result in any changes");
+      break;
+    case papilo::PresolveStatus::kReduced:
+      CUOPT_LOG_INFO("Presolve status:: reduced the problem");
       break;
-    case papilo::PresolveStatus::kReduced: CUOPT_LOG_INFO("Presolve reduced the problem"); break;
     case papilo::PresolveStatus::kUnbndOrInfeas:
-      CUOPT_LOG_INFO("Presolve found an unbounded or infeasible problem");
+      CUOPT_LOG_INFO("Presolve status:: found an unbounded or infeasible problem");
       break;
     case papilo::PresolveStatus::kInfeasible:
-      CUOPT_LOG_INFO("Presolve found an infeasible problem");
+      CUOPT_LOG_INFO("Presolve status:: found an infeasible problem");
       break;
     case papilo::PresolveStatus::kUnbounded:
-      CUOPT_LOG_INFO("Presolve found an unbounded problem");
+      CUOPT_LOG_INFO("Presolve status:: found an unbounded problem");
       break;
   }
 }
@@ -247,8 +249,8 @@ void check_presolve_status(const papilo::PresolveStatus& status)
 void check_postsolve_status(const papilo::PostsolveStatus& status)
 {
   switch (status) {
-    case papilo::PostsolveStatus::kOk: CUOPT_LOG_INFO("Post-solve succeeded"); break;
-    case papilo::PostsolveStatus::kFailed: CUOPT_LOG_INFO("Post-solve failed"); break;
+    case papilo::PostsolveStatus::kOk: CUOPT_LOG_INFO("Post-solve status:: succeeded"); break;
+    case papilo::PostsolveStatus::kFailed: CUOPT_LOG_INFO("Post-solve status:: failed"); break;
   }
 }
 
@@ -307,9 +309,9 @@ std::pair, bool> third_party_presolve_t papilo_problem = build_papilo_problem(op_problem);
 
-  CUOPT_LOG_INFO("Unpresolved problem:: Num variables: %d Num constraints: %d, NNZ: %d",
-                 papilo_problem.getNCols(),
+  CUOPT_LOG_INFO("Unpresolved problem:: %d constraints, %d variables, %d nonzeros",
                  papilo_problem.getNRows(),
+                 papilo_problem.getNCols(),
                  papilo_problem.getConstraintMatrix().getNnz());
 
   papilo::Presolve presolver;
@@ -327,9 +329,13 @@ std::pair, bool> third_party_presolve_t(op_problem.get_handle_ptr()), false);
   }
   post_solve_storage_ = result.postsolve;
-  CUOPT_LOG_INFO("Presolved problem:: Num variables: %d Num constraints: %d, NNZ: %d",
-                 papilo_problem.getNCols(),
+  CUOPT_LOG_INFO("Presolve removed:: %d constraints, %d variables, %d nonzeros",
+                 op_problem.get_n_constraints() - papilo_problem.getNRows(),
+                 op_problem.get_n_variables() - papilo_problem.getNCols(),
+                 op_problem.get_nnz() - papilo_problem.getConstraintMatrix().getNnz());
+  CUOPT_LOG_INFO("Presolved problem:: %d constraints, %d variables, %d nonzeros",
                  papilo_problem.getNRows(),
+                 papilo_problem.getNCols(),
                  papilo_problem.getConstraintMatrix().getNnz());
 
   return std::make_pair(

From faf0200876edd715647ac2767fffda1b49226cfe Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Tue, 12 Aug 2025 16:21:27 -0700
Subject: [PATCH 104/108] Remove boost build from docker file

---
 ci/docker/Dockerfile | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/ci/docker/Dockerfile b/ci/docker/Dockerfile
index 0015ac0dff..7810aa4de1 100644
--- a/ci/docker/Dockerfile
+++ b/ci/docker/Dockerfile
@@ -44,9 +44,6 @@ RUN apt-get update && apt-get install -y --no-install-recommends build-essential
     python${PYTHON_SHORT_VER} -m ensurepip --upgrade && \
     python${PYTHON_SHORT_VER} -m pip install --upgrade pip
 
-# Install Boost and TBB
-RUN bash ci/utils/install_boost_tbb.sh
-
 ENV DEBIAN_FRONTEND=""
 
 RUN ln -sf /usr/bin/python${PYTHON_SHORT_VER} /usr/bin/python && \

From b4900698899f8879edbe21f08df6cd541eb7040a Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Tue, 12 Aug 2025 16:37:53 -0700
Subject: [PATCH 105/108] Remove print

---
 cpp/tests/linear_programming/pdlp_test.cu | 1 -
 1 file changed, 1 deletion(-)

diff --git a/cpp/tests/linear_programming/pdlp_test.cu b/cpp/tests/linear_programming/pdlp_test.cu
index ec4edb80a0..dbeb5beda3 100644
--- a/cpp/tests/linear_programming/pdlp_test.cu
+++ b/cpp/tests/linear_programming/pdlp_test.cu
@@ -205,7 +205,6 @@ TEST(pdlp_class, run_sub_mittleman)
     const auto& name                    = entry.first;
     const auto expected_objective_value = entry.second;
 
-    std::cout << "Running " << name << std::endl;
     auto path = make_path_absolute("linear_programming/" + name + "/" + name + ".mps");
     cuopt::mps_parser::mps_data_model_t op_problem =
       cuopt::mps_parser::parse_mps(path);

From acae86933f3385463e1cbe6bcd167fea4f35ed11 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Wed, 13 Aug 2025 08:19:53 -0700
Subject: [PATCH 106/108] Move papilo to private

---
 cpp/CMakeLists.txt | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt
index 7e9f8ef66a..53071afaed 100644
--- a/cpp/CMakeLists.txt
+++ b/cpp/CMakeLists.txt
@@ -380,6 +380,7 @@ target_link_libraries(cuopt_cli
   PUBLIC
   cuopt
   OpenMP::OpenMP_CXX
+  PRIVATE
   papilo-core
 )
 set_property(TARGET cuopt_cli PROPERTY INSTALL_RPATH "$ORIGIN/../${lib_dir}")
@@ -401,6 +402,7 @@ if(BUILD_BENCHMARKS)
     PUBLIC
     cuopt
     OpenMP::OpenMP_CXX
+    PRIVATE
     papilo-core
   )
 endif()

From c953377fb4ed93b01f449fa3006c306c1beaad50 Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Wed, 13 Aug 2025 11:03:01 -0700
Subject: [PATCH 107/108] Update FAQ

---
 docs/cuopt/source/faq.rst | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/docs/cuopt/source/faq.rst b/docs/cuopt/source/faq.rst
index 881a8fe15f..4af4bdba13 100644
--- a/docs/cuopt/source/faq.rst
+++ b/docs/cuopt/source/faq.rst
@@ -350,6 +350,7 @@ Linear Programming FAQs
     - Tolerance: The set tolerance usually has a massive impact on performance. Try the lowest possible value using ``set_optimality_tolerance`` until you have reached your lowest possible acceptable accuracy.
     - PDLP Solver mode: PDLP solver mode will change the way PDLP internally optimizes the problem. The mode choice can drastically impact how fast a specific problem will be solved. You should test the different modes to see which one fits your problem best.
     - Batch mode: In case you know upfront that you need to solve multiple LP problems, instead of solving them sequentially, you should use the batch mode which can solve multiple LPs in parallel.
+    - Presolve: Presolve can reduce problem size and improve solve time.
 
 .. dropdown:: What solver mode should I choose?
 
@@ -369,6 +370,12 @@ Linear Programming FAQs
         - 10M rows/constraints, 10M columns/variables, and 2B non-zeros in the constraint matrix.
         - 74.5M rows/constraints, 74.5M columns/variables, and 1.49B non-zeros in the constraint matrix.
 
+.. dropdown:: Does cuOpt implement presolve reductions?
+
+    We use PaPILO presolve within cuOpt. It is enabled by default for MIP and disabled by default for LP.
+    For LP, dual postsolve is not supported, for this reason dual solution and reduced costs are filled with Nans.
+
+
 Mixed Integer Linear Programming FAQs
 --------------------------------------
 

From 69a5d2b8a3440a70c54629ccd94c3209546dcc5c Mon Sep 17 00:00:00 2001
From: Hugo Linsenmaier 
Date: Wed, 13 Aug 2025 11:03:35 -0700
Subject: [PATCH 108/108] Mention root node explicitely

---
 docs/cuopt/source/faq.rst | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/cuopt/source/faq.rst b/docs/cuopt/source/faq.rst
index 4af4bdba13..70ec008454 100644
--- a/docs/cuopt/source/faq.rst
+++ b/docs/cuopt/source/faq.rst
@@ -372,7 +372,7 @@ Linear Programming FAQs
 
 .. dropdown:: Does cuOpt implement presolve reductions?
 
-    We use PaPILO presolve within cuOpt. It is enabled by default for MIP and disabled by default for LP.
+    We use PaPILO presolve at the root node. It is enabled by default for MIP and disabled by default for LP.
     For LP, dual postsolve is not supported, for this reason dual solution and reduced costs are filled with Nans.