From b86145ccfaea7b8accacdcd2ff34d933b2611219 Mon Sep 17 00:00:00 2001 From: Christopher Maes Date: Mon, 30 Mar 2026 16:53:37 -0700 Subject: [PATCH 1/8] Fix issue with infinite lower bounds and try to bound free variables in barrier --- cpp/src/dual_simplex/presolve.cpp | 173 ++++++++++++++++++++++++++++-- cpp/src/dual_simplex/presolve.hpp | 4 + 2 files changed, 170 insertions(+), 7 deletions(-) diff --git a/cpp/src/dual_simplex/presolve.cpp b/cpp/src/dual_simplex/presolve.cpp index b9ee419517..36deb5a61d 100644 --- a/cpp/src/dual_simplex/presolve.cpp +++ b/cpp/src/dual_simplex/presolve.cpp @@ -821,6 +821,145 @@ i_t presolve(const lp_problem_t& original, { problem = original; std::vector row_sense(problem.num_rows, '='); + // Check for free variables + i_t free_variables = 0; + for (i_t j = 0; j < problem.num_cols; j++) { + if (problem.lower[j] == -inf && problem.upper[j] == inf) { free_variables++; } + } + + if (settings.barrier_presolve && free_variables > 0) { + // Try to remove free variables + std::vector constraints_to_check; + std::vector current_free_variables; + std::vector row_marked(problem.num_rows, 0); + current_free_variables.reserve(problem.num_cols); + constraints_to_check.reserve(problem.num_rows); + for (i_t j = 0; j < problem.num_cols; j++) { + if (problem.lower[j] == -inf && problem.upper[j] == inf) { + current_free_variables.push_back(j); + const i_t col_start = problem.A.col_start[j]; + const i_t col_end = problem.A.col_start[j + 1]; + for (i_t p = col_start; p < col_end; p++) { + const i_t i = problem.A.i[p]; + if (row_marked[i] == 0) { + row_marked[i] = 1; + constraints_to_check.push_back(i); + } + } + } + } + + i_t removed_free_variables = 0; + + if (constraints_to_check.size() > 0) { + // Check if the constraints are feasible + csr_matrix_t Arow(0, 0, 0); + problem.A.to_compressed_row(Arow); + + // The constraints are in the form: + // sum_j a_j x_j = beta + for (i_t i : constraints_to_check) { + const i_t row_start = Arow.row_start[i]; + const i_t row_end = Arow.row_start[i + 1]; + f_t lower_activity_i = 0.0; + f_t upper_activity_i = 0.0; + i_t lower_inf_i = 0; + i_t upper_inf_i = 0; + i_t last_free_i = -1; + f_t last_free_coeff_i = 0.0; + for (i_t p = row_start; p < row_end; p++) { + const i_t j = Arow.j[p]; + const f_t aij = Arow.x[p]; + const f_t lower_j = problem.lower[j]; + const f_t upper_j = problem.upper[j]; + if (lower_j == -inf && upper_j == inf) { + last_free_i = j; + last_free_coeff_i = aij; + } + if (aij > 0) { + if (lower_j > -inf) { + lower_activity_i += aij * lower_j; + } else { + lower_inf_i++; + } + if (upper_j < inf) { + upper_activity_i += aij * upper_j; + } else { + upper_inf_i++; + } + } else { + if (upper_j < inf) { + lower_activity_i += aij * upper_j; + } else { + lower_inf_i++; + } + if (lower_j > -inf) { + upper_activity_i += aij * lower_j; + } else { + upper_inf_i++; + } + } + } + + if (last_free_i == -1) { continue; } + + // sum_j a_ij x_j == beta + + const f_t rhs = problem.rhs[i]; + // sum_{k != j} a_ik x_k + a_ij x_j == rhs + // Suppose that -inf < x_j < inf and all other variables x_k with k != j are bounded + // a_ij x_j == rhs - sum_{k != j} a_ik x_k + // So if a_ij > 0, we have + // x_j == 1/a_ij * (rhs - sum_{k != j} a_ik x_k) + // We can derive two bounds from this: + // x_j <= 1/a_ij * (rhs - lower_activity_i) and + // x_j >= 1/a_ij * (rhs - upper_activity_i) + + // If a_ij < 0, we have + // x_j == 1/a_ij * (rhs - sum_{k != j} a_ik x_k + // And we can derive two bounds from this: + // x_j >= 1/a_ij * (rhs - lower_activity_i) + // x_j <= 1/a_ij * (rhs - upper_activity_i) + const i_t j = last_free_i; + const f_t a_ij = last_free_coeff_i; + bool bounded = false; + if (a_ij > 0) { + if (lower_inf_i == 1) { + problem.upper[j] = 1.0 / a_ij * (rhs - lower_activity_i); + bounded = true; + } + if (upper_inf_i == 1) { + problem.lower[j] = 1.0 / a_ij * (rhs - upper_activity_i); + bounded = true; + } + } else if (a_ij < 0) { + if (lower_inf_i == 1) { + problem.lower[j] = 1.0 / a_ij * (rhs - lower_activity_i); + bounded = true; + } + if (upper_inf_i == 1) { + problem.upper[j] = 1.0 / a_ij * (rhs - upper_activity_i); + bounded = true; + } + } + + if (bounded) { + removed_free_variables++; + } + } + } + + i_t new_free_variables = 0; + for (i_t j = 0; j < problem.num_cols; j++) { + if (problem.lower[j] == -inf && problem.upper[j] == inf) { new_free_variables++; } + } + if (removed_free_variables != 0) { + settings.log.printf("Bounded %d free variables\n", removed_free_variables); + } + assert(new_free_variables == free_variables - removed_free_variables); + free_variables = new_free_variables; + } + // The original problem may have a variable without a lower bound // but a finite upper bound @@ -834,7 +973,25 @@ i_t presolve(const lp_problem_t& original, settings.log.printf("%d variables with no lower bound\n", no_lower_bound); } - // FIXME:: handle no lower bound case for barrier presolve + // Handle -inf < x_j <= u_j by substituting x'_j = -x_j, giving -u_j <= x'_j < inf + if (settings.barrier_presolve && no_lower_bound > 0) { + presolve_info.negated_variables.reserve(no_lower_bound); + for (i_t j = 0; j < problem.num_cols; j++) { + if (problem.lower[j] == -inf && problem.upper[j] < inf) { + presolve_info.negated_variables.push_back(j); + + problem.lower[j] = -problem.upper[j]; + problem.upper[j] = inf; + problem.objective[j] *= -1; + + const i_t col_start = problem.A.col_start[j]; + const i_t col_end = problem.A.col_start[j + 1]; + for (i_t p = col_start; p < col_end; p++) { + problem.A.x[p] *= -1.0; + } + } + } + } // The original problem may have nonzero lower bounds // 0 != l_j <= x_j <= u_j @@ -934,12 +1091,6 @@ i_t presolve(const lp_problem_t& original, remove_empty_cols(problem, num_empty_cols, presolve_info); } - // Check for free variables - i_t free_variables = 0; - for (i_t j = 0; j < problem.num_cols; j++) { - if (problem.lower[j] == -inf && problem.upper[j] == inf) { free_variables++; } - } - problem.Q.check_matrix("Before free variable expansion"); if (settings.barrier_presolve && free_variables > 0) { @@ -1504,6 +1655,14 @@ void uncrush_solution(const presolve_info_t& presolve_info, input_x[j] += presolve_info.removed_lower_bounds[j]; } } + + if (presolve_info.negated_variables.size() > 0) { + for (const i_t j : presolve_info.negated_variables) { + input_x[j] *= -1.0; + input_z[j] *= -1.0; + } + } + assert(uncrushed_x.size() == input_x.size()); assert(uncrushed_y.size() == input_y.size()); assert(uncrushed_z.size() == input_z.size()); diff --git a/cpp/src/dual_simplex/presolve.hpp b/cpp/src/dual_simplex/presolve.hpp index a068ed04ab..b317d459ac 100644 --- a/cpp/src/dual_simplex/presolve.hpp +++ b/cpp/src/dual_simplex/presolve.hpp @@ -202,6 +202,10 @@ struct presolve_info_t { std::vector removed_constraints; folding_info_t folding_info; + + std::vector new_slacks; + // Variables that were negated to handle -inf < x_j <= u_j + std::vector negated_variables; }; template From 06ae7550672a4c90f8b53fa37a7543126e2ea18a Mon Sep 17 00:00:00 2001 From: Rajesh Gandham Date: Wed, 1 Apr 2026 11:59:26 -0700 Subject: [PATCH 2/8] Handle the Q matrix in no lower bound presolve --- cpp/src/dual_simplex/presolve.cpp | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/cpp/src/dual_simplex/presolve.cpp b/cpp/src/dual_simplex/presolve.cpp index 36deb5a61d..659fec46eb 100644 --- a/cpp/src/dual_simplex/presolve.cpp +++ b/cpp/src/dual_simplex/presolve.cpp @@ -943,9 +943,7 @@ i_t presolve(const lp_problem_t& original, } } - if (bounded) { - removed_free_variables++; - } + if (bounded) { removed_free_variables++; } } } @@ -960,7 +958,6 @@ i_t presolve(const lp_problem_t& original, free_variables = new_free_variables; } - // The original problem may have a variable without a lower bound // but a finite upper bound // -inf < x_j <= u_j @@ -991,6 +988,24 @@ i_t presolve(const lp_problem_t& original, } } } + + // (1/2) x^T Q x with x = D x' (D_ii = -1 for negated columns) is (1/2) x'^T D Q D x'. + // One pass: Q'_{ik} = D_{ii} D_{kk} Q_{ik} — flip iff exactly one of {i,k} is negated. + if (problem.Q.n > 0 && !presolve_info.negated_variables.empty()) { + std::vector is_negated(static_cast(problem.num_cols), false); + for (i_t const j : presolve_info.negated_variables) { + is_negated[static_cast(j)] = true; + } + for (i_t row = 0; row < problem.Q.m; ++row) { + const i_t q_start = problem.Q.row_start[row]; + const i_t q_end = problem.Q.row_start[row + 1]; + const bool is_negated_row = is_negated[static_cast(row)]; + for (i_t p = q_start; p < q_end; ++p) { + const i_t col = problem.Q.j[p]; + if (is_negated_row != is_negated[static_cast(col)]) { problem.Q.x[p] *= -1.0; } + } + } + } } // The original problem may have nonzero lower bounds From 4432c2783d33abe8dcdb270740744f5fabe8f2db Mon Sep 17 00:00:00 2001 From: Chris Maes Date: Wed, 1 Apr 2026 12:32:40 -0700 Subject: [PATCH 3/8] Update presolve.hpp Remove `new_slacks` variable. --- cpp/src/dual_simplex/presolve.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/cpp/src/dual_simplex/presolve.hpp b/cpp/src/dual_simplex/presolve.hpp index b317d459ac..d570ea933e 100644 --- a/cpp/src/dual_simplex/presolve.hpp +++ b/cpp/src/dual_simplex/presolve.hpp @@ -203,7 +203,6 @@ struct presolve_info_t { folding_info_t folding_info; - std::vector new_slacks; // Variables that were negated to handle -inf < x_j <= u_j std::vector negated_variables; }; From d8054d19b80e098aba9589b6172a183f2a19d3b0 Mon Sep 17 00:00:00 2001 From: Christopher Maes Date: Thu, 2 Apr 2026 15:55:09 -0700 Subject: [PATCH 4/8] Fix a bug in complementarity residual. Finite bounds can only be so big. Use user object for relative complementarity gap. Initialize logger so we get feedback when reading large mps files --- cpp/cuopt_cli.cpp | 6 ++++++ cpp/src/barrier/barrier.cu | 21 +++++++++++-------- cpp/src/dual_simplex/presolve.cpp | 35 +++++++++++++++++++++---------- 3 files changed, 42 insertions(+), 20 deletions(-) diff --git a/cpp/cuopt_cli.cpp b/cpp/cuopt_cli.cpp index 899a3118b3..4a4479b9ba 100644 --- a/cpp/cuopt_cli.cpp +++ b/cpp/cuopt_cli.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -104,11 +105,15 @@ int run_single_file(const std::string& file_path, return -1; } + cuopt::init_logger_t log(settings.get_parameter(CUOPT_LOG_FILE), + settings.get_parameter(CUOPT_LOG_TO_CONSOLE)); + std::string base_filename = file_path.substr(file_path.find_last_of("/\\") + 1); constexpr bool input_mps_strict = false; cuopt::mps_parser::mps_data_model_t mps_data_model; bool parsing_failed = false; + auto timer = cuopt::timer_t(settings.get_parameter(CUOPT_TIME_LIMIT)); { CUOPT_LOG_INFO("Reading file %s", base_filename.c_str()); try { @@ -123,6 +128,7 @@ int run_single_file(const std::string& file_path, CUOPT_LOG_ERROR("Parsing MPS failed. Exiting!"); return -1; } + CUOPT_LOG_INFO("Read file %s in %.2f seconds", base_filename.c_str(), timer.elapsed_time()); // Determine memory backend and create problem using interface // Create handle only for GPU memory backend (avoid CUDA init on CPU-only hosts) diff --git a/cpp/src/barrier/barrier.cu b/cpp/src/barrier/barrier.cu index 075323744d..c765e2868a 100644 --- a/cpp/src/barrier/barrier.cu +++ b/cpp/src/barrier/barrier.cu @@ -2153,8 +2153,8 @@ void barrier_solver_t::gpu_compute_residual_norms(const rmm::device_uv dual_residual_norm = device_vector_norm_inf(data.d_dual_residual_, stream_view_); // TODO: CMM understand why rhs and not residual complementarity_residual_norm = - std::max(device_vector_norm_inf(data.d_complementarity_xz_rhs_, stream_view_), - device_vector_norm_inf(data.d_complementarity_wv_rhs_, stream_view_)); + std::max(device_vector_norm_inf(data.d_complementarity_xz_residual_, stream_view_), + device_vector_norm_inf(data.d_complementarity_wv_residual_, stream_view_)); } template @@ -3411,6 +3411,7 @@ lp_status_t barrier_solver_t::solve(f_t start_time, if (lp.Q.n > 0) { settings.log.printf("Quadratic objective matrix: %d nonzeros\n", lp.Q.row_start[lp.Q.n]); } + settings.log.printf("Objective constant %e\n", lp.obj_constant); settings.log.printf("\n"); // Compute the number of free variables @@ -3492,7 +3493,8 @@ lp_status_t barrier_solver_t::solve(f_t start_time, f_t relative_primal_residual = primal_residual_norm / (1.0 + norm_b); f_t relative_dual_residual = dual_residual_norm / (1.0 + norm_c); f_t relative_complementarity_residual = - complementarity_residual_norm / (1.0 + std::abs(primal_objective)); + complementarity_residual_norm / + (1.0 + std::abs(compute_user_objective(lp, primal_objective))); dense_vector_t upper(lp.upper); data.gather_upper_bounds(upper, data.restrict_u_); @@ -3508,11 +3510,11 @@ lp_status_t barrier_solver_t::solve(f_t start_time, float64_t elapsed_time = toc(start_time); settings.log.printf("%3d %+.12e %+.12e %.2e %.2e %.2e %.1f\n", iter, - primal_objective, - dual_objective, - primal_residual_norm, - dual_residual_norm, - complementarity_residual_norm, + compute_user_objective(lp, primal_objective), + compute_user_objective(lp, dual_objective), + relative_primal_residual, + relative_dual_residual, + relative_complementarity_residual, elapsed_time); bool converged = primal_residual_norm < settings.barrier_relative_feasibility_tol && @@ -3654,7 +3656,8 @@ lp_status_t barrier_solver_t::solve(f_t start_time, relative_primal_residual = primal_residual_norm / (1.0 + norm_b); relative_dual_residual = dual_residual_norm / (1.0 + norm_c); relative_complementarity_residual = - complementarity_residual_norm / (1.0 + std::abs(primal_objective)); + complementarity_residual_norm / + (1.0 + std::abs(compute_user_objective(lp, primal_objective))); if (relative_primal_residual < settings.barrier_relaxed_feasibility_tol && relative_dual_residual < settings.barrier_relaxed_optimality_tol && diff --git a/cpp/src/dual_simplex/presolve.cpp b/cpp/src/dual_simplex/presolve.cpp index 659fec46eb..a478c867c5 100644 --- a/cpp/src/dual_simplex/presolve.cpp +++ b/cpp/src/dual_simplex/presolve.cpp @@ -920,26 +920,39 @@ i_t presolve(const lp_problem_t& original, // And we can derive two bounds from this: // x_j >= 1/a_ij * (rhs - lower_activity_i) // x_j <= 1/a_ij * (rhs - upper_activity_i) - const i_t j = last_free_i; - const f_t a_ij = last_free_coeff_i; - bool bounded = false; + const i_t j = last_free_i; + const f_t a_ij = last_free_coeff_i; + const f_t max_bound = 1e10; + bool bounded = false; if (a_ij > 0) { if (lower_inf_i == 1) { - problem.upper[j] = 1.0 / a_ij * (rhs - lower_activity_i); - bounded = true; + const f_t new_upper = 1.0 / a_ij * (rhs - lower_activity_i); + if (new_upper < max_bound) { + problem.upper[j] = new_upper; + bounded = true; + } } if (upper_inf_i == 1) { - problem.lower[j] = 1.0 / a_ij * (rhs - upper_activity_i); - bounded = true; + const f_t new_lower = 1.0 / a_ij * (rhs - upper_activity_i); + if (new_lower > -max_bound) { + problem.lower[j] = new_lower; + bounded = true; + } } } else if (a_ij < 0) { if (lower_inf_i == 1) { - problem.lower[j] = 1.0 / a_ij * (rhs - lower_activity_i); - bounded = true; + const f_t new_lower = 1.0 / a_ij * (rhs - lower_activity_i); + if (new_lower > -max_bound) { + problem.lower[j] = new_lower; + bounded = true; + } } if (upper_inf_i == 1) { - problem.upper[j] = 1.0 / a_ij * (rhs - upper_activity_i); - bounded = true; + const f_t new_upper = 1.0 / a_ij * (rhs - upper_activity_i); + if (new_upper < max_bound) { + problem.upper[j] = new_upper; + bounded = true; + } } } From c7816fa66e4fc45cb4beb2f41c7e31ca35e8594d Mon Sep 17 00:00:00 2001 From: Christopher Maes Date: Thu, 2 Apr 2026 16:24:15 -0700 Subject: [PATCH 5/8] Only use a single implied bound to avoid an extra variable in barrier --- cpp/src/dual_simplex/presolve.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/cpp/src/dual_simplex/presolve.cpp b/cpp/src/dual_simplex/presolve.cpp index a478c867c5..c274922216 100644 --- a/cpp/src/dual_simplex/presolve.cpp +++ b/cpp/src/dual_simplex/presolve.cpp @@ -960,6 +960,19 @@ i_t presolve(const lp_problem_t& original, } } + for (i_t j : current_free_variables) { + if (problem.lower[j] > -inf && problem.upper[j] < inf) { + // We don't need two bounds. Pick the smallest one. + if (std::abs(problem.lower[j]) < std::abs(problem.upper[j])) { + // Restore the inf in the upper bound. Barrier will not require an additional w variable + problem.upper[j] = inf; + } else { + // Restores the -inf in the lower bound. Barrier will require an additional w variable + problem.lower[j] = -inf; + } + } + } + i_t new_free_variables = 0; for (i_t j = 0; j < problem.num_cols; j++) { if (problem.lower[j] == -inf && problem.upper[j] == inf) { new_free_variables++; } From db8b73a59770fc81ac52030a22eb2d84e912914d Mon Sep 17 00:00:00 2001 From: Christopher Maes Date: Fri, 3 Apr 2026 11:21:42 -0700 Subject: [PATCH 6/8] Change compl termination. Add obj gap termination --- cpp/src/barrier/barrier.cu | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/cpp/src/barrier/barrier.cu b/cpp/src/barrier/barrier.cu index c765e2868a..af1d82e3fc 100644 --- a/cpp/src/barrier/barrier.cu +++ b/cpp/src/barrier/barrier.cu @@ -3657,7 +3657,7 @@ lp_status_t barrier_solver_t::solve(f_t start_time, relative_dual_residual = dual_residual_norm / (1.0 + norm_c); relative_complementarity_residual = complementarity_residual_norm / - (1.0 + std::abs(compute_user_objective(lp, primal_objective))); + (1.0 + std::abs(primal_objective)); if (relative_primal_residual < settings.barrier_relaxed_feasibility_tol && relative_dual_residual < settings.barrier_relaxed_optimality_tol && @@ -3708,10 +3708,14 @@ lp_status_t barrier_solver_t::solve(f_t start_time, solution); } + f_t user_primal_objective = compute_user_objective(lp, primal_objective); + f_t user_dual_objective = compute_user_objective(lp, dual_objective); + f_t relative_obj_gap = std::abs(user_primal_objective - user_dual_objective) / (1.0 + std::abs(user_primal_objective)); + settings.log.printf("%3d %+.12e %+.12e %.2e %.2e %.2e %.1f\n", iter, - compute_user_objective(lp, primal_objective), - compute_user_objective(lp, dual_objective), + user_primal_objective, + user_dual_objective, relative_primal_residual, relative_dual_residual, relative_complementarity_residual, @@ -3719,10 +3723,12 @@ lp_status_t barrier_solver_t::solve(f_t start_time, bool primal_feasible = relative_primal_residual < settings.barrier_relative_feasibility_tol; bool dual_feasible = relative_dual_residual < settings.barrier_relative_optimality_tol; - bool small_gap = + bool small_compl = relative_complementarity_residual < settings.barrier_relative_complementarity_tol; + bool small_obj_gap = relative_obj_gap < settings.barrier_relative_complementarity_tol; + - converged = primal_feasible && dual_feasible && small_gap; + converged = primal_feasible && dual_feasible && small_compl && small_obj_gap; if (converged) { settings.log.printf("\n"); @@ -3755,7 +3761,7 @@ lp_status_t barrier_solver_t::solve(f_t start_time, ((!primal_feasible && relative_primal_residual > 100 * data.relative_primal_residual_save) || (!dual_feasible && relative_dual_residual > 100 * data.relative_dual_residual_save) || - (!small_gap && relative_complementarity_residual > + (!small_compl && relative_complementarity_residual > 10000 * data.relative_complementarity_residual_save))) { if (data.relative_primal_residual_save < settings.barrier_relaxed_feasibility_tol && data.relative_dual_residual_save < settings.barrier_relaxed_optimality_tol && From 9133db254727747fe38ba54e2560c84a0703011b Mon Sep 17 00:00:00 2001 From: Christopher Maes Date: Fri, 3 Apr 2026 15:11:48 -0700 Subject: [PATCH 7/8] Remove objective gap termination. Use user_obj and obj in relative compl --- cpp/src/barrier/barrier.cu | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/cpp/src/barrier/barrier.cu b/cpp/src/barrier/barrier.cu index af1d82e3fc..8d607f2dec 100644 --- a/cpp/src/barrier/barrier.cu +++ b/cpp/src/barrier/barrier.cu @@ -2151,7 +2151,6 @@ void barrier_solver_t::gpu_compute_residual_norms(const rmm::device_uv std::max(device_vector_norm_inf(data.d_primal_residual_, stream_view_), device_vector_norm_inf(data.d_bound_residual_, stream_view_)); dual_residual_norm = device_vector_norm_inf(data.d_dual_residual_, stream_view_); - // TODO: CMM understand why rhs and not residual complementarity_residual_norm = std::max(device_vector_norm_inf(data.d_complementarity_xz_residual_, stream_view_), device_vector_norm_inf(data.d_complementarity_wv_residual_, stream_view_)); @@ -3493,8 +3492,7 @@ lp_status_t barrier_solver_t::solve(f_t start_time, f_t relative_primal_residual = primal_residual_norm / (1.0 + norm_b); f_t relative_dual_residual = dual_residual_norm / (1.0 + norm_c); f_t relative_complementarity_residual = - complementarity_residual_norm / - (1.0 + std::abs(compute_user_objective(lp, primal_objective))); + complementarity_residual_norm / (1.0 + std::min(std::abs(compute_user_objective(lp, primal_objective)), std::abs(primal_objective))); dense_vector_t upper(lp.upper); data.gather_upper_bounds(upper, data.restrict_u_); @@ -3657,7 +3655,7 @@ lp_status_t barrier_solver_t::solve(f_t start_time, relative_dual_residual = dual_residual_norm / (1.0 + norm_c); relative_complementarity_residual = complementarity_residual_norm / - (1.0 + std::abs(primal_objective)); + (1.0 + std::min(std::abs(compute_user_objective(lp, primal_objective)), std::abs(primal_objective))); if (relative_primal_residual < settings.barrier_relaxed_feasibility_tol && relative_dual_residual < settings.barrier_relaxed_optimality_tol && @@ -3708,14 +3706,10 @@ lp_status_t barrier_solver_t::solve(f_t start_time, solution); } - f_t user_primal_objective = compute_user_objective(lp, primal_objective); - f_t user_dual_objective = compute_user_objective(lp, dual_objective); - f_t relative_obj_gap = std::abs(user_primal_objective - user_dual_objective) / (1.0 + std::abs(user_primal_objective)); - settings.log.printf("%3d %+.12e %+.12e %.2e %.2e %.2e %.1f\n", iter, - user_primal_objective, - user_dual_objective, + compute_user_objective(lp, primal_objective), + compute_user_objective(lp, dual_objective), relative_primal_residual, relative_dual_residual, relative_complementarity_residual, @@ -3723,12 +3717,10 @@ lp_status_t barrier_solver_t::solve(f_t start_time, bool primal_feasible = relative_primal_residual < settings.barrier_relative_feasibility_tol; bool dual_feasible = relative_dual_residual < settings.barrier_relative_optimality_tol; - bool small_compl = + bool small_gap = relative_complementarity_residual < settings.barrier_relative_complementarity_tol; - bool small_obj_gap = relative_obj_gap < settings.barrier_relative_complementarity_tol; - - converged = primal_feasible && dual_feasible && small_compl && small_obj_gap; + converged = primal_feasible && dual_feasible && small_gap; if (converged) { settings.log.printf("\n"); @@ -3761,7 +3753,7 @@ lp_status_t barrier_solver_t::solve(f_t start_time, ((!primal_feasible && relative_primal_residual > 100 * data.relative_primal_residual_save) || (!dual_feasible && relative_dual_residual > 100 * data.relative_dual_residual_save) || - (!small_compl && relative_complementarity_residual > + (!small_gap && relative_complementarity_residual > 10000 * data.relative_complementarity_residual_save))) { if (data.relative_primal_residual_save < settings.barrier_relaxed_feasibility_tol && data.relative_dual_residual_save < settings.barrier_relaxed_optimality_tol && From 178f934d43a9b3c2106dc33446f8ade9c1bbe88b Mon Sep 17 00:00:00 2001 From: Christopher Maes Date: Fri, 3 Apr 2026 23:08:02 -0700 Subject: [PATCH 8/8] Style fixes and remove obj constant print --- cpp/cuopt_cli.cpp | 3 +-- cpp/src/barrier/barrier.cu | 8 +++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/cpp/cuopt_cli.cpp b/cpp/cuopt_cli.cpp index a6dc9347de..ac568e07cf 100644 --- a/cpp/cuopt_cli.cpp +++ b/cpp/cuopt_cli.cpp @@ -93,7 +93,6 @@ int run_single_file(const std::string& file_path, bool solve_relaxation, cuopt::linear_programming::solver_settings_t& settings) { - cuopt::init_logger_t log(settings.get_parameter(CUOPT_LOG_FILE), settings.get_parameter(CUOPT_LOG_TO_CONSOLE)); @@ -102,7 +101,7 @@ int run_single_file(const std::string& file_path, constexpr bool input_mps_strict = false; cuopt::mps_parser::mps_data_model_t mps_data_model; bool parsing_failed = false; - auto timer = cuopt::timer_t(settings.get_parameter(CUOPT_TIME_LIMIT)); + auto timer = cuopt::timer_t(settings.get_parameter(CUOPT_TIME_LIMIT)); { CUOPT_LOG_INFO("Reading file %s", base_filename.c_str()); try { diff --git a/cpp/src/barrier/barrier.cu b/cpp/src/barrier/barrier.cu index 8d607f2dec..4da66abe77 100644 --- a/cpp/src/barrier/barrier.cu +++ b/cpp/src/barrier/barrier.cu @@ -3410,7 +3410,6 @@ lp_status_t barrier_solver_t::solve(f_t start_time, if (lp.Q.n > 0) { settings.log.printf("Quadratic objective matrix: %d nonzeros\n", lp.Q.row_start[lp.Q.n]); } - settings.log.printf("Objective constant %e\n", lp.obj_constant); settings.log.printf("\n"); // Compute the number of free variables @@ -3492,7 +3491,9 @@ lp_status_t barrier_solver_t::solve(f_t start_time, f_t relative_primal_residual = primal_residual_norm / (1.0 + norm_b); f_t relative_dual_residual = dual_residual_norm / (1.0 + norm_c); f_t relative_complementarity_residual = - complementarity_residual_norm / (1.0 + std::min(std::abs(compute_user_objective(lp, primal_objective)), std::abs(primal_objective))); + complementarity_residual_norm / + (1.0 + std::min(std::abs(compute_user_objective(lp, primal_objective)), + std::abs(primal_objective))); dense_vector_t upper(lp.upper); data.gather_upper_bounds(upper, data.restrict_u_); @@ -3655,7 +3656,8 @@ lp_status_t barrier_solver_t::solve(f_t start_time, relative_dual_residual = dual_residual_norm / (1.0 + norm_c); relative_complementarity_residual = complementarity_residual_norm / - (1.0 + std::min(std::abs(compute_user_objective(lp, primal_objective)), std::abs(primal_objective))); + (1.0 + std::min(std::abs(compute_user_objective(lp, primal_objective)), + std::abs(primal_objective))); if (relative_primal_residual < settings.barrier_relaxed_feasibility_tol && relative_dual_residual < settings.barrier_relaxed_optimality_tol &&