From 2dce2e045b78539842b94afafa48afc3ad1e81c5 Mon Sep 17 00:00:00 2001 From: "Nicolas L. Guidotti" Date: Mon, 10 Aug 2026 10:11:32 +0200 Subject: [PATCH 1/2] set Papilo back to the main branch Signed-off-by: Nicolas L. Guidotti --- cpp/CMakeLists.txt | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index a5c8acc655..ee39a429a8 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -270,14 +270,8 @@ endif () FetchContent_Declare( papilo - GIT_REPOSITORY "https://github.com/akifcorduk/papilo.git" - # We would want to get the main branch. However, the main branch - # does not have some of the presolvers and settings that we need - # Mainly, probing and clique merging. - # This is the reason we are using the development branch - # from Oct 12, 2025. Once these changes are merged into the main branch, - #we can switch to the main branch. - GIT_TAG "32b3a87dbf4955d5a2803be74145c389ea31434d" + GIT_REPOSITORY "https://github.com/scipopt/papilo.git" + GIT_TAG "2f29bb6e4c4217d7d915da7e2936cd4ee1dddec2" GIT_PROGRESS TRUE EXCLUDE_FROM_ALL SYSTEM From e2e764d5c54b58a58497d30869606a9ad217ba83 Mon Sep 17 00:00:00 2001 From: "Nicolas L. Guidotti" Date: Mon, 7 Sep 2026 11:31:27 +0200 Subject: [PATCH 2/2] update presolve test. Papilo not longer fully reduces ex9. Signed-off-by: Nicolas L. Guidotti --- .../unit_tests/presolve_test.cu | 46 ++++++++++++++----- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/cpp/tests/linear_programming/unit_tests/presolve_test.cu b/cpp/tests/linear_programming/unit_tests/presolve_test.cu index 92e962059d..d8a723b5a9 100644 --- a/cpp/tests/linear_programming/unit_tests/presolve_test.cu +++ b/cpp/tests/linear_programming/unit_tests/presolve_test.cu @@ -1020,10 +1020,8 @@ TEST_P(papilo_problem, round_trip) } // Exercises the MIP presolve path: presolver_t::apply -> third_party_presolve_t::apply -// reduces a user_problem_t in place via PaPILO. ex9 is fully solved by presolve (it collapses -// to a 0x0 problem), so this also checks the OPTIMAL status and that postsolve maps the empty -// reduced solution back to a full-dimension, objective-81 assignment. -TEST(submip_presolve, ex9_fully_reduced) +// reduces a user_problem_t in place via PaPILO. +TEST(submip_presolve, ex9_reduced_to_residual_row) { const raft::handle_t handle_{}; @@ -1044,17 +1042,43 @@ TEST(submip_presolve, ex9_fully_reduced) mip::third_party_presolve_t presolver; auto status = presolver.apply_to_subproblem(user_problem, settings, 120, 8); - // PaPILO solves ex9 entirely during presolve -> empty reduced problem. - EXPECT_EQ(status, mip::third_party_presolve_status_t::OPTIMAL); - EXPECT_EQ(user_problem.num_rows, 0); - EXPECT_EQ(user_problem.num_cols, 0); - EXPECT_EQ(user_problem.A.nnz(), 0); + ASSERT_EQ(status, mip::third_party_presolve_status_t::REDUCED); + ASSERT_EQ(user_problem.num_rows, 1); + ASSERT_EQ(user_problem.num_cols, 4); + ASSERT_EQ(user_problem.A.nnz(), 4); - // Postsolve reconstructs the full original assignment from the (empty) reduced solution. - std::vector reduced_solution; // no reduced columns remain + // The residual is `x0 + x1 + x2 + x3 == 1` over four binaries of unit cost, with the remaining + // 80 of the optimum already banked in obj_constant. + EXPECT_DOUBLE_EQ(user_problem.obj_constant, 80.0); + EXPECT_EQ(user_problem.row_sense[0], 'E'); + EXPECT_DOUBLE_EQ(user_problem.rhs[0], 1.0); + for (int j = 0; j < user_problem.num_cols; ++j) { + EXPECT_DOUBLE_EQ(user_problem.objective[j], 1.0) << "column " << j; + EXPECT_DOUBLE_EQ(user_problem.lower[j], 0.0) << "column " << j; + EXPECT_DOUBLE_EQ(user_problem.upper[j], 1.0) << "column " << j; + ASSERT_EQ(user_problem.A.col_start[j + 1] - user_problem.A.col_start[j], 1) << "column " << j; + EXPECT_EQ(user_problem.A.i[user_problem.A.col_start[j]], 0) << "column " << j; + EXPECT_DOUBLE_EQ(user_problem.A.x[user_problem.A.col_start[j]], 1.0) << "column " << j; + } + + // One column per surviving reduced column, each pointing at a distinct original column. + const auto& reduced_to_original = presolver.get_reduced_to_original_map(); + ASSERT_EQ(reduced_to_original.size(), user_problem.num_cols); + for (int j = 0; j < user_problem.num_cols; ++j) { + EXPECT_GE(reduced_to_original[j], 0) << "column " << j; + EXPECT_LT(reduced_to_original[j], orig_cols) << "column " << j; + } + + // Satisfying the residual row completes the optimum: postsolve reconstructs the full original + // assignment, keeping the reduced values in place and filling in everything presolve removed. + std::vector reduced_solution(user_problem.num_cols, 0.0); + reduced_solution[0] = 1.0; std::vector full_solution; presolver.uncrush_primal_solution(reduced_solution, full_solution); ASSERT_EQ(static_cast(full_solution.size()), orig_cols); + for (int j = 0; j < user_problem.num_cols; ++j) { + EXPECT_DOUBLE_EQ(full_solution[reduced_to_original[j]], reduced_solution[j]) << "column " << j; + } double objective = 0.0; for (int j = 0; j < orig_cols; ++j) {