From e1b24f2a93137d34fe1ac3b7db0a5808a757f611 Mon Sep 17 00:00:00 2001 From: Rajesh Gandham Date: Fri, 14 Aug 2026 15:38:37 -0700 Subject: [PATCH 1/5] Add objective gap termination criteria for QP --- cpp/src/barrier/barrier.cu | 51 +++++++++++-------- cpp/src/barrier/barrier.hpp | 1 + .../dual_simplex/simplex_solver_settings.hpp | 4 ++ cpp/src/pdlp/solve.cu | 1 + 4 files changed, 37 insertions(+), 20 deletions(-) diff --git a/cpp/src/barrier/barrier.cu b/cpp/src/barrier/barrier.cu index c164296a25..ef346eebc1 100644 --- a/cpp/src/barrier/barrier.cu +++ b/cpp/src/barrier/barrier.cu @@ -4012,13 +4012,16 @@ lp_status_t barrier_solver_t::check_for_suboptimal_solution( f_t& relative_primal_residual, f_t& relative_dual_residual, f_t& relative_complementarity_residual, + f_t& relative_objective_gap, lp_solution_t& solution) { raft::common::nvtx::range fun_scope("Barrier: check_for_suboptimal_solution"); + bool small_gap = (!data.has_cones() && data.Q.n == 0) || + relative_objective_gap < settings.barrier_relaxed_objective_gap_tol; if (relative_primal_residual < settings.barrier_relaxed_feasibility_tol && relative_dual_residual < settings.barrier_relaxed_optimality_tol && relative_complementarity_residual < settings.barrier_relaxed_complementarity_tol && - primal_objective == primal_objective) { + small_gap) { raft::copy(data.x.data(), data.d_x_.data(), data.d_x_.size(), stream_view_); raft::copy(data.y.data(), data.d_y_.data(), data.d_y_.size(), stream_view_); raft::copy(data.z.data(), data.d_z_.data(), data.d_z_.size(), stream_view_); @@ -4220,14 +4223,14 @@ lp_status_t barrier_solver_t::solve(f_t start_time, lp_solution_t upper(lp.upper); data.gather_upper_bounds(upper, data.restrict_u_); @@ -4236,11 +4239,12 @@ lp_status_t barrier_solver_t::solve(f_t start_time, lp_solution_t::solve(f_t start_time, lp_solution_t::solve(f_t start_time, lp_solution_t settings.time_limit) { @@ -4354,6 +4362,7 @@ lp_status_t barrier_solver_t::solve(f_t start_time, lp_solution_t::solve(f_t start_time, lp_solution_t::solve(f_t start_time, lp_solution_t::solve(f_t start_time, lp_solution_t::solve(f_t start_time, lp_solution_t& solution); const simplex::lp_problem_t& lp; diff --git a/cpp/src/dual_simplex/simplex_solver_settings.hpp b/cpp/src/dual_simplex/simplex_solver_settings.hpp index c7fe06ed4a..1ff9766e41 100644 --- a/cpp/src/dual_simplex/simplex_solver_settings.hpp +++ b/cpp/src/dual_simplex/simplex_solver_settings.hpp @@ -48,9 +48,11 @@ struct simplex_solver_settings_t { barrier_relative_feasibility_tol(1e-8), barrier_relative_optimality_tol(1e-8), barrier_relative_complementarity_tol(1e-8), + barrier_relative_objective_gap_tol(1e-6), barrier_relaxed_feasibility_tol(1e-4), barrier_relaxed_optimality_tol(1e-4), barrier_relaxed_complementarity_tol(1e-4), + barrier_relaxed_objective_gap_tol(1e-4), cut_off(std::numeric_limits::infinity()), steepest_edge_ratio(0.5), steepest_edge_primal_tol(1e-9), @@ -140,9 +142,11 @@ struct simplex_solver_settings_t { f_t barrier_relative_optimality_tol; // Relative optimality tolerance for barrier method f_t barrier_relative_complementarity_tol; // Relative complementarity tolerance for barrier method + f_t barrier_relative_objective_gap_tol; // Relative objective gap tolerance for barrier method f_t barrier_relaxed_feasibility_tol; // Relative feasibility tolerance for barrier method f_t barrier_relaxed_optimality_tol; // Relative optimality tolerance for barrier method f_t barrier_relaxed_complementarity_tol; // Relative complementarity tolerance for barrier method + f_t barrier_relaxed_objective_gap_tol; // Relative objective gap tolerance for barrier method f_t cut_off; // If the dual objective is greater than the cutoff we stop f_t steepest_edge_ratio; // the ratio of computed steepest edge mismatch from updated steepest edge diff --git a/cpp/src/pdlp/solve.cu b/cpp/src/pdlp/solve.cu index 80b3da2c18..1d6955a961 100644 --- a/cpp/src/pdlp/solve.cu +++ b/cpp/src/pdlp/solve.cu @@ -522,6 +522,7 @@ std::tuple, simplex::lp_status_t, f_t, f_t, f_t barrier_settings.barrier_relaxed_feasibility_tol = settings.tolerances.relative_primal_tolerance; barrier_settings.barrier_relaxed_optimality_tol = settings.tolerances.relative_dual_tolerance; barrier_settings.barrier_relaxed_complementarity_tol = settings.tolerances.relative_gap_tolerance; + barrier_settings.barrier_relaxed_objective_gap_tol = settings.tolerances.relative_gap_tolerance; if (barrier_settings.concurrent_halt != nullptr) { // Don't show the barrier log in concurrent mode. Show the PDLP log instead barrier_settings.log.log = false; From 269cbd97a296bc3e755783de719b9697eb97c0d0 Mon Sep 17 00:00:00 2001 From: Rajesh Gandham Date: Fri, 14 Aug 2026 18:34:55 -0700 Subject: [PATCH 2/5] Add objective gap reporting and checks in suboptimal cases --- cpp/src/barrier/barrier.cu | 49 ++++++++++++++++++++++++++++--------- cpp/src/barrier/barrier.hpp | 1 + 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/cpp/src/barrier/barrier.cu b/cpp/src/barrier/barrier.cu index ef346eebc1..de11f22a16 100644 --- a/cpp/src/barrier/barrier.cu +++ b/cpp/src/barrier/barrier.cu @@ -4009,6 +4009,7 @@ lp_status_t barrier_solver_t::check_for_suboptimal_solution( f_t& primal_residual_norm, f_t& dual_residual_norm, f_t& complementarity_residual_norm, + f_t& objective_gap, f_t& relative_primal_residual, f_t& relative_dual_residual, f_t& relative_complementarity_residual, @@ -4046,12 +4047,16 @@ lp_status_t barrier_solver_t::check_for_suboptimal_solution( settings.log.printf("Complementarity gap (abs/rel): %8.2e/%8.2e\n", complementarity_residual_norm, relative_complementarity_residual); + settings.log.printf( + "Objective gap (abs/rel): %8.2e/%8.2e\n", objective_gap, relative_objective_gap); settings.log.printf("\n"); return lp_status_t::OPTIMAL; // TODO: Barrier should probably have a separate suboptimal // status } f_t primal_objective_save = data.c.inner_product(data.x_save); + f_t dual_objective_save = + data.b.inner_product(data.y_save) - data.restrict_u_.inner_product(data.v_save); if (data.Q.n > 0) { dense_vector_t Qx_save(data.Q.n); dense_vector_t x_save_host(data.Q.n); @@ -4059,11 +4064,21 @@ lp_status_t barrier_solver_t::check_for_suboptimal_solution( matrix_vector_multiply(data.Q, 1.0, x_save_host, 0.0, Qx_save); f_t quad_objective = 0.5 * x_save_host.inner_product(Qx_save); primal_objective_save += quad_objective; + dual_objective_save -= quad_objective; } + f_t objective_gap_save = std::abs(primal_objective_save - dual_objective_save); + f_t user_primal_objective_save = compute_user_objective(lp, primal_objective_save); + f_t relative_objective_gap_save = + objective_gap_save / + (1.0 + std::min(std::abs(user_primal_objective_save), std::abs(primal_objective_save))); + bool small_gap_save = (!data.has_cones() && data.Q.n == 0) || + relative_objective_gap_save < settings.barrier_relaxed_objective_gap_tol; + if (data.relative_primal_residual_save < settings.barrier_relaxed_feasibility_tol && data.relative_dual_residual_save < settings.barrier_relaxed_optimality_tol && - data.relative_complementarity_residual_save < settings.barrier_relaxed_complementarity_tol) { + data.relative_complementarity_residual_save < settings.barrier_relaxed_complementarity_tol && + small_gap_save) { settings.log.printf("Restoring previous solution\n"); data.restore_saved_iterate(); data.to_solution(lp, @@ -4086,14 +4101,19 @@ lp_status_t barrier_solver_t::check_for_suboptimal_solution( settings.log.printf("Complementarity gap (abs/rel): %8.2e/%8.2e\n", data.complementarity_residual_norm_save, data.relative_complementarity_residual_save); + settings.log.printf("Objective gap (abs/rel): %8.2e/%8.2e\n", + objective_gap_save, + relative_objective_gap_save); settings.log.printf("\n"); return lp_status_t::OPTIMAL; // TODO: Barrier should probably have a separate suboptimal // status } else { - settings.log.printf("Primal residual %.2e dual residual %.2e complementarity residual %.2e\n", - relative_primal_residual, - relative_dual_residual, - relative_complementarity_residual); + settings.log.printf( + "Primal residual %.2e dual residual %.2e complementarity residual %.2e objective gap %.2e\n", + relative_primal_residual, + relative_dual_residual, + relative_complementarity_residual, + relative_objective_gap); } settings.log.printf("Search direction computation failed\n"); return lp_status_t::NUMERICAL_ISSUES; @@ -4241,10 +4261,9 @@ lp_status_t barrier_solver_t::solve(f_t start_time, lp_solution_t::solve(f_t start_time, lp_solution_t::solve(f_t start_time, lp_solution_t::solve(f_t start_time, lp_solution_t::solve(f_t start_time, lp_solution_t::solve(f_t start_time, lp_solution_t::solve(f_t start_time, lp_solution_t::solve(f_t start_time, lp_solution_t Date: Mon, 17 Aug 2026 14:50:24 -0700 Subject: [PATCH 3/5] Rename the relaxed objective tolerance --- cpp/src/barrier/barrier.cu | 9 +++++---- cpp/src/dual_simplex/simplex_solver_settings.hpp | 5 +++-- cpp/src/pdlp/solve.cu | 3 ++- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/cpp/src/barrier/barrier.cu b/cpp/src/barrier/barrier.cu index de11f22a16..cc0c00c36b 100644 --- a/cpp/src/barrier/barrier.cu +++ b/cpp/src/barrier/barrier.cu @@ -4018,7 +4018,7 @@ lp_status_t barrier_solver_t::check_for_suboptimal_solution( { raft::common::nvtx::range fun_scope("Barrier: check_for_suboptimal_solution"); bool small_gap = (!data.has_cones() && data.Q.n == 0) || - relative_objective_gap < settings.barrier_relaxed_objective_gap_tol; + relative_objective_gap < settings.barrier_relaxed_relative_objective_gap_tol; if (relative_primal_residual < settings.barrier_relaxed_feasibility_tol && relative_dual_residual < settings.barrier_relaxed_optimality_tol && relative_complementarity_residual < settings.barrier_relaxed_complementarity_tol && @@ -4072,8 +4072,9 @@ lp_status_t barrier_solver_t::check_for_suboptimal_solution( f_t relative_objective_gap_save = objective_gap_save / (1.0 + std::min(std::abs(user_primal_objective_save), std::abs(primal_objective_save))); - bool small_gap_save = (!data.has_cones() && data.Q.n == 0) || - relative_objective_gap_save < settings.barrier_relaxed_objective_gap_tol; + bool small_gap_save = + (!data.has_cones() && data.Q.n == 0) || + relative_objective_gap_save < settings.barrier_relaxed_relative_objective_gap_tol; if (data.relative_primal_residual_save < settings.barrier_relaxed_feasibility_tol && data.relative_dual_residual_save < settings.barrier_relaxed_optimality_tol && @@ -4288,7 +4289,7 @@ lp_status_t barrier_solver_t::solve(f_t start_time, lp_solution_t::infinity()), steepest_edge_ratio(0.5), steepest_edge_primal_tol(1e-9), @@ -146,7 +146,8 @@ struct simplex_solver_settings_t { f_t barrier_relaxed_feasibility_tol; // Relative feasibility tolerance for barrier method f_t barrier_relaxed_optimality_tol; // Relative optimality tolerance for barrier method f_t barrier_relaxed_complementarity_tol; // Relative complementarity tolerance for barrier method - f_t barrier_relaxed_objective_gap_tol; // Relative objective gap tolerance for barrier method + f_t barrier_relaxed_relative_objective_gap_tol; // Relative objective gap tolerance for barrier + // method f_t cut_off; // If the dual objective is greater than the cutoff we stop f_t steepest_edge_ratio; // the ratio of computed steepest edge mismatch from updated steepest edge diff --git a/cpp/src/pdlp/solve.cu b/cpp/src/pdlp/solve.cu index 1d6955a961..2ef1ea7874 100644 --- a/cpp/src/pdlp/solve.cu +++ b/cpp/src/pdlp/solve.cu @@ -522,7 +522,8 @@ std::tuple, simplex::lp_status_t, f_t, f_t, f_t barrier_settings.barrier_relaxed_feasibility_tol = settings.tolerances.relative_primal_tolerance; barrier_settings.barrier_relaxed_optimality_tol = settings.tolerances.relative_dual_tolerance; barrier_settings.barrier_relaxed_complementarity_tol = settings.tolerances.relative_gap_tolerance; - barrier_settings.barrier_relaxed_objective_gap_tol = settings.tolerances.relative_gap_tolerance; + barrier_settings.barrier_relaxed_relative_objective_gap_tol = + settings.tolerances.relative_gap_tolerance; if (barrier_settings.concurrent_halt != nullptr) { // Don't show the barrier log in concurrent mode. Show the PDLP log instead barrier_settings.log.log = false; From ed622ed91f19ded53d1650ea22e0e89bb232f4bc Mon Sep 17 00:00:00 2001 From: Rajesh Gandham Date: Mon, 17 Aug 2026 15:59:42 -0700 Subject: [PATCH 4/5] Adjust the tolerance to 1e-8 --- cpp/src/dual_simplex/simplex_solver_settings.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/dual_simplex/simplex_solver_settings.hpp b/cpp/src/dual_simplex/simplex_solver_settings.hpp index febd7298e7..469518e794 100644 --- a/cpp/src/dual_simplex/simplex_solver_settings.hpp +++ b/cpp/src/dual_simplex/simplex_solver_settings.hpp @@ -48,7 +48,7 @@ struct simplex_solver_settings_t { barrier_relative_feasibility_tol(1e-8), barrier_relative_optimality_tol(1e-8), barrier_relative_complementarity_tol(1e-8), - barrier_relative_objective_gap_tol(1e-6), + barrier_relative_objective_gap_tol(1e-8), barrier_relaxed_feasibility_tol(1e-4), barrier_relaxed_optimality_tol(1e-4), barrier_relaxed_complementarity_tol(1e-4), From 1fedd5aebdebb7d48e8eadb7c6cff30e791653a2 Mon Sep 17 00:00:00 2001 From: Rajesh Gandham Date: Tue, 18 Aug 2026 14:34:41 -0700 Subject: [PATCH 5/5] Revert the tolerance --- cpp/src/dual_simplex/simplex_solver_settings.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/dual_simplex/simplex_solver_settings.hpp b/cpp/src/dual_simplex/simplex_solver_settings.hpp index 469518e794..febd7298e7 100644 --- a/cpp/src/dual_simplex/simplex_solver_settings.hpp +++ b/cpp/src/dual_simplex/simplex_solver_settings.hpp @@ -48,7 +48,7 @@ struct simplex_solver_settings_t { barrier_relative_feasibility_tol(1e-8), barrier_relative_optimality_tol(1e-8), barrier_relative_complementarity_tol(1e-8), - barrier_relative_objective_gap_tol(1e-8), + barrier_relative_objective_gap_tol(1e-6), barrier_relaxed_feasibility_tol(1e-4), barrier_relaxed_optimality_tol(1e-4), barrier_relaxed_complementarity_tol(1e-4),