From 07bd5ea74552a96e4a2de24bd825678b8db80235 Mon Sep 17 00:00:00 2001 From: yuwenchen95 Date: Thu, 13 Aug 2026 09:08:27 -0700 Subject: [PATCH 1/2] Fix dual residual computation in final solution output Signed-off-by: yuwenchen95 --- cpp/src/barrier/barrier.cu | 1 + .../qp/unit_tests/lp_parser_solve_test.cu | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/cpp/src/barrier/barrier.cu b/cpp/src/barrier/barrier.cu index edc7a00d1d..4d9a2b7425 100644 --- a/cpp/src/barrier/barrier.cu +++ b/cpp/src/barrier/barrier.cu @@ -1374,6 +1374,7 @@ class iteration_data_t { dense_vector_t dual_res = z_tilde; dual_res.axpy(-1.0, lp.objective, 1.0); cusparse_view.transpose_spmv(1.0, solution.y, 1.0, dual_res); + if (Q.n > 0) { matrix_vector_multiply(Q, -1.0, x, 1.0, dual_res); } f_t dual_residual_norm = vector_norm_inf(dual_res, stream_view_); #ifdef PRINT_INFO settings_.log.printf("Solution Dual residual: %e\n", dual_residual_norm); diff --git a/cpp/tests/qp/unit_tests/lp_parser_solve_test.cu b/cpp/tests/qp/unit_tests/lp_parser_solve_test.cu index 2a37f43008..afc31fb833 100644 --- a/cpp/tests/qp/unit_tests/lp_parser_solve_test.cu +++ b/cpp/tests/qp/unit_tests/lp_parser_solve_test.cu @@ -206,4 +206,31 @@ End {4.0, 2.0}); } +// Regression test for issue #1598: l2_dual_residual dropped the Q*x term, +// so an optimal QP solution reported a large spurious dual residual instead +// of a value near zero. Reuses the qp_diagonal_only problem above. +// +// Minimize x1^2 + 4 x2^2 - 8 x1 - 16 x2 s.t. x1 + x2 >= 5, 0 <= x1, x2 <= 10. +// Optimum (4, 2), obj = -32. +TEST(lp_parser_solve, qp_diagonal_only_dual_residual) +{ + raft::handle_t handle; + auto problem = io::read_lp_from_string(R"LP( +Minimize + obj: -8 x1 - 16 x2 + [ 2 x1 ^ 2 + 8 x2 ^ 2 ] / 2 +Subject To + c1: x1 + x2 >= 5 +Bounds + 0 <= x1 <= 10 + 0 <= x2 <= 10 +End +)LP"); + auto settings = pdlp_solver_settings_t(); + auto solution = solve_lp(&handle, problem, settings); + + ASSERT_EQ(solution.get_termination_status(), pdlp_termination_status_t::Optimal); + EXPECT_NEAR(solution.get_objective_value(), -32.0, 1e-4); + EXPECT_NEAR(solution.get_additional_termination_information().l2_dual_residual, 0.0, 1e-4); +} + } // namespace cuopt::mathematical_optimization From 83578c0b3a1ad16c043ceaf44c6dee8abe1f3b16 Mon Sep 17 00:00:00 2001 From: yuwenchen95 Date: Thu, 13 Aug 2026 10:03:22 -0700 Subject: [PATCH 2/2] clean up comment for the test Signed-off-by: yuwenchen95 --- cpp/tests/qp/unit_tests/lp_parser_solve_test.cu | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/cpp/tests/qp/unit_tests/lp_parser_solve_test.cu b/cpp/tests/qp/unit_tests/lp_parser_solve_test.cu index afc31fb833..241a7cb1f7 100644 --- a/cpp/tests/qp/unit_tests/lp_parser_solve_test.cu +++ b/cpp/tests/qp/unit_tests/lp_parser_solve_test.cu @@ -206,16 +206,11 @@ End {4.0, 2.0}); } -// Regression test for issue #1598: l2_dual_residual dropped the Q*x term, -// so an optimal QP solution reported a large spurious dual residual instead -// of a value near zero. Reuses the qp_diagonal_only problem above. -// -// Minimize x1^2 + 4 x2^2 - 8 x1 - 16 x2 s.t. x1 + x2 >= 5, 0 <= x1, x2 <= 10. -// Optimum (4, 2), obj = -32. +// Dual residual check for QP. TEST(lp_parser_solve, qp_diagonal_only_dual_residual) { raft::handle_t handle; - auto problem = io::read_lp_from_string(R"LP( + auto problem = io::read_lp_from_string(R"LP( Minimize obj: -8 x1 - 16 x2 + [ 2 x1 ^ 2 + 8 x2 ^ 2 ] / 2 Subject To