-
Notifications
You must be signed in to change notification settings - Fork 112
Add cuts to MIP solver #599
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
📝 WalkthroughWalkthroughAdds a comprehensive cut-generation subsystem, CSR-row utilities and sparse-vector helpers, bound-aware basis repair/refactor changes, basis append_cuts, extensive branch-and-bound and phase2 integrations, solver setting extensions, and multiple API signature updates across dual-simplex and MIP codepaths. Changes
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🧹 Nitpick comments (4)
cpp/src/dual_simplex/basis_updates.hpp (1)
294-295: New basis_update_mpf_t::append_cuts API looks consistentThe new
append_cutsdeclaration cleanly extends the basis update interface and matches the described usage fromsolve_linear_program_with_cuts. No header-level issues spotted; just ensure the implementation and callers clearly document/interpret the return code (0 vs error) consistently with otheri_t-returning methods.cpp/src/dual_simplex/sparse_matrix.hpp (1)
139-140: CSR append_rows API is reasonableAdding
csr_matrix_t::append_rowshere is a natural extension of the CSR interface and matches the intended use in the cut workflow. At the interface level it looks fine; just make sure callers only use it whenC.nmatchesthis->n(or clearly document if smallerC.nis intentionally supported).cpp/src/dual_simplex/sparse_vector.cpp (1)
31-44: CSR-row constructor is correct; consider adding a bounds assertThe implementation correctly builds a length‑
A.nsparse vector whose indices are the column indices from rowrowof the CSR matrix. To match existing defensive style (e.g., in other methods usingassertfor dimension checks), it would be helpful to add a guard:template <typename i_t, typename f_t> sparse_vector_t<i_t, f_t>::sparse_vector_t(const csr_matrix_t<i_t, f_t>& A, i_t row) { - const i_t row_start = A.row_start[row]; - const i_t row_end = A.row_start[row + 1]; + assert(row >= 0 && row + 1 <= A.m); + const i_t row_start = A.row_start[row]; + const i_t row_end = A.row_start[row + 1]; const i_t nz = row_end - row_start; n = A.n; i.reserve(nz); x.reserve(nz); for (i_t k = row_start; k < row_end; ++k) { i.push_back(A.j[k]); x.push_back(A.x[k]); } }cpp/src/dual_simplex/sparse_matrix.cpp (1)
360-400: append_rows implementation is sound; consider stricter dimension checksThe
append_rowslogic (row_start updates, contiguous copy ofC.j/C.x, andm/nz_maxupdates) looks correct and preserves CSR invariants for valid inputs.Two small improvements to consider:
- Enforce exact column compatibility instead of just
C.n > n:- if (C.n > n) { - return -1; - } + if (C.n != n) { + return -1; + }unless you explicitly intend to support
C.n < n.
- Document in the header that the function returns
0on success and-1on dimension mismatch, so callers know they must check the return value.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
cpp/src/dual_simplex/basis_updates.cpp(1 hunks)cpp/src/dual_simplex/basis_updates.hpp(1 hunks)cpp/src/dual_simplex/solve.cpp(2 hunks)cpp/src/dual_simplex/solve.hpp(1 hunks)cpp/src/dual_simplex/sparse_matrix.cpp(1 hunks)cpp/src/dual_simplex/sparse_matrix.hpp(1 hunks)cpp/src/dual_simplex/sparse_vector.cpp(1 hunks)cpp/src/dual_simplex/sparse_vector.hpp(1 hunks)cpp/tests/dual_simplex/unit_tests/solve.cpp(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (6)
cpp/src/dual_simplex/sparse_vector.cpp (2)
cpp/src/dual_simplex/sparse_vector.hpp (6)
sparse_vector_t(21-21)sparse_vector_t(23-23)sparse_vector_t(25-25)sparse_vector_t(27-27)sparse_vector_t(29-29)A(33-33)cpp/src/dual_simplex/sparse_matrix.hpp (4)
row(87-87)nz(78-78)x(70-70)x(74-74)
cpp/src/dual_simplex/sparse_vector.hpp (1)
cpp/src/dual_simplex/barrier.cu (2)
A(912-1172)A(912-917)
cpp/src/dual_simplex/sparse_matrix.cpp (1)
cpp/src/dual_simplex/sparse_matrix.hpp (1)
C(139-139)
cpp/tests/dual_simplex/unit_tests/solve.cpp (2)
cpp/src/dual_simplex/presolve.cpp (3)
convert_user_problem(773-1000)convert_user_problem(773-777)convert_user_problem(1558-1563)cpp/src/dual_simplex/solve.cpp (7)
basic_list(113-113)solve_linear_program_with_advanced_basis(128-294)solve_linear_program_with_advanced_basis(128-137)solve_linear_program_with_advanced_basis(803-812)solve_linear_program_with_cuts(297-444)solve_linear_program_with_cuts(297-307)solve_linear_program_with_cuts(814-825)
cpp/src/dual_simplex/solve.cpp (2)
cpp/src/dual_simplex/phase2.cpp (6)
settings(2119-2141)settings(2119-2119)basic_list(2176-2176)dual_phase2_with_advanced_basis(2197-2984)dual_phase2_with_advanced_basis(2197-2209)dual_phase2_with_advanced_basis(2999-3012)cpp/src/dual_simplex/phase2.hpp (1)
dual_phase2_with_advanced_basis(61-73)
cpp/src/dual_simplex/basis_updates.cpp (2)
cpp/src/dual_simplex/basis_updates.hpp (22)
cuts_basic(294-294)L0_(116-120)L0_(367-371)rhs(59-59)rhs(62-62)rhs(66-68)rhs(72-74)rhs(77-77)rhs(79-80)rhs(83-83)rhs(86-86)rhs(89-89)rhs(92-92)rhs(95-95)rhs(98-98)rhs(101-101)rhs(104-104)rhs(307-307)rhs(308-308)nz(149-149)nz(397-397)nz(411-411)cpp/src/dual_simplex/vector_math.cpp (3)
inverse_permutation(154-162)inverse_permutation(154-154)inverse_permutation(206-206)
🔇 Additional comments (1)
cpp/src/dual_simplex/sparse_vector.hpp (1)
28-29: CSR-row constructor declaration is consistentThe added constructor from a CSR row is a good complement to the existing CSC-column constructor and fits the class API. No issues at the declaration level.
|
@chris-maes Aspirational for 25.12? |
|
This does not need to be part of 25.12. |
…eursitics and MIP presolve for experimentation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 9
♻️ Duplicate comments (1)
cpp/src/dual_simplex/basis_updates.cpp (1)
1172-1172: Add template arguments tosparse_vector_tconstruction.As previously noted,
sparse_vector_tis a class template and requires explicit template arguments since there's no deduction guide for the(WT, h)constructor.- sparse_vector_t rhs(WT, h); + sparse_vector_t<i_t, f_t> rhs(WT, h);
🧹 Nitpick comments (6)
cpp/src/dual_simplex/sparse_matrix.cpp (1)
402-427: Consider adding bounds validation for the sparse vector.Unlike
append_rows, this method doesn't validate that the sparse vector's indices are within the matrix's column bounds. Consider adding validation similar to line 367-369 inappend_rows:template <typename i_t, typename f_t> i_t csr_matrix_t<i_t, f_t>::append_row(const sparse_vector_t<i_t, f_t>& c) { + // Validate column indices are within bounds + for (i_t k = 0; k < c.i.size(); k++) { + if (c.i[k] >= this->n) { + return -1; + } + } const i_t old_m = this->m;Alternatively, if callers are trusted, an
assertwould suffice for debug builds.cpp/src/dual_simplex/branch_and_bound.cpp (3)
1131-1161: Debug logging should be gated by a compile-time flag or log level.These logging statements print detailed information about every integer variable and fractional variable. Consider wrapping them in a debug macro or using a debug log level.
if (num_fractional == 0) { +#ifdef PRINT_INTEGER_VARS for (i_t j = 0; j < original_lp_.num_cols; j++) { if (var_types_[j] == variable_type_t::INTEGER) { settings_.log.printf("Variable %d type %d val %e\n", j, var_types_[j], root_relax_soln_.x[j]); } } +#endif // ... rest of optimal case } else { - settings_.log.printf("Found %d fractional variables on cut pass %d\n", num_fractional, cut_pass); - for (i_t j: fractional) { - settings_.log.printf("Fractional variable %d lower %e value %e upper %e\n", j, original_lp_.lower[j], root_relax_soln_.x[j], original_lp_.upper[j]); - } + settings_.log.debug("Found %d fractional variables on cut pass %d\n", num_fractional, cut_pass);
1237-1249: Debug assertion withexit(1)should use assert or be removed.This validation block checks that B^T * u_bar = e_i. While important for debugging, using
exit(1)in production code is inappropriate.+#ifndef NDEBUG std::vector<f_t> BTu_bar(original_lp_.num_rows); b_transpose_multiply(original_lp_, basic_list, u_bar_dense, BTu_bar); for (i_t k = 0; k < original_lp_.num_rows; k++) { if (k == i) { - if (std::abs(BTu_bar[k] - 1.0) > 1e-6) { - settings_.log.printf("BTu_bar[%d] = %e i %d\n", k, BTu_bar[k], i); - exit(1); - } + assert(std::abs(BTu_bar[k] - 1.0) <= 1e-6 && "BTu_bar diagonal should be 1"); } else { - if (std::abs(BTu_bar[k]) > 1e-6) { - settings_.log.printf("BTu_bar[%d] = %e i %d\n", k, BTu_bar[k], i); - exit(1); - } + assert(std::abs(BTu_bar[k]) <= 1e-6 && "BTu_bar off-diagonal should be 0"); } } +#endif
1218-1218: Debug print should be gated.This debug print for cut generation should be wrapped in a debug macro.
- settings_.log.printf("Generating cut for variable %d relaxed value %e row %d\n", j, x_j, i); + settings_.log.debug("Generating cut for variable %d relaxed value %e row %d\n", j, x_j, i);cpp/src/dual_simplex/solve.cpp (2)
315-327: Consider removing or guarding debug verification code.This block performs a sanity check on the basis factorization and calls
exit(1)on failure. Hard process termination is inappropriate for library code - it prevents callers from handling errors gracefully. Consider:
- Converting to
CUOPT_ASSERTfor debug builds only- Returning an error status instead of terminating
- Wrapping with a
constexpr bool debug_verify = false;guard similar towrite_out_matlab+ constexpr bool verify_basis = false; + if (verify_basis) { csc_matrix_t<i_t, f_t> Btest(lp.num_rows, lp.num_rows, 1); basis_update.multiply_lu(Btest); csc_matrix_t<i_t, f_t> B(lp.num_rows, lp.num_rows, 1); form_b(lp.A, basic_list, B); csc_matrix_t<i_t, f_t> Diff(lp.num_rows, lp.num_rows, 1); add(Btest, B, 1.0, -1.0, Diff); const f_t err = Diff.norm1(); settings.log.printf("Before || B - L*U || %e\n", err); - if (err > 1e-6) { - exit(1); - } + CUOPT_ASSERT(err <= 1e-6); }
435-449: Second debug block should also be guarded.Same concern as the earlier verification block -
exit(1)is inappropriate for library code. Additionally, there's inconsistent indentation at line 448.// Check the basis update + if (verify_basis) { csc_matrix_t<i_t, f_t> Btest(lp.num_rows, lp.num_rows, 1); basis_update.multiply_lu(Btest); // ... if (err > 1e-6) { Diff.print_matrix(); - exit(1); + CUOPT_ASSERT(false); } + }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
cpp/src/dual_simplex/basis_updates.cpp(1 hunks)cpp/src/dual_simplex/branch_and_bound.cpp(4 hunks)cpp/src/dual_simplex/phase2.cpp(1 hunks)cpp/src/dual_simplex/solve.cpp(3 hunks)cpp/src/dual_simplex/sparse_matrix.cpp(1 hunks)cpp/src/dual_simplex/sparse_matrix.hpp(1 hunks)cpp/src/mip/solver.cu(3 hunks)
✅ Files skipped from review due to trivial changes (1)
- cpp/src/dual_simplex/phase2.cpp
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-22T14:25:22.899Z
Learnt from: aliceb-nv
Repo: NVIDIA/cuopt PR: 527
File: cpp/src/mip/diversity/lns/rins.cu:167-175
Timestamp: 2025-10-22T14:25:22.899Z
Learning: In MIP (Mixed Integer Programming) problems in the cuOPT codebase, `n_integer_vars == 0` is impossible by definition—MIP problems must have at least one integer variable. If there are no integer variables, it would be a pure Linear Programming (LP) problem, not a MIP problem.
Applied to files:
cpp/src/mip/solver.cucpp/src/dual_simplex/branch_and_bound.cpp
🧬 Code graph analysis (3)
cpp/src/dual_simplex/sparse_matrix.cpp (1)
cpp/src/dual_simplex/sparse_matrix.hpp (2)
C(140-140)c(143-143)
cpp/src/mip/solver.cu (1)
cpp/tests/mip/elim_var_remap_test.cu (1)
sol(110-110)
cpp/src/dual_simplex/branch_and_bound.cpp (3)
cpp/src/dual_simplex/solve.cpp (16)
basic_list(114-114)solve_linear_program_with_advanced_basis(129-295)solve_linear_program_with_advanced_basis(129-138)solve_linear_program_with_advanced_basis(847-856)compute_user_objective(89-94)compute_user_objective(89-89)compute_user_objective(97-101)compute_user_objective(97-97)compute_user_objective(834-835)compute_user_objective(837-837)solve_linear_program_with_cuts(298-488)solve_linear_program_with_cuts(298-308)solve_linear_program_with_cuts(858-869)compute_objective(77-86)compute_objective(77-77)compute_objective(831-832)cpp/src/dual_simplex/solve.hpp (5)
solve_linear_program_with_advanced_basis(53-62)compute_user_objective(37-37)compute_user_objective(40-40)solve_linear_program_with_cuts(65-75)compute_objective(34-34)cpp/src/dual_simplex/presolve.hpp (1)
uncrush_primal_solution(160-163)
🔇 Additional comments (8)
cpp/src/dual_simplex/sparse_matrix.hpp (1)
139-143: LGTM!The new method declarations for
append_rowsandappend_rowfollow the existing class conventions and provide a clean API for extending CSR matrices with additional rows.cpp/src/dual_simplex/basis_updates.cpp (1)
1113-1315: Implementation ofappend_cutslooks structurally sound.The method correctly:
- Solves U^T W^T = C_B^T row-by-row
- Computes V from W using the inverse transforms
- Extends L and U with the appropriate block structure
- Updates permutations and workspace sizes
Note: Ensure the template argument fix at line 1172 is applied (see previous comment).
cpp/src/dual_simplex/sparse_matrix.cpp (1)
360-400: LGTM!The
append_rowsimplementation correctly:
- Validates that the input matrix has compatible column dimensions
- Resizes storage appropriately
- Updates row pointers and copies nonzero data
cpp/src/dual_simplex/branch_and_bound.cpp (1)
1070-1075: Settingscale_columns = falsemay affect numerical stability.Disabling column scaling could impact numerical stability for certain problem instances. Consider adding a comment explaining why scaling is disabled here, or making this configurable.
cpp/src/mip/solver.cu (1)
130-130: Debug code: LP-concurrent path is disabled.The
0 &&prefix disables the concurrent LP path when the problem is reduced to a pure LP.- if (0 && context.problem_ptr->n_integer_vars == 0) { + if (context.problem_ptr->n_integer_vars == 0) {⛔ Skipped due to learnings
Learnt from: aliceb-nv Repo: NVIDIA/cuopt PR: 527 File: cpp/src/mip/diversity/lns/rins.cu:167-175 Timestamp: 2025-10-22T14:25:22.899Z Learning: In MIP (Mixed Integer Programming) problems in the cuOPT codebase, `n_integer_vars == 0` is impossible by definition—MIP problems must have at least one integer variable. If there are no integer variables, it would be a pure Linear Programming (LP) problem, not a MIP problem.cpp/src/dual_simplex/solve.cpp (3)
11-11: LGTM!The new include for
basis_solves.hppis required to support theappend_cutsmethod used in the new function.
346-378: LGTM!The slack variable addition and LP dimension updates are implemented correctly. Each new cut constraint
C_i * x <= d_igets a slack variables_i >= 0such thatC_i * x + s_i = d_i, with identity columns appended to the constraint matrix.
858-869: LGTM!The explicit template instantiation is correctly specified and follows the existing pattern in the file.
| settings_.log.printf( | ||
| "Variable %d has negative lower bound %e\n", j, original_lp_.lower[j]); | ||
| exit(1); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: exit(1) will terminate the application.
This exit(1) is not inside a debug block and will terminate the application if any variable has a negative lower bound. Consider throwing an exception or returning an error status instead.
if (original_lp_.lower[j] < 0) {
- settings_.log.printf(
- "Variable %d has negative lower bound %e\n", j, original_lp_.lower[j]);
- exit(1);
+ settings_.log.printf(
+ "Variable %d has negative lower bound %e. Skipping cut generation.\n", j, original_lp_.lower[j]);
+ break; // or handle appropriately
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| settings_.log.printf( | |
| "Variable %d has negative lower bound %e\n", j, original_lp_.lower[j]); | |
| exit(1); | |
| } | |
| settings_.log.printf( | |
| "Variable %d has negative lower bound %e. Skipping cut generation.\n", j, original_lp_.lower[j]); | |
| break; // or handle appropriately | |
| } |
🤖 Prompt for AI Agents
In cpp/src/dual_simplex/branch_and_bound.cpp around lines 1182 to 1185, the code
logs a negative lower bound and calls exit(1) which abruptly terminates the
process; replace this with non-fatal error handling by either throwing a
descriptive C++ exception (e.g., std::runtime_error with the same log message)
or returning/propagating an error status from the function so the caller can
handle it; ensure any created log entry remains but remove exit(1), and update
callers/signatures as needed to handle the thrown exception or returned error
code.
| if (Cx[k] <= cut_rhs[k]) { | ||
| lp_settings.log.printf("C*x <= d for cut %d\n", k); | ||
| exit(1); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: exit(1) will terminate the application.
This exit(1) is not inside a debug block and will terminate the application if cut validation fails. This should be handled gracefully.
if (Cx[k] <= cut_rhs[k]) {
- lp_settings.log.printf("C*x <= d for cut %d\n", k);
- exit(1);
+ lp_settings.log.printf("Warning: C*x <= d for cut %d, skipping this cut round\n", k);
+ // Handle the error condition appropriately
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (Cx[k] <= cut_rhs[k]) { | |
| lp_settings.log.printf("C*x <= d for cut %d\n", k); | |
| exit(1); | |
| } | |
| } | |
| if (Cx[k] <= cut_rhs[k]) { | |
| lp_settings.log.printf("Warning: C*x <= d for cut %d, skipping this cut round\n", k); | |
| // Handle the error condition appropriately | |
| } | |
| } |
🤖 Prompt for AI Agents
In cpp/src/dual_simplex/branch_and_bound.cpp around lines 1467-1471, the code
calls exit(1) when a cut validation fails which will unconditionally terminate
the whole application; replace this with graceful error handling: log a clear
error including cut index and values, then propagate the failure to the caller
instead of exiting (e.g., return a boolean/error code, set an error status on
the object, or throw a std::runtime_error) so the caller can decide how to
handle the invalid cut; ensure any resources are not leaked and update the
callers to handle the propagated error if necessary.
| if (cut_status != lp_status_t::OPTIMAL) { | ||
| lp_settings.log.printf("Cut status %d\n", cut_status); | ||
| exit(1); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: exit(1) will terminate the application.
This exit(1) is not inside a debug block and will terminate the application if the cut solve returns a non-optimal status. This should be handled gracefully, e.g., by breaking out of the cut loop or returning an appropriate status.
if (cut_status != lp_status_t::OPTIMAL) {
lp_settings.log.printf("Cut status %d\n", cut_status);
- exit(1);
+ break; // Exit cut generation loop and proceed with B&B
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (cut_status != lp_status_t::OPTIMAL) { | |
| lp_settings.log.printf("Cut status %d\n", cut_status); | |
| exit(1); | |
| } | |
| if (cut_status != lp_status_t::OPTIMAL) { | |
| lp_settings.log.printf("Cut status %d\n", cut_status); | |
| break; // Exit cut generation loop and proceed with B&B | |
| } |
🤖 Prompt for AI Agents
In cpp/src/dual_simplex/branch_and_bound.cpp around lines 1490 to 1493, replace
the unconditional exit(1) when cut_status != lp_status_t::OPTIMAL with graceful
handling: log the status as you already do, then either break out of the current
cut/solve loop or propagate the non-optimal status to the caller (e.g., set a
return/overall status variable and return that status or throw a checked
exception) so the caller can decide next steps; do not terminate the process
directly.
cpp/src/dual_simplex/solve.cpp
Outdated
| lp_status_t lp_status; | ||
| if (status == dual::status_t::OPTIMAL) { lp_status = lp_status_t::OPTIMAL; } | ||
| if (status == dual::status_t::DUAL_UNBOUNDED) { lp_status = lp_status_t::INFEASIBLE; } | ||
| if (status == dual::status_t::TIME_LIMIT) { lp_status = lp_status_t::TIME_LIMIT; } | ||
| if (status == dual::status_t::ITERATION_LIMIT) { lp_status = lp_status_t::ITERATION_LIMIT; } | ||
| if (status == dual::status_t::CONCURRENT_LIMIT) { lp_status = lp_status_t::CONCURRENT_LIMIT; } | ||
| if (status == dual::status_t::NUMERICAL) { lp_status = lp_status_t::NUMERICAL_ISSUES; } | ||
| if (status == dual::status_t::CUTOFF) { lp_status = lp_status_t::CUTOFF; } | ||
| return lp_status; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Uninitialized lp_status leads to undefined behavior.
lp_status is declared without initialization. If status doesn't match any of the handled values, the function returns an indeterminate value, which is undefined behavior. Use a switch statement with a default case or initialize with a fallback value.
- lp_status_t lp_status;
- if (status == dual::status_t::OPTIMAL) { lp_status = lp_status_t::OPTIMAL; }
- if (status == dual::status_t::DUAL_UNBOUNDED) { lp_status = lp_status_t::INFEASIBLE; }
- if (status == dual::status_t::TIME_LIMIT) { lp_status = lp_status_t::TIME_LIMIT; }
- if (status == dual::status_t::ITERATION_LIMIT) { lp_status = lp_status_t::ITERATION_LIMIT; }
- if (status == dual::status_t::CONCURRENT_LIMIT) { lp_status = lp_status_t::CONCURRENT_LIMIT; }
- if (status == dual::status_t::NUMERICAL) { lp_status = lp_status_t::NUMERICAL_ISSUES; }
- if (status == dual::status_t::CUTOFF) { lp_status = lp_status_t::CUTOFF; }
+ lp_status_t lp_status = lp_status_t::NUMERICAL_ISSUES; // default fallback
+ switch (status) {
+ case dual::status_t::OPTIMAL: lp_status = lp_status_t::OPTIMAL; break;
+ case dual::status_t::DUAL_UNBOUNDED: lp_status = lp_status_t::INFEASIBLE; break;
+ case dual::status_t::TIME_LIMIT: lp_status = lp_status_t::TIME_LIMIT; break;
+ case dual::status_t::ITERATION_LIMIT: lp_status = lp_status_t::ITERATION_LIMIT; break;
+ case dual::status_t::CONCURRENT_LIMIT: lp_status = lp_status_t::CONCURRENT_LIMIT; break;
+ case dual::status_t::NUMERICAL: lp_status = lp_status_t::NUMERICAL_ISSUES; break;
+ case dual::status_t::CUTOFF: lp_status = lp_status_t::CUTOFF; break;
+ default: lp_status = lp_status_t::NUMERICAL_ISSUES; break;
+ }
return lp_status;🤖 Prompt for AI Agents
cpp/src/dual_simplex/solve.cpp lines 479-487: lp_status is declared
uninitialized and may be returned without being set; replace the chain of ifs
with a switch(status) that explicitly maps each dual::status_t case to the
corresponding lp_status_t and add a default case that assigns a sensible
fallback (e.g. lp_status_t::NUMERICAL_ISSUES or an appropriate "UNKNOWN"/error
status), and optionally log or assert on the default to catch unexpected enum
values.
| //bool presolve_success = dm.run_presolve(timer_.remaining_time()); | ||
| bool presolve_success = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Debug code: presolve is disabled.
The presolve step is commented out and presolve_success is hardcoded to true. This bypasses important infeasibility detection and optimization. This appears to be debug code that should be removed before merging.
- //bool presolve_success = dm.run_presolve(timer_.remaining_time());
- bool presolve_success = true;
+ bool presolve_success = dm.run_presolve(timer_.remaining_time());📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| //bool presolve_success = dm.run_presolve(timer_.remaining_time()); | |
| bool presolve_success = true; | |
| bool presolve_success = dm.run_presolve(timer_.remaining_time()); |
🤖 Prompt for AI Agents
In cpp/src/mip/solver.cu around lines 112-113, the presolve call was commented
out and presolve_success hardcoded to true; restore the original presolve
invocation and use its boolean result instead of the hardcoded true. Replace the
hardcoded assignment with the actual call (passing timer_.remaining_time() as
before) and propagate/check presolve_success as the code originally intended so
infeasibility and early exits are handled properly.
| return sol; | ||
| } | ||
| if (context.problem_ptr->empty) { | ||
| if (0 && context.problem_ptr->empty) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Debug code: condition is always false.
The 0 && prefix makes this condition always evaluate to false, disabling the "problem fully reduced" check. This appears to be debug code.
- if (0 && context.problem_ptr->empty) {
+ if (context.problem_ptr->empty) {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (0 && context.problem_ptr->empty) { | |
| if (context.problem_ptr->empty) { |
🤖 Prompt for AI Agents
In cpp/src/mip/solver.cu around line 121, the condition `if (0 &&
context.problem_ptr->empty)` contains a debug `0 &&` that forces the branch to
never run; remove the `0 &&` so the check becomes `if
(context.problem_ptr->empty)` (or replace with an appropriate runtime flag) and
ensure any associated handling/logging for the "problem fully reduced" case
remains intact and compiles correctly.
cpp/src/mip/solver.cu
Outdated
| auto bb_status = branch_and_bound_status_future.get(); | ||
| exit(1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: exit(1) terminates the process unconditionally.
This exit(1) call will terminate the entire application immediately after the branch-and-bound solve completes. This is debug code that must be removed before merging.
Additionally, the code at line 235 calls branch_and_bound_status_future.get() again, which would be undefined behavior (double-get on a future) if the exit(1) weren't present.
- auto bb_status = branch_and_bound_status_future.get();
- exit(1);
-📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| auto bb_status = branch_and_bound_status_future.get(); | |
| exit(1); |
🤖 Prompt for AI Agents
In cpp/src/mip/solver.cu around lines 228-229, remove the unconditional exit(1)
that was left from debugging; instead rely on the retrieved bb_status value and
ensure you do not call branch_and_bound_status_future.get() a second time (line
~235) — call get() only once, store its result in bb_status and reuse that
variable for subsequent logic, or check future.valid() before getting if
conditional behavior is needed.
|
🔔 Hi @anandhkb @chris-maes, this pull request has had no activity for 7 days. Please update or let us know if it can be closed. Thank you! If this is an "epic" issue, then please add the "epic" label to this issue. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
♻️ Duplicate comments (4)
cpp/src/mip/solver.cu (2)
112-113: Debug code: presolve is disabled.The presolve step is commented out and
presolve_successis hardcoded totrue. This bypasses important infeasibility detection and optimization. This should be restored before merging.- //bool presolve_success = dm.run_presolve(timer_.remaining_time()); - bool presolve_success = true; + bool presolve_success = dm.run_presolve(timer_.remaining_time());
121-121: Debug code: condition is always false.The
0 &&prefix makes this condition always evaluate tofalse, disabling the "problem fully reduced" check.- if (0 && context.problem_ptr->empty) { + if (context.problem_ptr->empty) {cpp/src/dual_simplex/branch_and_bound.cpp (2)
1245-1248: Replaceexit(1)with error return.Library code should not terminate the process when bound strengthening fails.
if (!feasible) { settings_.log.printf("Bound strengthening failed\n"); - exit(1); + return mip_status_t::INFEASIBLE; }
1279-1282: Replaceexit(1)with error return.Library code should not terminate the process on non-optimal cut status.
if (cut_status != dual::status_t::OPTIMAL) { settings_.log.printf("Cut status %d\n", cut_status); - exit(1); + // Break out of cut loop and proceed with B&B using current basis + break; }
🧹 Nitpick comments (14)
cpp/src/mip/diversity/lns/rins.cu (1)
248-248: Clarify the rationale for disabling cutting planes in RINS subproblems.The setting appears intentional and should function correctly. However, given that this PR introduces cutting plane infrastructure, it would be valuable to document why cutting planes are explicitly disabled for RINS subproblems. This could be due to performance considerations (keeping RINS fast), algorithmic simplicity, or avoiding interactions during initial development.
Consider adding an inline comment to document this design decision.
📝 Suggested documentation
+ // Disable cutting planes in RINS subproblems to keep them fast and simple branch_and_bound_settings.max_cut_passes = 0;cpp/src/mip/diversity/diversity_manager.cu (1)
412-412: Clarify intent: document or remove commented-out code.The
rins.enable()call has been commented out without explanation at line 412. If this is a temporary change for cutting planes integration or other development work, add a TODO comment with context. If RINS is permanently disabled, remove the line entirely rather than leaving it commented.cpp/src/dual_simplex/branch_and_bound.cpp (2)
1237-1241: Remove verbose diagnostic logging before merge.The "Before A check", "Before A transpose", "After A transpose" log messages appear to be development diagnostics. Consider removing or gating behind a debug flag.
🔎 Suggested fix
- settings_.log.printf("Before A check\n"); original_lp_.A.check_matrix(); - settings_.log.printf("Before A transpose\n"); original_lp_.A.transpose(Arow); - settings_.log.printf("After A transpose\n");
1214-1225: Mutex scope could be narrowed.The mutex lock spans
add_cutsand the subsequent status check. Ifadd_cutsis a lengthy operation, this could create contention withset_new_solution. Consider whether narrower locking is possible, or document why the broad scope is necessary.cpp/src/dual_simplex/cuts.hpp (3)
33-35: Use logging infrastructure instead of printf.The
printfcall bypasses the solver's logging infrastructure. Usesettings.log.printfor similar for consistency with the rest of the codebase.However, since
minimum_violationdoesn't take settings as a parameter, consider either:
- Adding a settings parameter, or
- Making this a debug-only diagnostic (gated by preprocessor)
if (Cx[k] <= cut_rhs[k]) { - printf("C*x <= d for cut %d. C*x %e rhs %e\n", k, Cx[k], cut_rhs[k]); +#ifdef DEBUG_CUTS + fprintf(stderr, "C*x <= d for cut %d. C*x %e rhs %e\n", k, Cx[k], cut_rhs[k]); +#endif }
126-138: Constructor performs computation (b_solve).The constructor calls
basis_update.b_solve(lp.rhs, b_bar_)which performs a potentially expensive basis solve. Consider whether this should be a separate initialization step for better control over when the computation occurs, or document that the constructor has side effects.
219-220: Add namespace closing comment.For consistency with other headers in the codebase, add a closing comment for the namespace.
-} +} // namespace cuopt::linear_programming::dual_simplexcpp/src/dual_simplex/cuts.cpp (7)
15-35: Consider returning an error code fromadd_cut.The function currently performs an early return at Line 28 when encountering invalid variables, but doesn't signal this failure to the caller. Consider returning an error code (e.g.,
i_tstatus) so callers can detect and handle validation failures.
85-150: Consider making cut selection parameters configurable.Lines 88-89 and 111-113 use hardcoded values for weights (
weight_distance,weight_orthogonality), thresholds (max_cuts=2000,min_orthogonality=0.5,min_cut_distance=1e-4). These are typically problem-dependent parameters that benefit from tuning. Consider exposing them through the settings structure.
304-365: Refactor duplicate cut violation checking logic.Lines 314-326 and 344-356 contain nearly identical code for checking cut violation and computing cut distance. This duplication can be extracted into a helper function to improve maintainability.
🔎 Example refactoring:
// Add a helper function template <typename i_t, typename f_t> bool check_cut_violation(const sparse_vector_t<i_t, f_t>& cut, f_t cut_rhs, const std::vector<f_t>& xstar, const simplex_solver_settings_t<i_t, f_t>& settings, i_t cut_id, f_t& distance) { f_t dot = 0.0; f_t cut_norm = 0.0; for (i_t k = 0; k < cut.i.size(); k++) { const i_t jj = cut.i[k]; const f_t aj = cut.x[k]; dot += aj * xstar[jj]; cut_norm += aj * aj; } if (dot >= cut_rhs) { settings.log.printf("Cut %d is not violated. Skipping\n", cut_id); return false; } distance = (cut_rhs - dot) / std::sqrt(cut_norm); return true; }Then replace both code blocks with calls to this helper.
Based on coding guidelines: Refactor code duplication in solver components (3+ occurrences) into shared utilities.
499-509: Consider making shallow cut tolerances configurable.Lines 499 and 506 use hardcoded tolerances (
shallow_tol = 1e-2,0.01,0.99) for filtering shallow or near-integer cuts. These thresholds are problem-dependent and could benefit from being exposed through the settings structure.
807-856: Verify algorithmic complexity of C_B construction.Lines 807-856 construct the
C_Bmatrix by iterating through all cuts and checking basic variable membership. The nested loops (cuts × cut_coefficients × basic_lookup) have O(p × nnz × 1) complexity wherepis the number of cuts. For large-scale problems with thousands of cuts, consider whether thein_basislookup (O(1) via hash map vs O(n) via vector) is optimal. The current implementation uses a vector which is acceptable, but document this for future optimization if needed.Based on coding guidelines: Assess algorithmic complexity for large-scale problems.
894-1012: Consider the performance impact of full basis refactorization.Line 1010 performs a full
basis_update.refactor_basis()after removing cuts, which is computationally expensive. For frequent cut removal operations in branch-and-bound, consider whether incremental basis updates would be more efficient. However, full refactorization may be necessary for numerical stability after structural changes.Based on coding guidelines: Assess algorithmic complexity for large-scale problems; operational concerns such as observability and performance monitoring.
912-918: Consider making cut removal tolerances configurable.Lines 912 and 918 use hardcoded tolerances (
dual_tol = 1e-10,slack_tol = 1e-3) for identifying removable cuts. These thresholds are problem-dependent and affect the aggressiveness of cut removal. Consider exposing them through the settings structure.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
cpp/src/dual_simplex/CMakeLists.txt(2 hunks)cpp/src/dual_simplex/branch_and_bound.cpp(16 hunks)cpp/src/dual_simplex/branch_and_bound.hpp(1 hunks)cpp/src/dual_simplex/cuts.cpp(1 hunks)cpp/src/dual_simplex/cuts.hpp(1 hunks)cpp/src/dual_simplex/solve.cpp(1 hunks)cpp/src/dual_simplex/sparse_matrix.cpp(2 hunks)cpp/src/mip/diversity/diversity_manager.cu(1 hunks)cpp/src/mip/diversity/lns/rins.cu(1 hunks)cpp/src/mip/solver.cu(4 hunks)cpp/tests/dual_simplex/unit_tests/solve.cpp(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- cpp/tests/dual_simplex/unit_tests/solve.cpp
🚧 Files skipped from review as they are similar to previous changes (1)
- cpp/src/dual_simplex/sparse_matrix.cpp
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{cu,cuh,cpp,hpp,h}
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
**/*.{cu,cuh,cpp,hpp,h}: Track GPU device memory allocations and deallocations to prevent memory leaks; ensure cudaMalloc/cudaFree balance and cleanup of streams/events
Validate algorithm correctness in optimization logic: simplex pivots, branch-and-bound decisions, routing heuristics, and constraint/objective handling must produce correct results
Check numerical stability: prevent overflow/underflow, precision loss, division by zero/near-zero, and use epsilon comparisons for floating-point equality checks
Validate correct initialization of variable bounds, constraint coefficients, and algorithm state before solving; ensure reset when transitioning between algorithm phases (presolve, simplex, diving, crossover)
Ensure variables and constraints are accessed from the correct problem context (original vs presolve vs folded vs postsolve); verify index mapping consistency across problem transformations
For concurrent CUDA operations (barriers, async operations), explicitly create and manage dedicated streams instead of reusing the default stream; document stream lifecycle
Eliminate unnecessary host-device synchronization (cudaDeviceSynchronize) in hot paths that blocks GPU pipeline; use streams and events for async execution
Assess algorithmic complexity for large-scale problems (millions of variables/constraints); ensure O(n log n) or better complexity, not O(n²) or worse
Verify correct problem size checks before expensive GPU/CPU operations; prevent resource exhaustion on oversized problems
Identify assertions with overly strict numerical tolerances that fail on legitimate degenerate/edge cases (near-zero pivots, singular matrices, empty problems)
Ensure race conditions are absent in multi-GPU code and multi-threaded server implementations; verify proper synchronization of shared state
Refactor code duplication in solver components (3+ occurrences) into shared utilities; for GPU kernels, use templated device functions to avoid duplication
Check that hard-coded GPU de...
Files:
cpp/src/dual_simplex/branch_and_bound.hppcpp/src/mip/diversity/diversity_manager.cucpp/src/mip/solver.cucpp/src/dual_simplex/solve.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/mip/diversity/lns/rins.cucpp/src/dual_simplex/cuts.hppcpp/src/dual_simplex/cuts.cpp
**/*.{h,hpp,py}
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
Verify C API does not break ABI stability (no struct layout changes, field reordering); maintain backward compatibility in Python and server APIs with deprecation warnings
Files:
cpp/src/dual_simplex/branch_and_bound.hppcpp/src/dual_simplex/cuts.hpp
**/*.{cpp,hpp,h}
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
**/*.{cpp,hpp,h}: Check for unclosed file handles when reading MPS/QPS problem files; ensure RAII patterns or proper cleanup in exception paths
Validate input sanitization to prevent buffer overflows and resource exhaustion attacks; avoid unsafe deserialization of problem files
Prevent thread-unsafe use of global and static variables; use proper mutex/synchronization in server code accessing shared solver state
Files:
cpp/src/dual_simplex/branch_and_bound.hppcpp/src/dual_simplex/solve.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.hppcpp/src/dual_simplex/cuts.cpp
**/*.{cu,cpp,hpp,h}
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
Avoid inappropriate use of exceptions in performance-critical GPU operation paths; prefer error codes or CUDA error checking for latency-sensitive code
Files:
cpp/src/dual_simplex/branch_and_bound.hppcpp/src/mip/diversity/diversity_manager.cucpp/src/mip/solver.cucpp/src/dual_simplex/solve.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/mip/diversity/lns/rins.cucpp/src/dual_simplex/cuts.hppcpp/src/dual_simplex/cuts.cpp
**/*.{cu,cuh}
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
**/*.{cu,cuh}: Every CUDA kernel launch and memory operation must have error checking with CUDA_CHECK or equivalent verification
Avoid reinventing functionality already available in Thrust, CCCL, or RMM libraries; prefer standard library utilities over custom implementations
Files:
cpp/src/mip/diversity/diversity_manager.cucpp/src/mip/solver.cucpp/src/mip/diversity/lns/rins.cu
**/*.cu
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
**/*.cu: Verify race conditions and correctness of GPU kernel shared memory, atomics, and warp-level operations
Detect inefficient GPU kernel launches with low occupancy or poor memory access patterns; optimize for coalesced memory access and minimize warp divergence in hot paths
Files:
cpp/src/mip/diversity/diversity_manager.cucpp/src/mip/solver.cucpp/src/mip/diversity/lns/rins.cu
🧠 Learnings (23)
📓 Common learnings
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Reduce tight coupling between solver components (presolve, simplex, basis, barrier); increase modularity and reusability of optimization algorithms
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cpp,hpp,h} : Prevent thread-unsafe use of global and static variables; use proper mutex/synchronization in server code accessing shared solver state
Applied to files:
cpp/src/dual_simplex/branch_and_bound.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Validate algorithm correctness in optimization logic: simplex pivots, branch-and-bound decisions, routing heuristics, and constraint/objective handling must produce correct results
Applied to files:
cpp/src/dual_simplex/branch_and_bound.hppcpp/src/mip/solver.cucpp/src/dual_simplex/solve.cppcpp/src/dual_simplex/CMakeLists.txtcpp/src/dual_simplex/branch_and_bound.cppcpp/src/mip/diversity/lns/rins.cucpp/src/dual_simplex/cuts.hppcpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Validate correct initialization of variable bounds, constraint coefficients, and algorithm state before solving; ensure reset when transitioning between algorithm phases (presolve, simplex, diving, crossover)
Applied to files:
cpp/src/dual_simplex/branch_and_bound.hppcpp/src/mip/diversity/diversity_manager.cucpp/src/mip/solver.cucpp/src/dual_simplex/solve.cppcpp/src/dual_simplex/CMakeLists.txtcpp/src/dual_simplex/branch_and_bound.cppcpp/src/mip/diversity/lns/rins.cucpp/src/dual_simplex/cuts.hppcpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*test*.{cpp,cu,py} : Add tests for algorithm phase transitions: verify correct initialization of bounds and state when transitioning from presolve to simplex to diving to crossover
Applied to files:
cpp/src/dual_simplex/branch_and_bound.hppcpp/src/mip/solver.cucpp/src/dual_simplex/solve.cppcpp/src/dual_simplex/CMakeLists.txtcpp/src/dual_simplex/branch_and_bound.cppcpp/src/mip/diversity/lns/rins.cucpp/src/dual_simplex/cuts.hppcpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Refactor code duplication in solver components (3+ occurrences) into shared utilities; for GPU kernels, use templated device functions to avoid duplication
Applied to files:
cpp/src/mip/diversity/diversity_manager.cucpp/src/mip/solver.cucpp/src/dual_simplex/solve.cppcpp/src/dual_simplex/CMakeLists.txtcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.hppcpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Ensure variables and constraints are accessed from the correct problem context (original vs presolve vs folded vs postsolve); verify index mapping consistency across problem transformations
Applied to files:
cpp/src/mip/solver.cucpp/src/dual_simplex/solve.cppcpp/src/dual_simplex/CMakeLists.txtcpp/src/dual_simplex/branch_and_bound.cppcpp/src/mip/diversity/lns/rins.cu
📚 Learning: 2025-12-04T20:09:09.264Z
Learnt from: chris-maes
Repo: NVIDIA/cuopt PR: 602
File: cpp/src/linear_programming/solve.cu:732-742
Timestamp: 2025-12-04T20:09:09.264Z
Learning: In cpp/src/linear_programming/solve.cu, the barrier solver does not currently return INFEASIBLE or UNBOUNDED status. It only returns OPTIMAL, TIME_LIMIT, NUMERICAL_ISSUES, or CONCURRENT_LIMIT.
Applied to files:
cpp/src/mip/solver.cucpp/src/dual_simplex/solve.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/mip/diversity/lns/rins.cucpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Reduce tight coupling between solver components (presolve, simplex, basis, barrier); increase modularity and reusability of optimization algorithms
Applied to files:
cpp/src/mip/solver.cucpp/src/dual_simplex/solve.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.hppcpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Assess algorithmic complexity for large-scale problems (millions of variables/constraints); ensure O(n log n) or better complexity, not O(n²) or worse
Applied to files:
cpp/src/mip/solver.cucpp/src/dual_simplex/solve.cppcpp/src/dual_simplex/branch_and_bound.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Check that hard-coded GPU device IDs and resource limits are made configurable; abstract multi-backend support for different CUDA versions
Applied to files:
cpp/src/mip/solver.cu
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Verify correct problem size checks before expensive GPU/CPU operations; prevent resource exhaustion on oversized problems
Applied to files:
cpp/src/mip/solver.cucpp/src/dual_simplex/solve.cppcpp/src/dual_simplex/branch_and_bound.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Identify assertions with overly strict numerical tolerances that fail on legitimate degenerate/edge cases (near-zero pivots, singular matrices, empty problems)
Applied to files:
cpp/src/mip/solver.cucpp/src/dual_simplex/solve.cppcpp/src/dual_simplex/branch_and_bound.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cpp,hpp,h} : Avoid inappropriate use of exceptions in performance-critical GPU operation paths; prefer error codes or CUDA error checking for latency-sensitive code
Applied to files:
cpp/src/mip/solver.cucpp/src/dual_simplex/branch_and_bound.cpp
📚 Learning: 2025-10-22T14:25:22.899Z
Learnt from: aliceb-nv
Repo: NVIDIA/cuopt PR: 527
File: cpp/src/mip/diversity/lns/rins.cu:167-175
Timestamp: 2025-10-22T14:25:22.899Z
Learning: In MIP (Mixed Integer Programming) problems in the cuOPT codebase, `n_integer_vars == 0` is impossible by definition—MIP problems must have at least one integer variable. If there are no integer variables, it would be a pure Linear Programming (LP) problem, not a MIP problem.
Applied to files:
cpp/src/mip/solver.cucpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*test*.{cpp,cu,py} : Test with free variables, singleton problems, and extreme problem dimensions near resource limits to validate edge case handling
Applied to files:
cpp/src/mip/solver.cucpp/src/dual_simplex/solve.cppcpp/src/dual_simplex/CMakeLists.txtcpp/src/dual_simplex/branch_and_bound.cpp
📚 Learning: 2025-12-04T04:11:12.640Z
Learnt from: chris-maes
Repo: NVIDIA/cuopt PR: 500
File: cpp/src/dual_simplex/scaling.cpp:68-76
Timestamp: 2025-12-04T04:11:12.640Z
Learning: In the cuOPT dual simplex solver, CSR/CSC matrices (including the quadratic objective matrix Q) are required to have valid dimensions and indices by construction. Runtime bounds checking in performance-critical paths like matrix scaling is avoided to prevent slowdowns. Validation is performed via debug-only check_matrix() calls wrapped in #ifdef CHECK_MATRIX.
Applied to files:
cpp/src/dual_simplex/solve.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*test*.{cpp,cu,py} : Write tests validating numerical correctness of optimization results (not just 'runs without error'); test degenerate cases (infeasible, unbounded, empty, singleton problems)
Applied to files:
cpp/src/dual_simplex/solve.cppcpp/src/dual_simplex/branch_and_bound.cpp
📚 Learning: 2025-12-03T23:29:26.391Z
Learnt from: chris-maes
Repo: NVIDIA/cuopt PR: 500
File: cpp/src/dual_simplex/sparse_matrix.cpp:519-524
Timestamp: 2025-12-03T23:29:26.391Z
Learning: In cpp/src/dual_simplex/sparse_matrix.cpp, the check_matrix() function is debug/diagnostic code (wrapped in #ifdef CHECK_MATRIX) that intentionally prints errors without necessarily returning early. The return codes from this debug code are not actively checked; the purpose is to print all validation errors in one pass for better diagnostics.
Applied to files:
cpp/src/dual_simplex/solve.cppcpp/src/dual_simplex/branch_and_bound.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*test*.{cpp,cu,py} : Add tests for problem transformations: verify correctness of original→transformed→postsolve mappings and index consistency across problem representations
Applied to files:
cpp/src/dual_simplex/CMakeLists.txt
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh} : Avoid reinventing functionality already available in Thrust, CCCL, or RMM libraries; prefer standard library utilities over custom implementations
Applied to files:
cpp/src/dual_simplex/CMakeLists.txt
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cpp,hpp,h} : Check for unclosed file handles when reading MPS/QPS problem files; ensure RAII patterns or proper cleanup in exception paths
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Check numerical stability: prevent overflow/underflow, precision loss, division by zero/near-zero, and use epsilon comparisons for floating-point equality checks
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cpp
🧬 Code graph analysis (3)
cpp/src/dual_simplex/branch_and_bound.cpp (3)
cpp/src/dual_simplex/solve.hpp (4)
solve_linear_program_with_advanced_basis(53-62)compute_user_objective(37-37)compute_user_objective(40-40)compute_objective(34-34)cpp/src/dual_simplex/presolve.hpp (2)
uncrush_primal_solution(160-163)bound_strengthening(185-190)cpp/src/dual_simplex/cuts.hpp (2)
minimum_violation(22-39)minimum_violation(22-24)
cpp/src/dual_simplex/cuts.hpp (2)
cpp/src/dual_simplex/sparse_matrix.hpp (2)
C(140-140)Arow(51-52)cpp/src/dual_simplex/sparse_matrix.cpp (1)
matrix_vector_multiply(957-962)
cpp/src/dual_simplex/cuts.cpp (3)
cpp/src/dual_simplex/cuts.hpp (6)
n(57-57)cut_storage_(68-68)row(71-71)row(72-72)i(73-73)a(176-182)cpp/src/dual_simplex/sparse_matrix.cpp (10)
dot(846-855)dot(846-846)sparse_dot(858-880)sparse_dot(858-861)sparse_dot(883-907)sparse_dot(883-886)sparse_dot(952-955)add(708-740)add(708-712)add(946-950)cpp/src/dual_simplex/sparse_matrix.hpp (1)
Arow(51-52)
🔇 Additional comments (15)
cpp/src/dual_simplex/CMakeLists.txt (2)
13-13: LGTM: New source file for cut generation.The addition of
cuts.cppto the build is appropriate for the new cutting plane support.
37-37: Debug compile flag enabled - verify this is intentional for release.The
-g1compile option is now active for all dual simplex sources. While useful during development, this adds debug symbols to the binary which may not be desired in production builds. Consider gating this behind a CMake option or removing before merging if not needed.cpp/src/dual_simplex/branch_and_bound.hpp (1)
152-154: LGTM: Mutex for thread-safe LP modifications.The new
mutex_original_lp_member appropriately protects the original LP data structure during concurrent cut-generation operations in the branch-and-bound workflow.cpp/src/dual_simplex/solve.cpp (1)
11-11: LGTM: Include for basis solve infrastructure.The new include supports the advanced basis-based solving workflow used by the cut-generation integration.
cpp/src/mip/solver.cu (3)
171-171: LGTM: Propagating new cut passes setting.Correctly propagates the
max_cut_passesconfiguration from solver settings to branch-and-bound settings.
229-231: Commented-out status retrieval.The immediate branch-and-bound status retrieval is commented out. The status is still retrieved later at line 236 after primal heuristics complete, so this appears intentional. Verify this is the desired behavior for the concurrent execution model.
130-130: Debug code: LP-concurrent path disabled.The
0 &&prefix disables the LP fallback path when the problem is reduced to pure LP.- if (0 && context.problem_ptr->n_integer_vars == 0) { + if (context.problem_ptr->n_integer_vars == 0) {⛔ Skipped due to learnings
Learnt from: aliceb-nv Repo: NVIDIA/cuopt PR: 527 File: cpp/src/mip/diversity/lns/rins.cu:167-175 Timestamp: 2025-10-22T14:25:22.899Z Learning: In MIP (Mixed Integer Programming) problems in the cuOPT codebase, `n_integer_vars == 0` is impossible by definition—MIP problems must have at least one integer variable. If there are no integer variables, it would be a pure Linear Programming (LP) problem, not a MIP problem.Learnt from: CR Repo: NVIDIA/cuopt PR: 0 File: .github/.coderabbit_review_guide.md:0-0 Timestamp: 2025-11-25T10:20:49.822Z Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Ensure variables and constraints are accessed from the correct problem context (original vs presolve vs folded vs postsolve); verify index mapping consistency across problem transformationsLearnt from: CR Repo: NVIDIA/cuopt PR: 0 File: .github/.coderabbit_review_guide.md:0-0 Timestamp: 2025-11-25T10:20:49.822Z Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Validate algorithm correctness in optimization logic: simplex pivots, branch-and-bound decisions, routing heuristics, and constraint/objective handling must produce correct resultsLearnt from: CR Repo: NVIDIA/cuopt PR: 0 File: .github/.coderabbit_review_guide.md:0-0 Timestamp: 2025-11-25T10:20:49.822Z Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Validate correct initialization of variable bounds, constraint coefficients, and algorithm state before solving; ensure reset when transitioning between algorithm phases (presolve, simplex, diving, crossover)cpp/src/dual_simplex/branch_and_bound.cpp (5)
9-10: LGTM: New includes for cut generation infrastructure.The includes for
basis_solves.hppandcuts.hppare required for the new cutting plane workflow.
260-269: LGTM: Thread-safe access to original LP.Proper mutex locking around
original_lp_access when processing external solutions. The lock scope is appropriately narrow.
1080-1088: LGTM: Root LP solve with advanced basis.The switch to
solve_linear_program_with_advanced_basisenables basis tracking required for cut generation. The additional parameters (basis_update,basic_list,nonbasic_list) are correctly initialized.
1148-1149: Cut pool initialized with fixed column count.The
cut_poolis initialized withoriginal_lp_.num_colsat this point. After cuts are added,original_lp_.num_colsincreases (slack variables are added). Verify that the cut pool'soriginal_vars_remains correct throughout the cut generation loop, or if it needs to track the original column count separately.
678-678: LGTM: Branch call updated with integer infeasibility count.The branch call now correctly passes
leaf_num_fractionalfor tracking integer infeasibility at each node.cpp/src/dual_simplex/cuts.hpp (3)
44-52: Verify lifetime ofsettings_reference.The
cut_pool_tstoressettings_as a const reference. Ensure thesimplex_solver_settings_tobject outlives thecut_pool_tinstance to avoid dangling reference issues.
41-88: LGTM: Cut pool class design.The
cut_pool_tclass provides a clean interface for managing cuts with scoring, aging, and selection. The separation of cut storage, scoring metrics, and selection logic is well-structured.
194-218: LGTM: add_cuts and remove_cuts function signatures.The function signatures appropriately take all necessary state for modifying the LP with cuts and updating the basis, solution vectors, and variable statuses.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (8)
cpp/src/dual_simplex/cuts.cpp (4)
34-51: Division by zero risk when computing cut distance.Line 49 divides
cut_violationbycut_normwithout checking ifcut_normis zero or near-zero. If all cut coefficients are zero (or extremely small), this will cause division by zero or numerical instability.Based on coding guidelines: Check numerical stability to prevent division by zero/near-zero.
🔎 Apply this diff to add a safety check:
cut_violation = rhs_storage_[row] - cut_x; cut_norm = std::sqrt(dot); + if (cut_norm < std::numeric_limits<f_t>::epsilon() * std::max(f_t(1.0), std::abs(cut_violation))) { + cut_violation = 0.0; + cut_norm = 1.0; + } const f_t distance = cut_violation / cut_norm; return distance;
63-79: Division by zero risk in orthogonality calculation.Line 78 divides by
(norm_i * norm_j)without checking if either norm is zero or near-zero.Based on coding guidelines: Check numerical stability to prevent division by zero/near-zero.
🔎 Apply this diff to add a safety check:
f_t norm_i = cut_norms_[i]; f_t norm_j = cut_norms_[j]; + const f_t denom = norm_i * norm_j; + const f_t eps = std::numeric_limits<f_t>::epsilon(); + if (denom < eps) { + return 1.0; // treat degenerate cuts as orthogonal + } - return 1.0 - std::abs(dot) / (norm_i * norm_j); + return 1.0 - std::abs(dot) / denom;
205-266: Fix control flow logic for invalid MIR cuts.Lines 247-263: When
mir_status != 0(failure), execution falls through to lines 262-263 and adds an invalid cut to the pool. The function should skip adding the cut whenmir_status != 0.Based on coding guidelines: Validate algorithm correctness in optimization logic.
🔎 Apply this diff to fix the control flow:
i_t mir_status = mir.generate_cut(inequality, inequality_rhs, lp.upper, lp.lower, var_types, cut, cut_rhs); if (mir_status == 0) { f_t dot = 0.0; f_t cut_norm = 0.0; for (i_t k = 0; k < cut.i.size(); k++) { const i_t jj = cut.i[k]; const f_t aj = cut.x[k]; dot += aj * xstar[jj]; cut_norm += aj * aj; } if (dot >= cut_rhs) { continue; } + settings.log.printf("Adding MIR cut %d\n", i); + cut_pool_.add_cut(cut, cut_rhs); } - - settings.log.printf("Adding MIR cut %d\n", i); - cut_pool_.add_cut(cut, cut_rhs); }
543-548: Replaceexit(1)with proper error handling.Lines 545-547 call
exit(1)when encountering a negative lower bound, which terminates the entire process. This is not appropriate for library code in a production path.Based on coding guidelines: Verify error propagation from internal functions to user-facing APIs.
🔎 Recommended approach:
Change the function signature to return an error code:
i_t initialize(const lp_problem_t<i_t, f_t>& lp, const std::vector<i_t>& new_slacks, const std::vector<f_t>& xstar)Then replace the exit call:
for (i_t j = 0; j < lp.num_cols; j++) { if (lp.lower[j] < 0) { settings_.log.printf("Variable %d has negative lower bound %e\n", j, lp.lower[j]); - exit(1); + return -1; // Return error code }Update callers to check the return value and handle errors appropriately.
cpp/src/dual_simplex/branch_and_bound.cpp (3)
1230-1233: Replaceexit(1)with error return.Line 1232 calls
exit(1)whenadd_cuts_status != 0. Library code should not terminate the process.Based on coding guidelines: Verify error propagation from internal functions to user-facing APIs.
🔎 Apply this diff:
if (add_cuts_status != 0) { settings_.log.printf("Failed to add cuts\n"); - exit(1); + status_ = mip_exploration_status_t::NUMERICAL; + return set_final_solution(solution, root_objective_); }
1282-1285: Replaceexit(1)with error return.Lines 1283-1284 call
exit(1)when cut solve returns non-optimal status. This should be handled gracefully.Based on coding guidelines: Verify error propagation from internal functions to user-facing APIs.
🔎 Apply this diff:
if (cut_status != dual::status_t::OPTIMAL) { settings_.log.printf("Cut status %d\n", cut_status); - exit(1); + status_ = mip_exploration_status_t::NUMERICAL; + return set_final_solution(solution, root_objective_); }
1154-1324: Cutting plane loop lacks convergence detection and memory bounds enforcement.The loop (lines 1154-1325) runs
for (i_t cut_pass = 0; cut_pass < settings_.max_cut_passes; cut_pass++)with three problems:
No early termination on stagnation: The loop always runs
max_cut_passes(default 10) iterations even if cuts stop improving the objective or no improving cuts are generated. Standard cutting plane algorithms should exit early when no new cuts improve the bound or when cut violations fall below a threshold.Unbounded cut pool memory:
cut_poolaccumulates cuts across iterations with no enforcement of memory bounds. Whileage_cuts()anddrop_cuts()methods exist incut_pool_t, they are never called during the loop. Cut pools can grow without bound, risking exhaustion of memory on large problems with many variables/constraints.Hard exit on LP failures: Lines 1230-1232 and 1249-1250 call
exit(1)on LP infeasibility or cut-addition failures. This bypasses proper error propagation to user-facing APIs. Instead, log the failure and gracefully return a status code indicating the issue.Add convergence detection (e.g., check objective improvement between iterations or cut violation magnitude), implement cut pool size limits with periodic pruning, and replace
exit(1)calls with proper error returns.cpp/src/dual_simplex/cuts.hpp (1)
156-160: Type mismatch:nonbasic_mark_should bestd::vector<i_t>.Line 158:
nonbasic_mark_is declared asstd::vector<f_t>but is used as a marker array with values 0 or 1 (see line 139 in the constructor). It should bestd::vector<i_t>for consistency withx_mark_.🔎 Apply this diff:
- std::vector<f_t> nonbasic_mark_; + std::vector<i_t> nonbasic_mark_;
🧹 Nitpick comments (3)
cpp/src/dual_simplex/cuts.cpp (1)
314-322: Add safety check for near-zero cut norm.Line 321 divides by
std::sqrt(cut_norm)without checking ifcut_normis near-zero. While cuts should normally have non-zero coefficients, defensive programming suggests adding a check.Based on coding guidelines: Check numerical stability to prevent division by zero/near-zero.
🔎 Suggested enhancement:
f_t dot = cut_A.dot(xstar); f_t cut_norm = cut_A.norm2_squared(); + if (cut_norm < std::numeric_limits<f_t>::epsilon()) { + settings.log.printf("Cut %d has near-zero norm. Skipping\n", i); + continue; + } if (dot >= cut_A_rhs) { settings.log.printf("Cut %d is not violated. Skipping\n", i); continue; } cut_A_distance = (cut_A_rhs - dot) / std::sqrt(cut_norm);Apply similar check at line 340-346 for cut_B.
cpp/src/dual_simplex/sparse_matrix.cpp (1)
360-401: LGTM - append_rows implementation with minor suggestion.The
append_rowsimplementation correctly validates dimensions, resizes storage, and appends row data. The implementation is clean and handles the CSR structure properly.Optional enhancement for better diagnostics:
Consider returning the validation error earlier with more context:
if (C.n > n) { - printf("append_rows error: C.n %d n %d\n", C.n, n); + printf("append_rows error: source matrix has %d columns but target has only %d columns\n", C.n, n); return -1; }cpp/src/dual_simplex/cuts.hpp (1)
21-39: Consider adding epsilon tolerance to minimum_violation check.Line 33 checks
Cx[k] <= cut_rhs[k]with exact comparison. For numerical robustness, consider using an epsilon tolerance since floating-point arithmetic may have small rounding errors.Based on coding guidelines: Use epsilon comparisons for floating-point equality checks.
Suggested enhancement:
+ const f_t eps = std::numeric_limits<f_t>::epsilon() * 10; for (i_t k = 0; k < Cx.size(); k++) { - if (Cx[k] <= cut_rhs[k]) { + if (Cx[k] <= cut_rhs[k] + eps) { printf("C*x <= d for cut %d. C*x %e rhs %e\n", k, Cx[k], cut_rhs[k]); }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
cpp/src/dual_simplex/branch_and_bound.cpp(16 hunks)cpp/src/dual_simplex/cuts.cpp(1 hunks)cpp/src/dual_simplex/cuts.hpp(1 hunks)cpp/src/dual_simplex/sparse_matrix.cpp(3 hunks)cpp/src/dual_simplex/sparse_vector.cpp(2 hunks)cpp/src/dual_simplex/sparse_vector.hpp(2 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{cu,cuh,cpp,hpp,h}
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
**/*.{cu,cuh,cpp,hpp,h}: Track GPU device memory allocations and deallocations to prevent memory leaks; ensure cudaMalloc/cudaFree balance and cleanup of streams/events
Validate algorithm correctness in optimization logic: simplex pivots, branch-and-bound decisions, routing heuristics, and constraint/objective handling must produce correct results
Check numerical stability: prevent overflow/underflow, precision loss, division by zero/near-zero, and use epsilon comparisons for floating-point equality checks
Validate correct initialization of variable bounds, constraint coefficients, and algorithm state before solving; ensure reset when transitioning between algorithm phases (presolve, simplex, diving, crossover)
Ensure variables and constraints are accessed from the correct problem context (original vs presolve vs folded vs postsolve); verify index mapping consistency across problem transformations
For concurrent CUDA operations (barriers, async operations), explicitly create and manage dedicated streams instead of reusing the default stream; document stream lifecycle
Eliminate unnecessary host-device synchronization (cudaDeviceSynchronize) in hot paths that blocks GPU pipeline; use streams and events for async execution
Assess algorithmic complexity for large-scale problems (millions of variables/constraints); ensure O(n log n) or better complexity, not O(n²) or worse
Verify correct problem size checks before expensive GPU/CPU operations; prevent resource exhaustion on oversized problems
Identify assertions with overly strict numerical tolerances that fail on legitimate degenerate/edge cases (near-zero pivots, singular matrices, empty problems)
Ensure race conditions are absent in multi-GPU code and multi-threaded server implementations; verify proper synchronization of shared state
Refactor code duplication in solver components (3+ occurrences) into shared utilities; for GPU kernels, use templated device functions to avoid duplication
Check that hard-coded GPU de...
Files:
cpp/src/dual_simplex/sparse_vector.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/sparse_matrix.cppcpp/src/dual_simplex/sparse_vector.hppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.hpp
**/*.{cpp,hpp,h}
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
**/*.{cpp,hpp,h}: Check for unclosed file handles when reading MPS/QPS problem files; ensure RAII patterns or proper cleanup in exception paths
Validate input sanitization to prevent buffer overflows and resource exhaustion attacks; avoid unsafe deserialization of problem files
Prevent thread-unsafe use of global and static variables; use proper mutex/synchronization in server code accessing shared solver state
Files:
cpp/src/dual_simplex/sparse_vector.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/sparse_matrix.cppcpp/src/dual_simplex/sparse_vector.hppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.hpp
**/*.{cu,cpp,hpp,h}
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
Avoid inappropriate use of exceptions in performance-critical GPU operation paths; prefer error codes or CUDA error checking for latency-sensitive code
Files:
cpp/src/dual_simplex/sparse_vector.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/sparse_matrix.cppcpp/src/dual_simplex/sparse_vector.hppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.hpp
**/*.{h,hpp,py}
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
Verify C API does not break ABI stability (no struct layout changes, field reordering); maintain backward compatibility in Python and server APIs with deprecation warnings
Files:
cpp/src/dual_simplex/sparse_vector.hppcpp/src/dual_simplex/cuts.hpp
🧠 Learnings (19)
📓 Common learnings
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Reduce tight coupling between solver components (presolve, simplex, basis, barrier); increase modularity and reusability of optimization algorithms
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*test*.{cpp,cu,py} : Add tests for algorithm phase transitions: verify correct initialization of bounds and state when transitioning from presolve to simplex to diving to crossover
📚 Learning: 2025-12-04T04:11:12.640Z
Learnt from: chris-maes
Repo: NVIDIA/cuopt PR: 500
File: cpp/src/dual_simplex/scaling.cpp:68-76
Timestamp: 2025-12-04T04:11:12.640Z
Learning: In the cuOPT dual simplex solver, CSR/CSC matrices (including the quadratic objective matrix Q) are required to have valid dimensions and indices by construction. Runtime bounds checking in performance-critical paths like matrix scaling is avoided to prevent slowdowns. Validation is performed via debug-only check_matrix() calls wrapped in #ifdef CHECK_MATRIX.
Applied to files:
cpp/src/dual_simplex/sparse_vector.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/sparse_matrix.cppcpp/src/dual_simplex/branch_and_bound.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Validate algorithm correctness in optimization logic: simplex pivots, branch-and-bound decisions, routing heuristics, and constraint/objective handling must produce correct results
Applied to files:
cpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*test*.{cpp,cu,py} : Add tests for algorithm phase transitions: verify correct initialization of bounds and state when transitioning from presolve to simplex to diving to crossover
Applied to files:
cpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Validate correct initialization of variable bounds, constraint coefficients, and algorithm state before solving; ensure reset when transitioning between algorithm phases (presolve, simplex, diving, crossover)
Applied to files:
cpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Refactor code duplication in solver components (3+ occurrences) into shared utilities; for GPU kernels, use templated device functions to avoid duplication
Applied to files:
cpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Check numerical stability: prevent overflow/underflow, precision loss, division by zero/near-zero, and use epsilon comparisons for floating-point equality checks
Applied to files:
cpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/branch_and_bound.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Identify assertions with overly strict numerical tolerances that fail on legitimate degenerate/edge cases (near-zero pivots, singular matrices, empty problems)
Applied to files:
cpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Verify correct problem size checks before expensive GPU/CPU operations; prevent resource exhaustion on oversized problems
Applied to files:
cpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/branch_and_bound.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Ensure variables and constraints are accessed from the correct problem context (original vs presolve vs folded vs postsolve); verify index mapping consistency across problem transformations
Applied to files:
cpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cpp,hpp,h} : Check for unclosed file handles when reading MPS/QPS problem files; ensure RAII patterns or proper cleanup in exception paths
Applied to files:
cpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/branch_and_bound.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*test*.{cpp,cu,py} : Test with free variables, singleton problems, and extreme problem dimensions near resource limits to validate edge case handling
Applied to files:
cpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/branch_and_bound.cpp
📚 Learning: 2025-12-04T20:09:09.264Z
Learnt from: chris-maes
Repo: NVIDIA/cuopt PR: 602
File: cpp/src/linear_programming/solve.cu:732-742
Timestamp: 2025-12-04T20:09:09.264Z
Learning: In cpp/src/linear_programming/solve.cu, the barrier solver does not currently return INFEASIBLE or UNBOUNDED status. It only returns OPTIMAL, TIME_LIMIT, NUMERICAL_ISSUES, or CONCURRENT_LIMIT.
Applied to files:
cpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*test*.{cpp,cu,py} : Write tests validating numerical correctness of optimization results (not just 'runs without error'); test degenerate cases (infeasible, unbounded, empty, singleton problems)
Applied to files:
cpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/branch_and_bound.cpp
📚 Learning: 2025-10-22T14:25:22.899Z
Learnt from: aliceb-nv
Repo: NVIDIA/cuopt PR: 527
File: cpp/src/mip/diversity/lns/rins.cu:167-175
Timestamp: 2025-10-22T14:25:22.899Z
Learning: In MIP (Mixed Integer Programming) problems in the cuOPT codebase, `n_integer_vars == 0` is impossible by definition—MIP problems must have at least one integer variable. If there are no integer variables, it would be a pure Linear Programming (LP) problem, not a MIP problem.
Applied to files:
cpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Reduce tight coupling between solver components (presolve, simplex, basis, barrier); increase modularity and reusability of optimization algorithms
Applied to files:
cpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-12-03T23:29:26.391Z
Learnt from: chris-maes
Repo: NVIDIA/cuopt PR: 500
File: cpp/src/dual_simplex/sparse_matrix.cpp:519-524
Timestamp: 2025-12-03T23:29:26.391Z
Learning: In cpp/src/dual_simplex/sparse_matrix.cpp, the check_matrix() function is debug/diagnostic code (wrapped in #ifdef CHECK_MATRIX) that intentionally prints errors without necessarily returning early. The return codes from this debug code are not actively checked; the purpose is to print all validation errors in one pass for better diagnostics.
Applied to files:
cpp/src/dual_simplex/sparse_matrix.cppcpp/src/dual_simplex/branch_and_bound.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Assess algorithmic complexity for large-scale problems (millions of variables/constraints); ensure O(n log n) or better complexity, not O(n²) or worse
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cpp,hpp,h} : Avoid inappropriate use of exceptions in performance-critical GPU operation paths; prefer error codes or CUDA error checking for latency-sensitive code
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cpp
🧬 Code graph analysis (5)
cpp/src/dual_simplex/sparse_vector.cpp (4)
cpp/src/dual_simplex/sparse_vector.hpp (6)
sparse_vector_t(21-21)sparse_vector_t(23-23)sparse_vector_t(25-25)sparse_vector_t(27-27)sparse_vector_t(29-29)x(44-44)cpp/src/dual_simplex/sparse_matrix.cpp (2)
dot(851-860)dot(851-851)cpp/src/dual_simplex/vector_math.hpp (1)
dot(42-42)cpp/src/dual_simplex/vector_math.cpp (3)
dot(48-57)dot(48-48)dot(181-181)
cpp/src/dual_simplex/cuts.cpp (2)
cpp/src/dual_simplex/cuts.hpp (13)
cut(57-57)cut_storage_(68-68)row(71-71)row(72-72)i(73-73)x_relax(59-59)a(181-187)lp(96-104)lp(107-115)lp(117-122)lp(144-154)lp(177-179)lp(189-192)cpp/src/dual_simplex/sparse_vector.hpp (3)
p(40-40)p(42-42)x(44-44)
cpp/src/dual_simplex/sparse_matrix.cpp (1)
cpp/src/dual_simplex/sparse_matrix.hpp (5)
C(140-140)new_nz(48-48)nz(78-78)c(143-143)j(63-63)
cpp/src/dual_simplex/sparse_vector.hpp (1)
cpp/src/dual_simplex/sparse_matrix.hpp (3)
row(87-87)x(70-70)x(74-74)
cpp/src/dual_simplex/branch_and_bound.cpp (4)
cpp/src/dual_simplex/solve.hpp (4)
solve_linear_program_with_advanced_basis(53-62)compute_user_objective(37-37)compute_user_objective(40-40)compute_objective(34-34)cpp/src/dual_simplex/presolve.hpp (2)
uncrush_primal_solution(160-163)bound_strengthening(185-190)cpp/src/dual_simplex/cuts.hpp (3)
minimum_violation(22-39)minimum_violation(22-24)add_cuts(207-217)cpp/src/dual_simplex/phase2.hpp (1)
dual_phase2_with_advanced_basis(61-73)
🔇 Additional comments (9)
cpp/src/dual_simplex/sparse_vector.hpp (2)
28-29: LGTM - CSR constructor declaration.The new constructor from CSR matrix row complements the existing CSC-based constructor and aligns with the expanded CSR support in the codebase.
43-44: LGTM - dot product method declaration.The dot product method declaration is clean and follows the existing API patterns in the class.
cpp/src/dual_simplex/sparse_vector.cpp (2)
31-44: LGTM - CSR constructor implementation.The CSR row constructor implementation correctly mirrors the CSC column constructor pattern. It properly initializes the sparse vector dimension, reserves storage, and populates from the CSR row data.
Based on learnings: Runtime bounds checking is avoided in performance-critical paths; validation is performed via debug-only checks.
124-133: LGTM - dot product implementation.The dot product implementation is straightforward and efficient, accumulating the product of sparse coefficients with the corresponding dense vector elements.
cpp/src/dual_simplex/sparse_matrix.cpp (3)
403-427: LGTM - append_row implementation.The
append_rowimplementation correctly handles appending a single sparse vector as a CSR row, properly updating dimensions and storage.
570-573: Good addition - CSC column pointer bounds check.The added bounds check for
col_startsize prevents potential out-of-bounds access during matrix validation. This is a good defensive programming practice.
632-632: Good addition - CSR column index bounds check.The added bounds check for column indices ensures CSR matrix integrity during validation.
Based on learnings: check_matrix() is debug/diagnostic code that prints all validation errors for better diagnostics.
cpp/src/dual_simplex/branch_and_bound.cpp (2)
9-10: LGTM - New includes for cut framework.The new includes for
basis_solves.hppandcuts.hppare necessary for the extended cutting plane functionality.
1080-1088: LGTM - Enhanced root LP solve with advanced basis.The change from
solve_linear_program_advancedtosolve_linear_program_with_advanced_basiswith explicit basis tracking (basis_update,basic_list,nonbasic_list) enables the warm-start dual simplex flow described in the PR objectives.
|
🔔 Hi @anandhkb @chris-maes, this pull request has had no activity for 7 days. Please update or let us know if it can be closed. Thank you! If this is an "epic" issue, then please add the "epic" label to this issue. |
1 similar comment
|
🔔 Hi @anandhkb @chris-maes, this pull request has had no activity for 7 days. Please update or let us know if it can be closed. Thank you! If this is an "epic" issue, then please add the "epic" label to this issue. |
…b-ball and swath1. Add reliability branching as an option. Finally seeing good performance on swath1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Fix all issues with AI Agents 🤖
In @cpp/src/dual_simplex/pseudo_costs.cpp:
- Around line 428-445: The mutex is being unlocked twice and there's a potential
out-of-bounds read of score[select] when no fractional variables were chosen:
remove the spurious mutex.unlock() call at the end of the function (do not
unlock here since it was already unlocked earlier) and add a defensive check
after the selection loop (check if select == -1 or fractional.empty()) before
using score[select]; if no selection was made, log a safe message (e.g.,
indicate no fractional var selected or use a default) and return early with
branch_var (or a sentinel) to avoid indexing score[-1]; reference the
variables/function names branch_var, select, fractional, score, solution and the
final log.printf call when making the changes.
In @cpp/src/mip/diversity/recombiners/sub_mip.cuh:
- Line 89: The line forcing run_sub_mip to false uses a hardcoded "0 &&" and
should be replaced with a real toggle: introduce a boolean feature flag (e.g.,
context.settings.enable_sub_mip_recombiner) and set run_sub_mip =
context.settings.enable_sub_mip_recombiner && fixed_problem.n_integer_vars > 8,
or if the feature is permanently disabled remove the run_sub_mip variable and
the associated sub-MIP code block (the logic guarded by run_sub_mip in
sub_mip.cuh) or wrap it in a compile-time guard (e.g., #ifdef ENABLE_SUB_MIP) so
the intent is explicit and maintainable.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
cpp/src/mip/diversity/recombiners/sub_mip.cuh (1)
188-188: Verify return logic when sub-MIP is disabled.When
run_sub_mipis false,branch_and_bound_solutionremains at its initialized state, and line 188 returns based on whether the default objective value is NaN. This may not correctly represent whether the recombination found a valid solution.When sub-MIP is disabled, the function should return
falseto indicate no valid solution was found via sub-MIP, or adjust the logic to account for theoffspring.round_nearest()fallback on line 145.As per coding guidelines, ensure algorithm correctness and proper initialization of state before use.
Proposed fix
-return std::make_pair(offspring, !std::isnan(branch_and_bound_solution.objective)); +// When sub-MIP is disabled, indicate that no sub-MIP solution was found +return std::make_pair(offspring, run_sub_mip && !std::isnan(branch_and_bound_solution.objective));
🧹 Nitpick comments (10)
cpp/src/dual_simplex/phase2.cpp (3)
2085-2089: Usesettings.log.printffor consistency with the rest of the codebase.This diagnostic output uses raw
printfwhile the rest of the file usessettings.log.printf, which respects log filtering and configuration.🔎 Proposed fix
if (primal_infeas > settings.primal_tol) { - printf("Primal infeasibility %e. Info %d\n", primal_infeas, info); + settings.log.printf("Primal infeasibility %e. Info %d\n", primal_infeas, info); }
2333-2348: Simplify redundant logic and clean up commented-out code.The
fix_neededvariable and its check are redundant since they occur within the same loop iteration. The commented-outprintfstatements should be removed or converted to proper debug macros (similar to#ifdef STEEPEST_EDGE_DEBUGused elsewhere).🔎 Proposed fix
// Check that none of the basic variables have a steepest edge that is nonpositive for (i_t k = 0; k < m; k++) { const i_t j = basic_list[k]; - bool fix_needed = false; if (delta_y_steepest_edge[j] <= 0.0) { - fix_needed = true; - //printf("Basic variable %d has a nonpositive steepest edge %e\n", j, delta_y_steepest_edge[j]); +#ifdef STEEPEST_EDGE_DEBUG + settings.log.printf("Basic variable %d has a nonpositive steepest edge %e\n", j, delta_y_steepest_edge[j]); +#endif delta_y_steepest_edge[j] = 1e-4; } - if (fix_needed) { - //printf("Basic variable had nonpositive steepest edge\n"); - } }
2454-2471: Dead code should be removed or properly guarded.The condition
if (0 && primal_infeasibility > ...)is always false, making this entire block dead code. If this is intended for future debugging, wrap it in a preprocessor guard; otherwise, remove it to reduce confusion.🔎 Proposed fix - Remove dead code
- //primal_infeasibility = phase2::compute_initial_primal_infeasibilities( - // lp, settings, basic_list, x, squared_infeasibilities, infeasibility_indices); - if (0 && primal_infeasibility > settings.primal_tol) { - - const i_t nz = infeasibility_indices.size(); - for (i_t k = 0; k < nz; ++k) { - const i_t j = infeasibility_indices[k]; - const f_t squared_infeas = squared_infeasibilities[j]; - const f_t val = squared_infeas / delta_y_steepest_edge[j]; - if (squared_infeas >= 0.0 && delta_y_steepest_edge[j] < 0.0) { - printf("Iter %d potential leaving %d val %e squared infeas %e delta_y_steepest_edge %e\n", iter, j, val, squared_infeas, delta_y_steepest_edge[j]); - delta_y_steepest_edge[j] = 1e-4; - } - } - - //printf("No leaving variable. Updated primal infeasibility: %e\n", primal_infeasibility); - continue; - }cpp/src/mip/diversity/recombiners/sub_mip.cuh (1)
92-138: Consider removing or conditionally compiling unreachable code.Lines 92-138 are now unreachable since
run_sub_mipis always false. Dead code increases maintenance burden and reduces readability. If sub-MIP recombination is being permanently disabled, remove this code. If it's temporary, consider using preprocessor directives or feature flags as suggested above.cpp/src/dual_simplex/sparse_vector.cpp (1)
236-258: Variablenshadows member variable, causing confusion.Line 242 declares
const i_t n = x.size()which shadows the member variablen(the vector dimension). While the code functions correctly becausey.nis assigned fromthis->n(line 239) before the shadow, this is confusing and error-prone.🔎 Rename local variable to avoid shadowing
template <typename i_t, typename f_t> void sparse_vector_t<i_t, f_t>::squeeze(sparse_vector_t<i_t, f_t>& y) const { y.n = n; i_t nz = 0; - const i_t n = x.size(); - for (i_t k = 0; k < n; k++) { + const i_t num_entries = x.size(); + for (i_t k = 0; k < num_entries; k++) { if (x[k] != 0.0) { nz++; } } y.i.reserve(nz); y.x.reserve(nz); y.i.clear(); y.x.clear(); - for (i_t k = 0; k < n; k++) { + for (i_t k = 0; k < num_entries; k++) { if (x[k] != 0.0) { y.i.push_back(i[k]); y.x.push_back(x[k]); } } }cpp/src/dual_simplex/pseudo_costs.cpp (1)
159-159: Replace rawprintfwith structured logging.This debug output should use
settings.log.printforsettings.log.debugfor consistency with the rest of the codebase and to respect the logging configuration.🔎 Use logger instead of printf
- printf("Trial branching on variable %d. Lo: %e Up: %e. Iter %d. Status %d. Obj %e\n", branch_var, child_problem.lower[branch_var], child_problem.upper[branch_var], iter, status, compute_objective(child_problem, solution.x)); + settings.log.debug("Trial branching on variable %d. Lo: %e Up: %e. Iter %d. Status %d. Obj %e\n", branch_var, child_problem.lower[branch_var], child_problem.upper[branch_var], iter, static_cast<int>(status), compute_objective(child_problem, solution.x));cpp/src/dual_simplex/branch_and_bound.cpp (3)
486-510: Debug file I/O should be wrapped in debug macro or removed.This block writes
solution.datunconditionally in production builds. Consider wrapping it in a debug macro or removing it before release. Additionally, ensure RAII for file handle safety.🔎 Wrap in debug macro
+#ifdef DEBUG_SOLUTION_OUTPUT FILE* fid = NULL; fid = fopen("solution.dat", "w"); if (fid != NULL) { // ... writing logic ... fclose(fid); } +#endif
1222-1308: Debug solution file loading should be wrapped in debug macro.This large block that reads
solution.datis debug/development code that should not run in production. It also containsexit(1)calls (line 1394) that will terminate the application.🔎 Wrap in debug macro and remove exit(1)
+#ifdef DEBUG_SOLUTION_VALIDATION printf("Trying to open solution.dat\n"); FILE* fid = NULL; fid = fopen("solution.dat", "r"); // ... rest of solution loading ... if (Cx[k] > cut_rhs[k] + 1e-6) { printf("Cut %d is violated by saved solution. Cx %e cut_rhs %e\n", k, Cx[k], cut_rhs[k]); - exit(1); + break; // Exit validation loop on error } // ... +#endif
616-624: Debug printf statements should use structured logging.Multiple raw
printfcalls in this block should use the logger for consistency and to respect logging configuration.cpp/src/dual_simplex/cuts.hpp (1)
307-309: Add namespace closing brace identifier.Minor style suggestion for clarity in large codebases.
🔎 Add namespace comment
-} +} // namespace cuopt::linear_programming::dual_simplex
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (12)
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/branch_and_bound.hppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hppcpp/src/dual_simplex/dense_matrix.hppcpp/src/dual_simplex/phase2.cppcpp/src/dual_simplex/presolve.cppcpp/src/dual_simplex/pseudo_costs.cppcpp/src/dual_simplex/pseudo_costs.hppcpp/src/dual_simplex/sparse_vector.cppcpp/src/dual_simplex/sparse_vector.hppcpp/src/mip/diversity/recombiners/sub_mip.cuh
🚧 Files skipped from review as they are similar to previous changes (2)
- cpp/src/dual_simplex/sparse_vector.hpp
- cpp/src/dual_simplex/branch_and_bound.hpp
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{cu,cuh}
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
**/*.{cu,cuh}: Every CUDA kernel launch and memory operation must have error checking with CUDA_CHECK or equivalent verification
Avoid reinventing functionality already available in Thrust, CCCL, or RMM libraries; prefer standard library utilities over custom implementations
Files:
cpp/src/mip/diversity/recombiners/sub_mip.cuh
**/*.{cu,cuh,cpp,hpp,h}
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
**/*.{cu,cuh,cpp,hpp,h}: Track GPU device memory allocations and deallocations to prevent memory leaks; ensure cudaMalloc/cudaFree balance and cleanup of streams/events
Validate algorithm correctness in optimization logic: simplex pivots, branch-and-bound decisions, routing heuristics, and constraint/objective handling must produce correct results
Check numerical stability: prevent overflow/underflow, precision loss, division by zero/near-zero, and use epsilon comparisons for floating-point equality checks
Validate correct initialization of variable bounds, constraint coefficients, and algorithm state before solving; ensure reset when transitioning between algorithm phases (presolve, simplex, diving, crossover)
Ensure variables and constraints are accessed from the correct problem context (original vs presolve vs folded vs postsolve); verify index mapping consistency across problem transformations
For concurrent CUDA operations (barriers, async operations), explicitly create and manage dedicated streams instead of reusing the default stream; document stream lifecycle
Eliminate unnecessary host-device synchronization (cudaDeviceSynchronize) in hot paths that blocks GPU pipeline; use streams and events for async execution
Assess algorithmic complexity for large-scale problems (millions of variables/constraints); ensure O(n log n) or better complexity, not O(n²) or worse
Verify correct problem size checks before expensive GPU/CPU operations; prevent resource exhaustion on oversized problems
Identify assertions with overly strict numerical tolerances that fail on legitimate degenerate/edge cases (near-zero pivots, singular matrices, empty problems)
Ensure race conditions are absent in multi-GPU code and multi-threaded server implementations; verify proper synchronization of shared state
Refactor code duplication in solver components (3+ occurrences) into shared utilities; for GPU kernels, use templated device functions to avoid duplication
Check that hard-coded GPU de...
Files:
cpp/src/mip/diversity/recombiners/sub_mip.cuhcpp/src/dual_simplex/presolve.cppcpp/src/dual_simplex/dense_matrix.hppcpp/src/dual_simplex/pseudo_costs.hppcpp/src/dual_simplex/pseudo_costs.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/phase2.cppcpp/src/dual_simplex/sparse_vector.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hpp
**/*.{cpp,hpp,h}
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
**/*.{cpp,hpp,h}: Check for unclosed file handles when reading MPS/QPS problem files; ensure RAII patterns or proper cleanup in exception paths
Validate input sanitization to prevent buffer overflows and resource exhaustion attacks; avoid unsafe deserialization of problem files
Prevent thread-unsafe use of global and static variables; use proper mutex/synchronization in server code accessing shared solver state
Files:
cpp/src/dual_simplex/presolve.cppcpp/src/dual_simplex/dense_matrix.hppcpp/src/dual_simplex/pseudo_costs.hppcpp/src/dual_simplex/pseudo_costs.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/phase2.cppcpp/src/dual_simplex/sparse_vector.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hpp
**/*.{cu,cpp,hpp,h}
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
Avoid inappropriate use of exceptions in performance-critical GPU operation paths; prefer error codes or CUDA error checking for latency-sensitive code
Files:
cpp/src/dual_simplex/presolve.cppcpp/src/dual_simplex/dense_matrix.hppcpp/src/dual_simplex/pseudo_costs.hppcpp/src/dual_simplex/pseudo_costs.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/phase2.cppcpp/src/dual_simplex/sparse_vector.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hpp
**/*.{h,hpp,py}
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
Verify C API does not break ABI stability (no struct layout changes, field reordering); maintain backward compatibility in Python and server APIs with deprecation warnings
Files:
cpp/src/dual_simplex/dense_matrix.hppcpp/src/dual_simplex/pseudo_costs.hppcpp/src/dual_simplex/cuts.hpp
🧠 Learnings (19)
📓 Common learnings
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Reduce tight coupling between solver components (presolve, simplex, basis, barrier); increase modularity and reusability of optimization algorithms
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Validate algorithm correctness in optimization logic: simplex pivots, branch-and-bound decisions, routing heuristics, and constraint/objective handling must produce correct results
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Validate correct initialization of variable bounds, constraint coefficients, and algorithm state before solving; ensure reset when transitioning between algorithm phases (presolve, simplex, diving, crossover)
📚 Learning: 2025-10-22T14:25:22.899Z
Learnt from: aliceb-nv
Repo: NVIDIA/cuopt PR: 527
File: cpp/src/mip/diversity/lns/rins.cu:167-175
Timestamp: 2025-10-22T14:25:22.899Z
Learning: In MIP (Mixed Integer Programming) problems in the cuOPT codebase, `n_integer_vars == 0` is impossible by definition—MIP problems must have at least one integer variable. If there are no integer variables, it would be a pure Linear Programming (LP) problem, not a MIP problem.
Applied to files:
cpp/src/mip/diversity/recombiners/sub_mip.cuhcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Validate algorithm correctness in optimization logic: simplex pivots, branch-and-bound decisions, routing heuristics, and constraint/objective handling must produce correct results
Applied to files:
cpp/src/mip/diversity/recombiners/sub_mip.cuhcpp/src/dual_simplex/presolve.cppcpp/src/dual_simplex/pseudo_costs.hppcpp/src/dual_simplex/pseudo_costs.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/phase2.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Validate correct initialization of variable bounds, constraint coefficients, and algorithm state before solving; ensure reset when transitioning between algorithm phases (presolve, simplex, diving, crossover)
Applied to files:
cpp/src/mip/diversity/recombiners/sub_mip.cuhcpp/src/dual_simplex/presolve.cppcpp/src/dual_simplex/pseudo_costs.hppcpp/src/dual_simplex/pseudo_costs.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/phase2.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Ensure variables and constraints are accessed from the correct problem context (original vs presolve vs folded vs postsolve); verify index mapping consistency across problem transformations
Applied to files:
cpp/src/mip/diversity/recombiners/sub_mip.cuhcpp/src/dual_simplex/presolve.cppcpp/src/dual_simplex/pseudo_costs.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/phase2.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-12-04T20:09:09.264Z
Learnt from: chris-maes
Repo: NVIDIA/cuopt PR: 602
File: cpp/src/linear_programming/solve.cu:732-742
Timestamp: 2025-12-04T20:09:09.264Z
Learning: In cpp/src/linear_programming/solve.cu, the barrier solver does not currently return INFEASIBLE or UNBOUNDED status. It only returns OPTIMAL, TIME_LIMIT, NUMERICAL_ISSUES, or CONCURRENT_LIMIT.
Applied to files:
cpp/src/mip/diversity/recombiners/sub_mip.cuhcpp/src/dual_simplex/presolve.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Reduce tight coupling between solver components (presolve, simplex, basis, barrier); increase modularity and reusability of optimization algorithms
Applied to files:
cpp/src/dual_simplex/presolve.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/phase2.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*test*.{cpp,cu,py} : Add tests for algorithm phase transitions: verify correct initialization of bounds and state when transitioning from presolve to simplex to diving to crossover
Applied to files:
cpp/src/dual_simplex/pseudo_costs.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/phase2.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*test*.{cpp,cu,py} : Test with free variables, singleton problems, and extreme problem dimensions near resource limits to validate edge case handling
Applied to files:
cpp/src/dual_simplex/pseudo_costs.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Assess algorithmic complexity for large-scale problems (millions of variables/constraints); ensure O(n log n) or better complexity, not O(n²) or worse
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Refactor code duplication in solver components (3+ occurrences) into shared utilities; for GPU kernels, use templated device functions to avoid duplication
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Identify assertions with overly strict numerical tolerances that fail on legitimate degenerate/edge cases (near-zero pivots, singular matrices, empty problems)
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cpp,hpp,h} : Check for unclosed file handles when reading MPS/QPS problem files; ensure RAII patterns or proper cleanup in exception paths
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Verify correct problem size checks before expensive GPU/CPU operations; prevent resource exhaustion on oversized problems
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*test*.{cpp,cu,py} : Write tests validating numerical correctness of optimization results (not just 'runs without error'); test degenerate cases (infeasible, unbounded, empty, singleton problems)
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-12-03T23:29:26.391Z
Learnt from: chris-maes
Repo: NVIDIA/cuopt PR: 500
File: cpp/src/dual_simplex/sparse_matrix.cpp:519-524
Timestamp: 2025-12-03T23:29:26.391Z
Learning: In cpp/src/dual_simplex/sparse_matrix.cpp, the check_matrix() function is debug/diagnostic code (wrapped in #ifdef CHECK_MATRIX) that intentionally prints errors without necessarily returning early. The return codes from this debug code are not actively checked; the purpose is to print all validation errors in one pass for better diagnostics.
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Check numerical stability: prevent overflow/underflow, precision loss, division by zero/near-zero, and use epsilon comparisons for floating-point equality checks
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cpp,hpp,h} : Avoid inappropriate use of exceptions in performance-critical GPU operation paths; prefer error codes or CUDA error checking for latency-sensitive code
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cpp
📚 Learning: 2025-12-04T04:11:12.640Z
Learnt from: chris-maes
Repo: NVIDIA/cuopt PR: 500
File: cpp/src/dual_simplex/scaling.cpp:68-76
Timestamp: 2025-12-04T04:11:12.640Z
Learning: In the cuOPT dual simplex solver, CSR/CSC matrices (including the quadratic objective matrix Q) are required to have valid dimensions and indices by construction. Runtime bounds checking in performance-critical paths like matrix scaling is avoided to prevent slowdowns. Validation is performed via debug-only check_matrix() calls wrapped in #ifdef CHECK_MATRIX.
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cpp
🧬 Code graph analysis (3)
cpp/src/dual_simplex/pseudo_costs.cpp (1)
cpp/src/dual_simplex/pseudo_costs.hpp (4)
lp(50-58)fractional(46-48)fractional(60-61)num_initialized_down(41-44)
cpp/src/dual_simplex/phase2.cpp (1)
cpp/src/dual_simplex/crossover.cpp (2)
primal_infeasibility(159-199)primal_infeasibility(159-162)
cpp/src/dual_simplex/sparse_vector.cpp (1)
cpp/src/dual_simplex/sparse_vector.hpp (10)
sparse_vector_t(21-21)sparse_vector_t(23-23)sparse_vector_t(25-25)sparse_vector_t(27-27)sparse_vector_t(29-29)A(33-33)x(44-44)x_dense(35-35)x_dense(38-38)y(54-54)
🪛 Cppcheck (2.19.0)
cpp/src/dual_simplex/pseudo_costs.cpp
[warning] 440-440: Array index -1 is out of bounds.
(negativeContainerIndex)
cpp/src/dual_simplex/branch_and_bound.cpp
[error] 497-497: Resource leak
(resourceLeak)
🔇 Additional comments (11)
cpp/src/dual_simplex/presolve.cpp (1)
214-218: LGTM! Clear diagnostic improvement.The updated log format explicitly labels the bound values and shows their relationship, making infeasibility diagnostics easier to understand during presolve bound strengthening.
cpp/src/dual_simplex/dense_matrix.hpp (1)
21-22: LGTM! Useful addition for uniform initialization.The new constructor correctly extends the existing pattern by allowing initialization with a specific fill value rather than defaulting to 0.0. The implementation using
std::vector(rows * cols, value)is safe and consistent with the existing constructor.cpp/src/dual_simplex/phase2.cpp (4)
2017-2018: LGTM - Signature change for diagnostic context.The addition of the
infoparameter provides useful call-site identification for debugging primal infeasibility issues.
2473-2488: LGTM - Call site correctly updated.The call to
prepare_optimalityis correctly updated with the newinfoparameter.
2640-2655: LGTM - Call site correctly updated.The call to
prepare_optimalityis correctly updated with the newinfoparameter.
2678-2693: LGTM - Call site correctly updated.The call to
prepare_optimalityis correctly updated with the newinfoparameter.cpp/src/dual_simplex/pseudo_costs.hpp (1)
50-59: LGTM!The new
reliable_variable_selectionmethod declaration is well-structured. Parameters are appropriately passed by const reference, and the signature provides all necessary context for reliability-based branching decisions.cpp/src/dual_simplex/sparse_vector.cpp (2)
31-44: LGTM!The CSR row constructor correctly extracts the row data, mirroring the existing CSC column constructor pattern. The implementation properly handles row bounds and reserves appropriate capacity.
124-133: LGTM!The sparse-dense dot product is correctly implemented with straightforward accumulation over nonzero indices.
cpp/src/dual_simplex/cuts.hpp (1)
66-115: LGTM!The
cut_pool_tclass is well-designed with clear separation between public interface and private implementation details. The methods for scoring, selecting, and managing cuts follow a logical workflow.cpp/src/dual_simplex/cuts.cpp (1)
57-64: Replaceexit(1)with error return inminimum_violation.The
exit(1)at line 59 will terminate the entire process. Library code should return an error or propagate an exception instead.🔎 Return error indicator instead of exiting
f_t min_cut_violation = inf; for (i_t k = 0; k < Cx.size(); k++) { if (Cx[k] <= cut_rhs[k]) { printf("C*x <= d for cut %d. C*x %e rhs %e\n", k, Cx[k], cut_rhs[k]); - exit(1); + return -inf; // Return sentinel value indicating invalid cut } min_cut_violation = std::min(min_cut_violation, Cx[k] - cut_rhs[k]); } return min_cut_violation;Based on coding guidelines: Verify error propagation from internal functions to user-facing APIs.
⛔ Skipped due to learnings
Learnt from: CR Repo: NVIDIA/cuopt PR: 0 File: .github/.coderabbit_review_guide.md:0-0 Timestamp: 2025-11-25T10:20:49.822Z Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Validate algorithm correctness in optimization logic: simplex pivots, branch-and-bound decisions, routing heuristics, and constraint/objective handling must produce correct resultsLearnt from: CR Repo: NVIDIA/cuopt PR: 0 File: .github/.coderabbit_review_guide.md:0-0 Timestamp: 2025-11-25T10:20:49.822Z Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Validate correct initialization of variable bounds, constraint coefficients, and algorithm state before solving; ensure reset when transitioning between algorithm phases (presolve, simplex, diving, crossover)Learnt from: CR Repo: NVIDIA/cuopt PR: 0 File: .github/.coderabbit_review_guide.md:0-0 Timestamp: 2025-11-25T10:20:49.822Z Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Identify assertions with overly strict numerical tolerances that fail on legitimate degenerate/edge cases (near-zero pivots, singular matrices, empty problems)
| fixed_problem.check_problem_representation(true); | ||
| // brute force rounding threshold is 8 | ||
| const bool run_sub_mip = fixed_problem.n_integer_vars > 8; | ||
| const bool run_sub_mip = 0 && fixed_problem.n_integer_vars > 8; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Replace hardcoded 0 && with a configurable feature flag or remove dead code.
Hardcoding 0 && to disable functionality is a code smell that reduces maintainability. Consider one of these alternatives:
- Add a configurable setting (e.g.,
context.settings.enable_sub_mip_recombiner) to make this runtime-controllable - Remove or comment out the dead code (lines 92-138) if sub-MIP is being permanently disabled
- Use preprocessor directives (
#ifdef ENABLE_SUB_MIP) if this is experimental/conditional compilation
Proposed fix using a feature flag
-const bool run_sub_mip = 0 && fixed_problem.n_integer_vars > 8;
+const bool run_sub_mip = context.settings.enable_sub_mip_recombiner && fixed_problem.n_integer_vars > 8;Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In @cpp/src/mip/diversity/recombiners/sub_mip.cuh around line 89, The line
forcing run_sub_mip to false uses a hardcoded "0 &&" and should be replaced with
a real toggle: introduce a boolean feature flag (e.g.,
context.settings.enable_sub_mip_recombiner) and set run_sub_mip =
context.settings.enable_sub_mip_recombiner && fixed_problem.n_integer_vars > 8,
or if the feature is permanently disabled remove the run_sub_mip variable and
the associated sub-MIP code block (the logic guarded by run_sub_mip in
sub_mip.cuh) or wrap it in a compile-time guard (e.g., #ifdef ENABLE_SUB_MIP) so
the intent is explicit and maintainable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 16
🤖 Fix all issues with AI Agents
In @cpp/src/dual_simplex/basis_updates.cpp:
- Line 1172: The constructor call for sparse_vector_t lacks explicit template
parameters so the compiler cannot deduce i_t and f_t; change the declaration of
rhs to use explicit template arguments (e.g., sparse_vector_t<i_t,f_t> rhs(WT,
h)) where i_t and f_t are the concrete index/value types used elsewhere in the
file, ensuring the types match the rest of the basis update code that uses
sparse_vector_t.
- Around line 1275-1278: The unconditional exit(1) in the consistency check
comparing L_nz and predicted_nz must be replaced with graceful error
propagation: remove the printf/exit and instead construct and propagate an error
(e.g., throw a std::runtime_error with a descriptive message including L_nz and
predicted_nz, or return a failure status/enum from the enclosing function) so
callers can handle it; update the enclosing function (the function in
basis_updates.cpp that contains this check) to propagate that error upward
(adjust its return type or add exception handling) and ensure callers translate
it to a user-facing error/log rather than terminating the process.
In @cpp/src/dual_simplex/branch_and_bound.cpp:
- Around line 1834-1837: Replace the abrupt process termination in the
bound-strengthening failure block: instead of calling exit(1) when feasible is
false (the block containing settings_.log.printf("Bound strengthening
failed\n")), set status_ = mip_exploration_status_t::NUMERICAL and return
set_final_solution(solution, root_objective_); so the error propagates via the
function's return path rather than terminating the process.
- Around line 1815-1818: Replace the process-terminating call in the add_cuts
error path: instead of calling exit(1) when add_cuts_status != 0, return an
appropriate error status (e.g., return mip_status_t::NUMERICAL) or propagate the
add_cuts_status up to the caller; update the code immediately after the
add_cuts_status check (the block using settings_.log.printf("Failed to add
cuts\n")) to return the error rather than exiting so library callers can handle
the failure.
- Around line 1787-1791: The code currently calls exit(1) when a saved solution
violates a cut (check using Cx[k] and cut_rhs[k], with index k); replace this
process-terminating call with proper error propagation: return an error status
(e.g., false or an error code/exception) from the current function and propagate
it up to the caller so the caller can handle the failure; update the current
function's signature or error-path handling and adjust all callers to check and
forward the error (refer to the function containing this check and the variables
Cx, cut_rhs, and k to locate where to make the changes).
- Around line 1870-1873: Replace the call to exit(1) inside the cut generation
block: do not terminate the process from library code. Instead, log the error
(keeping settings_.log.printf("Cut status %d\n", cut_status)); then
set/propagate an error return (or throw) so the caller can handle it — e.g., set
a local error flag or change the function return to an error status and break
out of the cut generation loop when cut_status != dual::status_t::OPTIMAL.
Ensure the function's signature/return path is updated so callers receive and
handle the non-OK condition rather than the code calling exit(1).
- Around line 238-262: The slack-validation loop that iterates new_slacks_
currently calls exit(1) on failures (checks of col_len != 1 and std::abs(x) !=
1.0); remove those exit(1) calls and replace them with non-terminating error
handling: for debug builds use assertions (e.g., assert(col_len == 1) and
assert(std::abs(x) == 1.0)), otherwise propagate the error to the caller by
returning an error/false or throwing a descriptive exception (e.g.,
std::runtime_error) from the containing function; update the containing function
signature/return path as needed so callers can handle the validation failure and
include the same diagnostic message text that was printed (Slack %d has %d nzs /
Slack %d row %d has non-unit coefficient %e) in the assertion/exception/log.
- Around line 604-628: The block that opens "solution.dat" with FILE* fid and
uses fopen/fclose is not exception-safe; replace the C-style I/O with a
std::ofstream (e.g., create std::ofstream ofs("solution.dat")) and use ofs <<
... (and ofs.is_open() to check) instead of fprintf, letting RAII close the file
if matrix_vector_multiply, vector_norm_inf or any other call throws; preserve
the same outputs (write n and each incumbent_.x[j], and compute/update seed with
the existing hash_combine_f and print the final hash with the existing printf)
and remove fopen/fclose usage so the file is automatically cleaned up.
In @cpp/src/dual_simplex/cuts.cpp:
- Around line 1309-1313: The code currently calls exit(1) when
new_A_row.append_rows(cuts) fails; instead propagate the error to the caller:
check append_status after new_A_row.append_rows(cuts) and return or throw that
error (or set the function's error/Status return value) rather than calling
exit(1); preserve the log via settings.log.printf but remove exit(1) and ensure
the surrounding function signature (caller of append_rows) returns an
appropriate error code or exception so the failure bubbles up to higher-level
APIs.
- Around line 39-56: In cut_pool_t<i_t,f_t>::cut_distance, guard the division by
cut_norm: compute a small threshold eps (e.g. based on
std::numeric_limits<f_t>::epsilon()) and if cut_norm is <= eps treat it as
near-zero by setting cut_norm to eps (and/or set distance using eps) so you
never divide by zero or a near-zero value; update cut_norm accordingly so
callers see the adjusted norm and return cut_violation / cut_norm.
- Around line 932-935: The code currently aborts the process with exit(1) when
lp.lower[j] < 0; instead modify the block to return a failure error code from
the enclosing function (do not call exit), include a descriptive log via
settings_.log.printf mentioning the variable index and bound, and update
upstream callers to propagate and handle that error code (or convert to the
library's error enum/Status) so the caller can decide how to handle the invalid
lower bound; locate this change around the if (lp.lower[j] < 0) check
referencing lp.lower, j, and settings_.log.printf and replace exit(1) with a
returned error value and ensure the function signature/return propagation is
adjusted accordingly.
In @cpp/src/dual_simplex/cuts.hpp:
- Around line 45-64: Replace the process termination in minimum_violation with
throwing a descriptive exception: where the code currently calls exit(1) inside
the loop that checks Cx[k] <= cut_rhs[k], throw a std::runtime_error (or a
project-standard exception type) including the cut index k and the offending
values (Cx[k], cut_rhs[k]) and a clear message that the provided matrix/vector
do not form a valid cut; ensure the function signature remains unchanged and
that any printf logging is converted to the exception message (or left as debug
logging if desired).
- Around line 231-232: nonbasic_mark_ is declared as std::vector<f_t> but is
used as a binary marker (0/1) and should use integer type for consistency with
x_mark_; change the declaration of nonbasic_mark_ from std::vector<f_t> to
std::vector<i_t> and update any related initialization/assignments or function
signatures that assume f_t to use i_t instead so marker semantics remain
integer-based (refer to nonbasic_mark_, x_mark_, f_t and i_t to locate all
usages).
🧹 Nitpick comments (3)
cpp/src/dual_simplex/solution.hpp (1)
42-42: Good terminology correction!The change from "Lagrange multipliers for inequality constraints" to "Reduced costs" accurately reflects LP theory and dual simplex conventions. This improves code clarity.
Optional: Consider adding a brief explanation
For users less familiar with LP theory, you could optionally expand the comment slightly:
- // Reduced costs + // Reduced costs (cj - yᵀAj): marginal objective change per unit increase in each variable std::vector<f_t> z;However, this is entirely optional as "Reduced costs" is standard terminology.
cpp/src/mip/diversity/lns/rins.cu (1)
265-265: Consider documenting the rationale for disabling cuts in RINS subproblems.The explicit setting
max_cut_passes = 0disables cut generation for the RINS sub-MIP. While this is likely correct (RINS is a fast heuristic and the subproblem is already restricted), a brief comment would help future maintainers understand the design intent.Suggested documentation
+ // Disable cuts for RINS: the subproblem is already restricted and speed is prioritized branch_and_bound_settings.max_cut_passes = 0;cpp/src/dual_simplex/branch_and_bound.cpp (1)
1617-1703: Consider removing or conditionally compiling solution file I/O.The solution file reading code (lines 1617-1703) includes multiple file operations wrapped in
#if 1. For production builds, consider:
- Removing this debug code entirely, or
- Wrapping it in a named debug macro (e.g.,
#ifdef DEBUG_READ_SOLUTION)This improves maintainability and prevents accidental file I/O in production.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (22)
cpp/include/cuopt/linear_programming/constants.hcpp/include/cuopt/linear_programming/mip/solver_settings.hppcpp/src/dual_simplex/CMakeLists.txtcpp/src/dual_simplex/basis_updates.cppcpp/src/dual_simplex/basis_updates.hppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/branch_and_bound.hppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hppcpp/src/dual_simplex/mip_node.hppcpp/src/dual_simplex/phase2.cppcpp/src/dual_simplex/pseudo_costs.cppcpp/src/dual_simplex/simplex_solver_settings.hppcpp/src/dual_simplex/solution.hppcpp/src/dual_simplex/solve.cppcpp/src/dual_simplex/sparse_matrix.cppcpp/src/dual_simplex/sparse_matrix.hppcpp/src/math_optimization/solver_settings.cucpp/src/mip/diversity/diversity_manager.cucpp/src/mip/diversity/lns/rins.cucpp/src/mip/diversity/recombiners/sub_mip.cuhcpp/src/mip/solver.cu
🚧 Files skipped from review as they are similar to previous changes (11)
- cpp/include/cuopt/linear_programming/constants.h
- cpp/src/mip/diversity/recombiners/sub_mip.cuh
- cpp/src/dual_simplex/solve.cpp
- cpp/src/dual_simplex/sparse_matrix.hpp
- cpp/src/mip/diversity/diversity_manager.cu
- cpp/src/dual_simplex/phase2.cpp
- cpp/src/dual_simplex/CMakeLists.txt
- cpp/src/mip/solver.cu
- cpp/src/dual_simplex/simplex_solver_settings.hpp
- cpp/src/math_optimization/solver_settings.cu
- cpp/include/cuopt/linear_programming/mip/solver_settings.hpp
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{cu,cuh,cpp,hpp,h}
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
**/*.{cu,cuh,cpp,hpp,h}: Track GPU device memory allocations and deallocations to prevent memory leaks; ensure cudaMalloc/cudaFree balance and cleanup of streams/events
Validate algorithm correctness in optimization logic: simplex pivots, branch-and-bound decisions, routing heuristics, and constraint/objective handling must produce correct results
Check numerical stability: prevent overflow/underflow, precision loss, division by zero/near-zero, and use epsilon comparisons for floating-point equality checks
Validate correct initialization of variable bounds, constraint coefficients, and algorithm state before solving; ensure reset when transitioning between algorithm phases (presolve, simplex, diving, crossover)
Ensure variables and constraints are accessed from the correct problem context (original vs presolve vs folded vs postsolve); verify index mapping consistency across problem transformations
For concurrent CUDA operations (barriers, async operations), explicitly create and manage dedicated streams instead of reusing the default stream; document stream lifecycle
Eliminate unnecessary host-device synchronization (cudaDeviceSynchronize) in hot paths that blocks GPU pipeline; use streams and events for async execution
Assess algorithmic complexity for large-scale problems (millions of variables/constraints); ensure O(n log n) or better complexity, not O(n²) or worse
Verify correct problem size checks before expensive GPU/CPU operations; prevent resource exhaustion on oversized problems
Identify assertions with overly strict numerical tolerances that fail on legitimate degenerate/edge cases (near-zero pivots, singular matrices, empty problems)
Ensure race conditions are absent in multi-GPU code and multi-threaded server implementations; verify proper synchronization of shared state
Refactor code duplication in solver components (3+ occurrences) into shared utilities; for GPU kernels, use templated device functions to avoid duplication
Check that hard-coded GPU de...
Files:
cpp/src/dual_simplex/basis_updates.cppcpp/src/dual_simplex/mip_node.hppcpp/src/dual_simplex/pseudo_costs.cppcpp/src/dual_simplex/sparse_matrix.cppcpp/src/dual_simplex/basis_updates.hppcpp/src/dual_simplex/solution.hppcpp/src/mip/diversity/lns/rins.cucpp/src/dual_simplex/branch_and_bound.hppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.hpp
**/*.{cpp,hpp,h}
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
**/*.{cpp,hpp,h}: Check for unclosed file handles when reading MPS/QPS problem files; ensure RAII patterns or proper cleanup in exception paths
Validate input sanitization to prevent buffer overflows and resource exhaustion attacks; avoid unsafe deserialization of problem files
Prevent thread-unsafe use of global and static variables; use proper mutex/synchronization in server code accessing shared solver state
Files:
cpp/src/dual_simplex/basis_updates.cppcpp/src/dual_simplex/mip_node.hppcpp/src/dual_simplex/pseudo_costs.cppcpp/src/dual_simplex/sparse_matrix.cppcpp/src/dual_simplex/basis_updates.hppcpp/src/dual_simplex/solution.hppcpp/src/dual_simplex/branch_and_bound.hppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.hpp
**/*.{cu,cpp,hpp,h}
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
Avoid inappropriate use of exceptions in performance-critical GPU operation paths; prefer error codes or CUDA error checking for latency-sensitive code
Files:
cpp/src/dual_simplex/basis_updates.cppcpp/src/dual_simplex/mip_node.hppcpp/src/dual_simplex/pseudo_costs.cppcpp/src/dual_simplex/sparse_matrix.cppcpp/src/dual_simplex/basis_updates.hppcpp/src/dual_simplex/solution.hppcpp/src/mip/diversity/lns/rins.cucpp/src/dual_simplex/branch_and_bound.hppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.hpp
**/*.{h,hpp,py}
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
Verify C API does not break ABI stability (no struct layout changes, field reordering); maintain backward compatibility in Python and server APIs with deprecation warnings
Files:
cpp/src/dual_simplex/mip_node.hppcpp/src/dual_simplex/basis_updates.hppcpp/src/dual_simplex/solution.hppcpp/src/dual_simplex/branch_and_bound.hppcpp/src/dual_simplex/cuts.hpp
**/*.{cu,cuh}
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
**/*.{cu,cuh}: Every CUDA kernel launch and memory operation must have error checking with CUDA_CHECK or equivalent verification
Avoid reinventing functionality already available in Thrust, CCCL, or RMM libraries; prefer standard library utilities over custom implementations
Files:
cpp/src/mip/diversity/lns/rins.cu
**/*.cu
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
**/*.cu: Verify race conditions and correctness of GPU kernel shared memory, atomics, and warp-level operations
Detect inefficient GPU kernel launches with low occupancy or poor memory access patterns; optimize for coalesced memory access and minimize warp divergence in hot paths
Files:
cpp/src/mip/diversity/lns/rins.cu
🧠 Learnings (21)
📓 Common learnings
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Reduce tight coupling between solver components (presolve, simplex, basis, barrier); increase modularity and reusability of optimization algorithms
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Validate algorithm correctness in optimization logic: simplex pivots, branch-and-bound decisions, routing heuristics, and constraint/objective handling must produce correct results
Applied to files:
cpp/src/dual_simplex/basis_updates.cppcpp/src/dual_simplex/mip_node.hppcpp/src/dual_simplex/pseudo_costs.cppcpp/src/mip/diversity/lns/rins.cucpp/src/dual_simplex/branch_and_bound.hppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-12-04T04:11:12.640Z
Learnt from: chris-maes
Repo: NVIDIA/cuopt PR: 500
File: cpp/src/dual_simplex/scaling.cpp:68-76
Timestamp: 2025-12-04T04:11:12.640Z
Learning: In the cuOPT dual simplex solver, CSR/CSC matrices (including the quadratic objective matrix Q) are required to have valid dimensions and indices by construction. Runtime bounds checking in performance-critical paths like matrix scaling is avoided to prevent slowdowns. Validation is performed via debug-only check_matrix() calls wrapped in #ifdef CHECK_MATRIX.
Applied to files:
cpp/src/dual_simplex/basis_updates.cppcpp/src/dual_simplex/sparse_matrix.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/branch_and_bound.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Reduce tight coupling between solver components (presolve, simplex, basis, barrier); increase modularity and reusability of optimization algorithms
Applied to files:
cpp/src/dual_simplex/basis_updates.cppcpp/src/dual_simplex/basis_updates.hppcpp/src/dual_simplex/branch_and_bound.hppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Validate correct initialization of variable bounds, constraint coefficients, and algorithm state before solving; ensure reset when transitioning between algorithm phases (presolve, simplex, diving, crossover)
Applied to files:
cpp/src/dual_simplex/basis_updates.cppcpp/src/dual_simplex/pseudo_costs.cppcpp/src/mip/diversity/lns/rins.cucpp/src/dual_simplex/branch_and_bound.hppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Identify assertions with overly strict numerical tolerances that fail on legitimate degenerate/edge cases (near-zero pivots, singular matrices, empty problems)
Applied to files:
cpp/src/dual_simplex/basis_updates.cppcpp/src/dual_simplex/pseudo_costs.cppcpp/src/dual_simplex/sparse_matrix.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-10-22T14:25:22.899Z
Learnt from: aliceb-nv
Repo: NVIDIA/cuopt PR: 527
File: cpp/src/mip/diversity/lns/rins.cu:167-175
Timestamp: 2025-10-22T14:25:22.899Z
Learning: In MIP (Mixed Integer Programming) problems in the cuOPT codebase, `n_integer_vars == 0` is impossible by definition—MIP problems must have at least one integer variable. If there are no integer variables, it would be a pure Linear Programming (LP) problem, not a MIP problem.
Applied to files:
cpp/src/dual_simplex/mip_node.hppcpp/src/dual_simplex/branch_and_bound.hppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*test*.{cpp,cu,py} : Add tests for algorithm phase transitions: verify correct initialization of bounds and state when transitioning from presolve to simplex to diving to crossover
Applied to files:
cpp/src/dual_simplex/pseudo_costs.cppcpp/src/mip/diversity/lns/rins.cucpp/src/dual_simplex/branch_and_bound.hppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Ensure variables and constraints are accessed from the correct problem context (original vs presolve vs folded vs postsolve); verify index mapping consistency across problem transformations
Applied to files:
cpp/src/dual_simplex/pseudo_costs.cppcpp/src/mip/diversity/lns/rins.cucpp/src/dual_simplex/branch_and_bound.hppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*test*.{cpp,cu,py} : Test with free variables, singleton problems, and extreme problem dimensions near resource limits to validate edge case handling
Applied to files:
cpp/src/dual_simplex/pseudo_costs.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/branch_and_bound.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cpp,hpp,h} : Prevent thread-unsafe use of global and static variables; use proper mutex/synchronization in server code accessing shared solver state
Applied to files:
cpp/src/dual_simplex/pseudo_costs.cppcpp/src/dual_simplex/branch_and_bound.hpp
📚 Learning: 2025-12-03T23:29:26.391Z
Learnt from: chris-maes
Repo: NVIDIA/cuopt PR: 500
File: cpp/src/dual_simplex/sparse_matrix.cpp:519-524
Timestamp: 2025-12-03T23:29:26.391Z
Learning: In cpp/src/dual_simplex/sparse_matrix.cpp, the check_matrix() function is debug/diagnostic code (wrapped in #ifdef CHECK_MATRIX) that intentionally prints errors without necessarily returning early. The return codes from this debug code are not actively checked; the purpose is to print all validation errors in one pass for better diagnostics.
Applied to files:
cpp/src/dual_simplex/sparse_matrix.cppcpp/src/dual_simplex/branch_and_bound.cpp
📚 Learning: 2025-12-06T00:22:48.638Z
Learnt from: chris-maes
Repo: NVIDIA/cuopt PR: 500
File: cpp/tests/linear_programming/c_api_tests/c_api_test.c:1033-1048
Timestamp: 2025-12-06T00:22:48.638Z
Learning: In cuOPT's quadratic programming API, when a user provides a quadratic objective matrix Q via set_quadratic_objective_matrix or the C API functions cuOptCreateQuadraticProblem/cuOptCreateQuadraticRangedProblem, the API internally computes Q_symmetric = Q + Q^T and the barrier solver uses 0.5 * x^T * Q_symmetric * x. From the user's perspective, the convention is x^T Q x. For a diagonal Q with values [q1, q2, ...], the resulting quadratic terms are q1*x1^2 + q2*x2^2 + ...
Applied to files:
cpp/src/dual_simplex/solution.hpp
📚 Learning: 2025-12-04T20:09:09.264Z
Learnt from: chris-maes
Repo: NVIDIA/cuopt PR: 602
File: cpp/src/linear_programming/solve.cu:732-742
Timestamp: 2025-12-04T20:09:09.264Z
Learning: In cpp/src/linear_programming/solve.cu, the barrier solver does not currently return INFEASIBLE or UNBOUNDED status. It only returns OPTIMAL, TIME_LIMIT, NUMERICAL_ISSUES, or CONCURRENT_LIMIT.
Applied to files:
cpp/src/mip/diversity/lns/rins.cucpp/src/dual_simplex/branch_and_bound.hppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Assess algorithmic complexity for large-scale problems (millions of variables/constraints); ensure O(n log n) or better complexity, not O(n²) or worse
Applied to files:
cpp/src/dual_simplex/branch_and_bound.hppcpp/src/dual_simplex/branch_and_bound.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Refactor code duplication in solver components (3+ occurrences) into shared utilities; for GPU kernels, use templated device functions to avoid duplication
Applied to files:
cpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Check numerical stability: prevent overflow/underflow, precision loss, division by zero/near-zero, and use epsilon comparisons for floating-point equality checks
Applied to files:
cpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/branch_and_bound.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Verify correct problem size checks before expensive GPU/CPU operations; prevent resource exhaustion on oversized problems
Applied to files:
cpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/branch_and_bound.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cpp,hpp,h} : Check for unclosed file handles when reading MPS/QPS problem files; ensure RAII patterns or proper cleanup in exception paths
Applied to files:
cpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/branch_and_bound.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*test*.{cpp,cu,py} : Write tests validating numerical correctness of optimization results (not just 'runs without error'); test degenerate cases (infeasible, unbounded, empty, singleton problems)
Applied to files:
cpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/branch_and_bound.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cpp,hpp,h} : Avoid inappropriate use of exceptions in performance-critical GPU operation paths; prefer error codes or CUDA error checking for latency-sensitive code
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cpp
🧬 Code graph analysis (4)
cpp/src/dual_simplex/basis_updates.cpp (5)
cpp/src/dual_simplex/basis_updates.hpp (5)
L0_(116-120)L0_(367-371)nz(149-149)nz(401-401)nz(415-415)cpp/src/dual_simplex/sparse_matrix.hpp (6)
matrix_transpose_vector_multiply(240-270)matrix_transpose_vector_multiply(240-244)nz(80-80)i(163-174)matrix_vector_multiply(274-306)matrix_vector_multiply(274-278)cpp/src/dual_simplex/sparse_vector.cpp (2)
dot(125-133)dot(125-125)cpp/src/dual_simplex/vector_math.hpp (1)
dot(42-42)cpp/src/dual_simplex/sparse_vector.hpp (2)
p(40-40)p(42-42)
cpp/src/dual_simplex/sparse_matrix.cpp (1)
cpp/src/dual_simplex/sparse_matrix.hpp (8)
C(155-155)new_nz(49-49)nz(80-80)i(163-174)c(158-158)j(65-65)j(115-126)col_start(51-51)
cpp/src/dual_simplex/branch_and_bound.cpp (4)
cpp/src/dual_simplex/branch_and_bound.hpp (5)
upper_bound(126-126)solution(102-102)solution(129-129)solution(215-215)lp_settings(118-124)cpp/src/dual_simplex/bounds_strengthening.hpp (1)
lower_bounds(23-25)cpp/src/dual_simplex/solve.hpp (4)
compute_objective(34-34)solve_linear_program_with_advanced_basis(53-62)compute_user_objective(37-37)compute_user_objective(40-40)cpp/src/dual_simplex/vector_math.hpp (2)
vector_norm_inf(17-26)vector_norm_inf(17-17)
cpp/src/dual_simplex/cuts.hpp (7)
cpp/src/dual_simplex/sparse_matrix.hpp (1)
C(155-155)cpp/src/dual_simplex/sparse_vector.hpp (2)
x(44-44)y(54-54)cpp/src/dual_simplex/cuts.cpp (2)
cut(167-167)inequality(580-580)cpp/src/mip/solver.cu (6)
solution(64-68)solution(64-64)solution(70-75)solution(70-72)solution(77-80)solution(77-77)cpp/tests/dual_simplex/unit_tests/solve.cpp (1)
solution(156-156)cpp/src/dual_simplex/branch_and_bound.cpp (3)
basic_list(1024-1024)basic_list(1234-1234)basic_list(1303-1303)cpp/src/dual_simplex/solve.cpp (1)
basic_list(114-114)
🪛 Cppcheck (2.19.0)
cpp/src/dual_simplex/pseudo_costs.cpp
[warning] 447-447: Array index -1 is out of bounds.
(negativeContainerIndex)
cpp/src/dual_simplex/branch_and_bound.cpp
[error] 615-615: Resource leak
(resourceLeak)
🔇 Additional comments (17)
cpp/src/dual_simplex/branch_and_bound.hpp (3)
118-124: Signature expansion aligns with warm-start basis support.The expanded signature to return basis-related structures (basis_update, basic_list, nonbasic_list, edge_norms) is consistent with the PR's objective to enable warm-start dual simplex from the current basis.
126-126: LGTM: New method for reduced-cost fixings.The
find_reduced_cost_fixingsmethod declaration aligns with the PR's goal to strengthen the LP via reduced-cost variable fixings.
147-148: The lock ordering concern is not applicable here. The code acquiresmutex_original_lp_andmutex_upper_sequentially (lock, unlock, then lock the next), never holding both simultaneously. Lock ordering deadlocks only occur with nested/concurrent mutex holding; this code pattern is inherently deadlock-safe.Likely an incorrect or invalid review comment.
cpp/src/dual_simplex/mip_node.hpp (4)
54-67: LGTM: Root node constructor updated consistently.The initialization of
integer_infeasible(-1)in the root node constructor is appropriate for indicating unset/root state.
69-94: LGTM: Constructor signature updated consistently.The new
integer_infparameter is properly threaded through the constructor signature and initializer list.
250-250: LGTM: New field declaration.The
integer_infeasiblefield is appropriately declared alongside other node attributes.
299-343: LGTM: Branching updated to propagate integer infeasibility.The
branchmethod correctly threads theinteger_infeasibleparameter to both down and up child node constructors.cpp/src/dual_simplex/branch_and_bound.cpp (6)
10-12: LGTM: New includes for cut generation and basis solves.The new includes for
basis_solves.hpp,cuts.hpp, andbounds_strengthening.hppsupport the PR's cut generation and warm-start features.
302-376: Reduced-cost fixings implementation looks sound.The
find_reduced_cost_fixingsmethod correctly acquires and releasesmutex_original_lp_(lines 306, 375) and implements the reduced-cost strengthening logic as described in the PR objectives.
379-449: Mutex usage appears correct withinset_new_solution.The function properly acquires and releases
mutex_original_lp_andmutex_upper_in separate critical sections. Lock ordering is consistent: when both are needed,mutex_original_lp_is released before acquiringmutex_upper_.
745-753: Debug depth validation is acceptable.The depth check and diagnostic printing (lines 745-753) helpfully identifies potential branching issues without terminating the process.
867-881: LGTM: Diagnostic for fixed fractional variables.The check for fixed variables with fractional values (lines 867-881) is a useful diagnostic that doesn't terminate the process.
1394-1492: Root relaxation solve implementation updated correctly.The
solve_root_relaxationimplementation properly threads the new basis-related parameters (basis_update, basic_list, nonbasic_list, edge_norms) through async solving and crossover paths. The basis reconstruction from crossover vstatus (lines 1469-1484) correctly builds the basic/nonbasic lists and refactors the basis.cpp/src/dual_simplex/cuts.hpp (4)
21-42: LGTM: Cut type enum and helper function.The
cut_type_tenum andprint_cut_typeshelper function provide clear classification and logging for different cut types.
66-115: LGTM: Cut pool class design.The
cut_pool_tclass provides a well-structured interface for managing cut storage, scoring, and selection. The separation between pool management and cut generation is good design.Based on learnings: This design reduces tight coupling between solver components, improving modularity.
117-147: LGTM: Knapsack cut generation class.The
knapsack_generation_tclass has a focused responsibility for generating knapsack-based cuts with a clear interface.
280-306: Public API for cut integration is comprehensive.The
add_cutsandremove_cutstemplate functions provide the necessary parameters to integrate cuts into the LP, update the basis, and maintain solver state. While the parameter lists are extensive, they reflect the complexity of modifying an LP with an active basis.Based on learnings: Ensure variables and constraints are accessed from the correct problem context during LP modifications.
| num_integer_variables_ = 0; | ||
| for (i_t j = 0; j < original_lp_.num_cols; j++) { | ||
| if (var_types_[j] == variable_type_t::INTEGER) { | ||
| num_integer_variables_++; | ||
| } | ||
| } | ||
| printf("num_integer_variables %d\n", num_integer_variables_); | ||
|
|
||
| // Check slack | ||
| printf("slacks size %ld m %d\n", new_slacks_.size(), original_lp_.num_rows); | ||
| for (i_t slack : new_slacks_) { | ||
| const i_t col_start = original_lp_.A.col_start[slack]; | ||
| const i_t col_end = original_lp_.A.col_start[slack + 1]; | ||
| const i_t col_len = col_end - col_start; | ||
| if (col_len != 1) { | ||
| printf("Slack %d has %d nzs\n", slack, col_len); | ||
| exit(1); | ||
| } | ||
| const i_t i = original_lp_.A.i[col_start]; | ||
| const f_t x = original_lp_.A.x[col_start]; | ||
| if (std::abs(x) != 1.0) { | ||
| printf("Slack %d row %d has non-unit coefficient %e\n", slack, i, x); | ||
| exit(1); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Remove exit(1) calls from validation code.
Lines 254 and 260 call exit(1) when slack validation fails, terminating the entire process. Library code should not call exit(). Convert these to assertions (for debug builds) or propagate errors to the caller.
🔎 Recommended approach
For debug-only checks:
+#ifdef DEBUG_SLACK_VALIDATION
if (col_len != 1) {
printf("Slack %d has %d nzs\n", slack, col_len);
- exit(1);
+ assert(false && "Invalid slack column length");
}
+#endifOr wrap the entire validation block in #ifdef DEBUG if it's purely diagnostic.
Based on coding guidelines: Verify error propagation from internal functions to user-facing APIs is complete.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In @cpp/src/dual_simplex/branch_and_bound.cpp around lines 238 - 262, The
slack-validation loop that iterates new_slacks_ currently calls exit(1) on
failures (checks of col_len != 1 and std::abs(x) != 1.0); remove those exit(1)
calls and replace them with non-terminating error handling: for debug builds use
assertions (e.g., assert(col_len == 1) and assert(std::abs(x) == 1.0)),
otherwise propagate the error to the caller by returning an error/false or
throwing a descriptive exception (e.g., std::runtime_error) from the containing
function; update the containing function signature/return path as needed so
callers can handle the validation failure and include the same diagnostic
message text that was printed (Slack %d has %d nzs / Slack %d row %d has
non-unit coefficient %e) in the assertion/exception/log.
| if (Cx[k] > cut_rhs[k] + 1e-6) { | ||
| printf("Cut %d is violated by saved solution. Cx %e cut_rhs %e\n", k, Cx[k], cut_rhs[k]); | ||
| exit(1); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Replace exit(1) with error propagation.
Line 1789 calls exit(1) when a saved solution violates a cut. This terminates the entire process. Return an error status instead so the caller can handle the failure gracefully.
This issue was previously flagged in past reviews. Based on coding guidelines: Verify error propagation from internal functions to user-facing APIs is complete.
🤖 Prompt for AI Agents
In @cpp/src/dual_simplex/branch_and_bound.cpp around lines 1787 - 1791, The code
currently calls exit(1) when a saved solution violates a cut (check using Cx[k]
and cut_rhs[k], with index k); replace this process-terminating call with proper
error propagation: return an error status (e.g., false or an error
code/exception) from the current function and propagate it up to the caller so
the caller can handle the failure; update the current function's signature or
error-path handling and adjust all callers to check and forward the error (refer
to the function containing this check and the variables Cx, cut_rhs, and k to
locate where to make the changes).
| if (lp.lower[j] < 0) { | ||
| settings_.log.printf("Variable %d has negative lower bound %e\n", j, lp.lower[j]); | ||
| exit(1); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace exit(1) with error code return.
Lines 933-934 call exit(1) when encountering a negative lower bound, terminating the entire process. This is inappropriate for a library function. Return an error code instead to allow the caller to handle the error gracefully.
🔎 Apply this diff to propagate error instead
for (i_t j = 0; j < lp.num_cols; j++) {
if (lp.lower[j] < 0) {
settings_.log.printf("Variable %d has negative lower bound %e\n", j, lp.lower[j]);
- exit(1);
+ return -1;
}Based on coding guidelines: Verify error propagation from internal functions to user-facing APIs.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (lp.lower[j] < 0) { | |
| settings_.log.printf("Variable %d has negative lower bound %e\n", j, lp.lower[j]); | |
| exit(1); | |
| } | |
| if (lp.lower[j] < 0) { | |
| settings_.log.printf("Variable %d has negative lower bound %e\n", j, lp.lower[j]); | |
| return -1; | |
| } |
🤖 Prompt for AI Agents
In @cpp/src/dual_simplex/cuts.cpp around lines 932 - 935, The code currently
aborts the process with exit(1) when lp.lower[j] < 0; instead modify the block
to return a failure error code from the enclosing function (do not call exit),
include a descriptive log via settings_.log.printf mentioning the variable index
and bound, and update upstream callers to propagate and handle that error code
(or convert to the library's error enum/Status) so the caller can decide how to
handle the invalid lower bound; locate this change around the if (lp.lower[j] <
0) check referencing lp.lower, j, and settings_.log.printf and replace exit(1)
with a returned error value and ensure the function signature/return propagation
is adjusted accordingly.
| std::vector<f_t> child_edge_norms = edge_norms; | ||
| dual::status_t status = dual_phase2( | ||
| 2, 0, lp_start_time, child_problem, child_settings, vstatus, solution, iter, child_edge_norms); | ||
| printf("Trial branching on variable %d. Lo: %e Up: %e. Iter %d. Status %d. Obj %e\n", branch_var, child_problem.lower[branch_var], child_problem.upper[branch_var], iter, status, compute_objective(child_problem, solution.x)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Convert debug printf to use the logging system.
Line 160 uses printf directly instead of the settings logging system. This bypasses the configured log level and cannot be suppressed when set_log(false) is called.
🔎 Apply this diff
- printf("Trial branching on variable %d. Lo: %e Up: %e. Iter %d. Status %d. Obj %e\n", branch_var, child_problem.lower[branch_var], child_problem.upper[branch_var], iter, status, compute_objective(child_problem, solution.x));
+ settings.log.debug("Trial branching on variable %d. Lo: %e Up: %e. Iter %d. Status %d. Obj %e\n", branch_var, child_problem.lower[branch_var], child_problem.upper[branch_var], iter, status, compute_objective(child_problem, solution.x));Committable suggestion skipped: line range outside the PR's diff.
| i_t branch_var = fractional[0]; | ||
| f_t max_score = -1; | ||
| i_t select = -1; | ||
| for (i_t k = 0; k < num_fractional; k++) { | ||
| if (score[k] > max_score) { | ||
| max_score = score[k]; | ||
| branch_var = fractional[k]; | ||
| select = k; | ||
| } | ||
| } | ||
|
|
||
| log.printf( | ||
| "pc branching on %d. Value %e. Score %e\n", branch_var, solution[branch_var], score[select]); | ||
|
|
||
| mutex.unlock(); | ||
|
|
||
| return branch_var; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix mutex synchronization error and prevent out-of-bounds access.
Two critical issues in this block:
-
Line 449: The mutex was unlocked at line 378 and is not re-locked before this point. The per-variable lock/unlock operations (lines 391-427) leave the mutex unlocked. This
mutex.unlock()call causes undefined behavior (double-unlock). -
Line 447: If
fractionalis empty,selectremains-1, causingscore[select]to access an invalid index.
🔎 Apply this diff to fix both issues
i_t branch_var = fractional[0];
f_t max_score = -1;
i_t select = -1;
for (i_t k = 0; k < num_fractional; k++) {
if (score[k] > max_score) {
max_score = score[k];
branch_var = fractional[k];
select = k;
}
}
- log.printf(
- "pc branching on %d. Value %e. Score %e\n", branch_var, solution[branch_var], score[select]);
-
- mutex.unlock();
+ if (select >= 0) {
+ log.printf(
+ "pc branching on %d. Value %e. Score %e\n", branch_var, solution[branch_var], score[select]);
+ }
return branch_var;
}Based on coding guidelines: Prevent thread-unsafe use of synchronization primitives.
🧰 Tools
🪛 Cppcheck (2.19.0)
[warning] 447-447: Array index -1 is out of bounds.
(negativeContainerIndex)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
cpp/src/linear_programming/optimization_problem.cu (1)
137-145: Implement or remove unusedvalidate_positive_semi_definiteparameter.The
validate_positive_semi_definiteparameter (Line 144) is defined in the function signature but never used. The TODO comment at Line 244 indicates that PSD validation is not yet implemented. This creates confusion about the API's capabilities.Recommendations:
- Preferred: Implement the PSD validation when
validate_positive_semi_definiteis true, as this is important for ensuring correctness when the quadratic matrix is used by the barrier solver.- Alternative: Remove the unused parameter from the function signature if PSD validation is deferred to a later release.
Do you want me to open an issue to track the PSD validation implementation, or would you prefer to remove the parameter in this PR?
Also applies to: 244-244
🧹 Nitpick comments (2)
cpp/src/linear_programming/optimization_problem.cu (2)
161-191: Triplet construction for symmetrization is correct.The symmetrization logic correctly constructs H = Q + Q^T by adding both (i,j) and (j,i) entries for off-diagonal elements while avoiding duplication for diagonal elements.
Minor typo at Line 164: "covert" should be "convert".
Fix typo
- // Then covert the triplet to CSR + // Then convert the triplet to CSR
193-211: Triplet-to-CSR conversion implemented correctly.The counting sort approach for CSR construction is correct and has optimal O(n) complexity. The implementation correctly saves
H_row_startsbefore modifyingH_cumulative_countsduring the distribution phase.Consider extracting triplet-to-CSR conversion into a utility function. The relevant code snippets indicate similar CSR manipulation patterns exist in
cpp/src/dual_simplex/sparse_matrix.cpp. Extracting this logic would improve maintainability and reduce duplication.Based on coding guidelines and learnings: Refactor code duplication in solver components into shared utilities.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
cpp/src/linear_programming/optimization_problem.cu
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{cu,cuh}
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
**/*.{cu,cuh}: Every CUDA kernel launch and memory operation must have error checking with CUDA_CHECK or equivalent verification
Avoid reinventing functionality already available in Thrust, CCCL, or RMM libraries; prefer standard library utilities over custom implementations
Files:
cpp/src/linear_programming/optimization_problem.cu
**/*.cu
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
**/*.cu: Verify race conditions and correctness of GPU kernel shared memory, atomics, and warp-level operations
Detect inefficient GPU kernel launches with low occupancy or poor memory access patterns; optimize for coalesced memory access and minimize warp divergence in hot paths
Files:
cpp/src/linear_programming/optimization_problem.cu
**/*.{cu,cuh,cpp,hpp,h}
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
**/*.{cu,cuh,cpp,hpp,h}: Track GPU device memory allocations and deallocations to prevent memory leaks; ensure cudaMalloc/cudaFree balance and cleanup of streams/events
Validate algorithm correctness in optimization logic: simplex pivots, branch-and-bound decisions, routing heuristics, and constraint/objective handling must produce correct results
Check numerical stability: prevent overflow/underflow, precision loss, division by zero/near-zero, and use epsilon comparisons for floating-point equality checks
Validate correct initialization of variable bounds, constraint coefficients, and algorithm state before solving; ensure reset when transitioning between algorithm phases (presolve, simplex, diving, crossover)
Ensure variables and constraints are accessed from the correct problem context (original vs presolve vs folded vs postsolve); verify index mapping consistency across problem transformations
For concurrent CUDA operations (barriers, async operations), explicitly create and manage dedicated streams instead of reusing the default stream; document stream lifecycle
Eliminate unnecessary host-device synchronization (cudaDeviceSynchronize) in hot paths that blocks GPU pipeline; use streams and events for async execution
Assess algorithmic complexity for large-scale problems (millions of variables/constraints); ensure O(n log n) or better complexity, not O(n²) or worse
Verify correct problem size checks before expensive GPU/CPU operations; prevent resource exhaustion on oversized problems
Identify assertions with overly strict numerical tolerances that fail on legitimate degenerate/edge cases (near-zero pivots, singular matrices, empty problems)
Ensure race conditions are absent in multi-GPU code and multi-threaded server implementations; verify proper synchronization of shared state
Refactor code duplication in solver components (3+ occurrences) into shared utilities; for GPU kernels, use templated device functions to avoid duplication
Check that hard-coded GPU de...
Files:
cpp/src/linear_programming/optimization_problem.cu
**/*.{cu,cpp,hpp,h}
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
Avoid inappropriate use of exceptions in performance-critical GPU operation paths; prefer error codes or CUDA error checking for latency-sensitive code
Files:
cpp/src/linear_programming/optimization_problem.cu
🧠 Learnings (11)
📓 Common learnings
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Reduce tight coupling between solver components (presolve, simplex, basis, barrier); increase modularity and reusability of optimization algorithms
📚 Learning: 2025-12-06T00:22:48.638Z
Learnt from: chris-maes
Repo: NVIDIA/cuopt PR: 500
File: cpp/tests/linear_programming/c_api_tests/c_api_test.c:1033-1048
Timestamp: 2025-12-06T00:22:48.638Z
Learning: In cuOPT's quadratic programming API, when a user provides a quadratic objective matrix Q via set_quadratic_objective_matrix or the C API functions cuOptCreateQuadraticProblem/cuOptCreateQuadraticRangedProblem, the API internally computes Q_symmetric = Q + Q^T and the barrier solver uses 0.5 * x^T * Q_symmetric * x. From the user's perspective, the convention is x^T Q x. For a diagonal Q with values [q1, q2, ...], the resulting quadratic terms are q1*x1^2 + q2*x2^2 + ...
Applied to files:
cpp/src/linear_programming/optimization_problem.cu
📚 Learning: 2025-12-04T04:11:12.640Z
Learnt from: chris-maes
Repo: NVIDIA/cuopt PR: 500
File: cpp/src/dual_simplex/scaling.cpp:68-76
Timestamp: 2025-12-04T04:11:12.640Z
Learning: In the cuOPT dual simplex solver, CSR/CSC matrices (including the quadratic objective matrix Q) are required to have valid dimensions and indices by construction. Runtime bounds checking in performance-critical paths like matrix scaling is avoided to prevent slowdowns. Validation is performed via debug-only check_matrix() calls wrapped in #ifdef CHECK_MATRIX.
Applied to files:
cpp/src/linear_programming/optimization_problem.cu
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Ensure variables and constraints are accessed from the correct problem context (original vs presolve vs folded vs postsolve); verify index mapping consistency across problem transformations
Applied to files:
cpp/src/linear_programming/optimization_problem.cu
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Refactor code duplication in solver components (3+ occurrences) into shared utilities; for GPU kernels, use templated device functions to avoid duplication
Applied to files:
cpp/src/linear_programming/optimization_problem.cu
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Validate algorithm correctness in optimization logic: simplex pivots, branch-and-bound decisions, routing heuristics, and constraint/objective handling must produce correct results
Applied to files:
cpp/src/linear_programming/optimization_problem.cu
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Validate correct initialization of variable bounds, constraint coefficients, and algorithm state before solving; ensure reset when transitioning between algorithm phases (presolve, simplex, diving, crossover)
Applied to files:
cpp/src/linear_programming/optimization_problem.cu
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*test*.{cpp,cu,py} : Add tests for problem transformations: verify correctness of original→transformed→postsolve mappings and index consistency across problem representations
Applied to files:
cpp/src/linear_programming/optimization_problem.cu
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh} : Avoid reinventing functionality already available in Thrust, CCCL, or RMM libraries; prefer standard library utilities over custom implementations
Applied to files:
cpp/src/linear_programming/optimization_problem.cu
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Assess algorithmic complexity for large-scale problems (millions of variables/constraints); ensure O(n log n) or better complexity, not O(n²) or worse
Applied to files:
cpp/src/linear_programming/optimization_problem.cu
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.cu : Detect inefficient GPU kernel launches with low occupancy or poor memory access patterns; optimize for coalesced memory access and minimize warp divergence in hot paths
Applied to files:
cpp/src/linear_programming/optimization_problem.cu
🧬 Code graph analysis (1)
cpp/src/linear_programming/optimization_problem.cu (3)
cpp/include/cuopt/linear_programming/optimization_problem.hpp (1)
Q_values(205-211)cpp/libmps_parser/include/mps_parser/mps_data_model.hpp (1)
Q_values(258-263)cpp/src/dual_simplex/sparse_matrix.cpp (1)
workspace(63-63)
🔇 Additional comments (1)
cpp/src/linear_programming/optimization_problem.cu (1)
213-242: Deduplication logic is correct and efficient.The workspace-based deduplication technique correctly identifies and accumulates duplicate (i,j) entries within each row. The implementation properly initializes the workspace, tracks column positions, and finalizes the CSR structure.
Note on numerical stability: Line 230 accumulates duplicate entries via addition. While this is the correct approach for symmetrization, be aware that summing many duplicate entries could compound floating-point precision errors. This is an inherent limitation of the algorithm rather than an implementation defect.
Based on coding guidelines: Check numerical stability to prevent precision loss.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In @cpp/src/dual_simplex/cuts.cpp:
- Around line 1483-1488: The block that checks col_len (using col_start,
col_end, col_len and printing with printf for j) must not call exit(1); instead
have the function return a non-zero error code (e.g., return -1 or a defined
ERROR_* constant) so the caller can handle the failure — adjust the function
signature/return type if needed and replace exit(1) with that return, and then
update all callers to check the returned value and propagate or handle the error
appropriately.
- Around line 1349-1354: The check for slack column length currently calls
exit(1) on failure; change this to return a proper error code from the function
containing this check (include context in the log with slack and col_len), e.g.,
return a dedicated error enum or a negative int (INVALID_SLACK / -1) instead of
terminating; update the function signature if needed to propagate an error code
and update all callers to check and propagate the returned error so the
validation failure bubbles up to the user-facing API rather than calling exit.
- Around line 926-929: The code currently calls exit(1) inside the Initialize
block when a slack coefficient check fails (printf "Initialize: Slack row %d..."
uses lp.A.x[col_start] and col_start); replace the process-terminating exit call
with returning a suitable error code (e.g., return non-zero) from the function
(likely Initialize) and adjust the function signature and all callers to
propagate and handle that error instead of terminating the process. Ensure the
return value is documented and used by callers to surface the error to
user-facing APIs.
- Around line 1157-1165: The code currently calls exit(1) on validation failures
when checking slack column length and coefficient (the checks around slack_len
!= 1 and std::abs(alpha) != 1.0, and the similar block at 1197-1202); replace
those exit(1) calls with returning a non-zero error code (e.g., return -1 or a
defined ERR_INVALID_SLACK) from the containing function, update the function
signature to propagate the error if it is currently void, and ensure callers of
this function check and propagate the returned error up to the public API so
failures are handled gracefully rather than terminating the process.
🧹 Nitpick comments (1)
cpp/src/dual_simplex/cuts.cpp (1)
240-240: Usesettings.log.printffor consistency with logging infrastructure.Multiple
printfcalls are scattered throughout the knapsack generation code (e.g., lines 240, 271-295, 317-323, 357, 448, 461, 470-473, 481). For consistency with the rest of the codebase and to respect the logging infrastructure, replace these withsettings.log.printforsettings_.log.printfas appropriate.Also applies to: 271-295, 317-323, 357-357, 448-448, 461-461, 470-473, 481-481
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
cpp/src/dual_simplex/cuts.cpp
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{cu,cuh,cpp,hpp,h}
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
**/*.{cu,cuh,cpp,hpp,h}: Track GPU device memory allocations and deallocations to prevent memory leaks; ensure cudaMalloc/cudaFree balance and cleanup of streams/events
Validate algorithm correctness in optimization logic: simplex pivots, branch-and-bound decisions, routing heuristics, and constraint/objective handling must produce correct results
Check numerical stability: prevent overflow/underflow, precision loss, division by zero/near-zero, and use epsilon comparisons for floating-point equality checks
Validate correct initialization of variable bounds, constraint coefficients, and algorithm state before solving; ensure reset when transitioning between algorithm phases (presolve, simplex, diving, crossover)
Ensure variables and constraints are accessed from the correct problem context (original vs presolve vs folded vs postsolve); verify index mapping consistency across problem transformations
For concurrent CUDA operations (barriers, async operations), explicitly create and manage dedicated streams instead of reusing the default stream; document stream lifecycle
Eliminate unnecessary host-device synchronization (cudaDeviceSynchronize) in hot paths that blocks GPU pipeline; use streams and events for async execution
Assess algorithmic complexity for large-scale problems (millions of variables/constraints); ensure O(n log n) or better complexity, not O(n²) or worse
Verify correct problem size checks before expensive GPU/CPU operations; prevent resource exhaustion on oversized problems
Identify assertions with overly strict numerical tolerances that fail on legitimate degenerate/edge cases (near-zero pivots, singular matrices, empty problems)
Ensure race conditions are absent in multi-GPU code and multi-threaded server implementations; verify proper synchronization of shared state
Refactor code duplication in solver components (3+ occurrences) into shared utilities; for GPU kernels, use templated device functions to avoid duplication
Check that hard-coded GPU de...
Files:
cpp/src/dual_simplex/cuts.cpp
**/*.{cpp,hpp,h}
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
**/*.{cpp,hpp,h}: Check for unclosed file handles when reading MPS/QPS problem files; ensure RAII patterns or proper cleanup in exception paths
Validate input sanitization to prevent buffer overflows and resource exhaustion attacks; avoid unsafe deserialization of problem files
Prevent thread-unsafe use of global and static variables; use proper mutex/synchronization in server code accessing shared solver state
Files:
cpp/src/dual_simplex/cuts.cpp
**/*.{cu,cpp,hpp,h}
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
Avoid inappropriate use of exceptions in performance-critical GPU operation paths; prefer error codes or CUDA error checking for latency-sensitive code
Files:
cpp/src/dual_simplex/cuts.cpp
🧠 Learnings (11)
📓 Common learnings
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Reduce tight coupling between solver components (presolve, simplex, basis, barrier); increase modularity and reusability of optimization algorithms
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Validate algorithm correctness in optimization logic: simplex pivots, branch-and-bound decisions, routing heuristics, and constraint/objective handling must produce correct results
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Validate correct initialization of variable bounds, constraint coefficients, and algorithm state before solving; ensure reset when transitioning between algorithm phases (presolve, simplex, diving, crossover)
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*test*.{cpp,cu,py} : Add tests for algorithm phase transitions: verify correct initialization of bounds and state when transitioning from presolve to simplex to diving to crossover
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Refactor code duplication in solver components (3+ occurrences) into shared utilities; for GPU kernels, use templated device functions to avoid duplication
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Validate algorithm correctness in optimization logic: simplex pivots, branch-and-bound decisions, routing heuristics, and constraint/objective handling must produce correct results
Applied to files:
cpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*test*.{cpp,cu,py} : Add tests for algorithm phase transitions: verify correct initialization of bounds and state when transitioning from presolve to simplex to diving to crossover
Applied to files:
cpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Refactor code duplication in solver components (3+ occurrences) into shared utilities; for GPU kernels, use templated device functions to avoid duplication
Applied to files:
cpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Validate correct initialization of variable bounds, constraint coefficients, and algorithm state before solving; ensure reset when transitioning between algorithm phases (presolve, simplex, diving, crossover)
Applied to files:
cpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Identify assertions with overly strict numerical tolerances that fail on legitimate degenerate/edge cases (near-zero pivots, singular matrices, empty problems)
Applied to files:
cpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Check numerical stability: prevent overflow/underflow, precision loss, division by zero/near-zero, and use epsilon comparisons for floating-point equality checks
Applied to files:
cpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cpp,hpp,h} : Check for unclosed file handles when reading MPS/QPS problem files; ensure RAII patterns or proper cleanup in exception paths
Applied to files:
cpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-12-04T20:09:09.264Z
Learnt from: chris-maes
Repo: NVIDIA/cuopt PR: 602
File: cpp/src/linear_programming/solve.cu:732-742
Timestamp: 2025-12-04T20:09:09.264Z
Learning: In cpp/src/linear_programming/solve.cu, the barrier solver does not currently return INFEASIBLE or UNBOUNDED status. It only returns OPTIMAL, TIME_LIMIT, NUMERICAL_ISSUES, or CONCURRENT_LIMIT.
Applied to files:
cpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Reduce tight coupling between solver components (presolve, simplex, basis, barrier); increase modularity and reusability of optimization algorithms
Applied to files:
cpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-12-04T04:11:12.640Z
Learnt from: chris-maes
Repo: NVIDIA/cuopt PR: 500
File: cpp/src/dual_simplex/scaling.cpp:68-76
Timestamp: 2025-12-04T04:11:12.640Z
Learning: In the cuOPT dual simplex solver, CSR/CSC matrices (including the quadratic objective matrix Q) are required to have valid dimensions and indices by construction. Runtime bounds checking in performance-critical paths like matrix scaling is avoided to prevent slowdowns. Validation is performed via debug-only check_matrix() calls wrapped in #ifdef CHECK_MATRIX.
Applied to files:
cpp/src/dual_simplex/cuts.cpp
| if (std::abs(lp.A.x[col_start]) != 1.0) { | ||
| printf("Initialize: Slack row %d has non-unit coefficient %e for variable %d\n", i, lp.A.x[col_start], j); | ||
| exit(1); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace exit(1) with error code return.
Lines 927-928 call exit(1) when encountering a non-unit slack coefficient, terminating the entire process. This is inappropriate for a library function. Return an error code instead to allow the caller to handle the error gracefully.
Based on coding guidelines: Verify error propagation from internal functions to user-facing APIs.
🤖 Prompt for AI Agents
In @cpp/src/dual_simplex/cuts.cpp around lines 926 - 929, The code currently
calls exit(1) inside the Initialize block when a slack coefficient check fails
(printf "Initialize: Slack row %d..." uses lp.A.x[col_start] and col_start);
replace the process-terminating exit call with returning a suitable error code
(e.g., return non-zero) from the function (likely Initialize) and adjust the
function signature and all callers to propagate and handle that error instead of
terminating the process. Ensure the return value is documented and used by
callers to surface the error to user-facing APIs.
| if (slack_len != 1) { | ||
| printf("Slack %d has %d nzs in colum\n", j, slack_len); | ||
| exit(1); | ||
| } | ||
| const f_t alpha = lp.A.x[slack_start]; | ||
| if (std::abs(alpha) != 1.0) { | ||
| printf("Slack %d has non-unit coefficient %e\n", j, alpha); | ||
| exit(1); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace exit(1) with error code return for validation failures.
Lines 1159, 1164, and 1200 call exit(1) when encountering invalid slack configurations (non-unit column length or non-unit coefficients). These validation failures should return an error code to allow the caller to handle the error gracefully rather than terminating the process.
Based on coding guidelines: Verify error propagation from internal functions to user-facing APIs.
Also applies to: 1197-1202
🤖 Prompt for AI Agents
In @cpp/src/dual_simplex/cuts.cpp around lines 1157 - 1165, The code currently
calls exit(1) on validation failures when checking slack column length and
coefficient (the checks around slack_len != 1 and std::abs(alpha) != 1.0, and
the similar block at 1197-1202); replace those exit(1) calls with returning a
non-zero error code (e.g., return -1 or a defined ERR_INVALID_SLACK) from the
containing function, update the function signature to propagate the error if it
is currently void, and ensure callers of this function check and propagate the
returned error up to the public API so failures are handled gracefully rather
than terminating the process.
| const i_t col_len = col_end - col_start; | ||
| if (col_len != 1) { | ||
| printf("Add cuts: Slack %d has %d nzs in column\n", slack, col_len); | ||
| exit(1); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace exit(1) with error code return for slack validation.
Lines 1351-1352 call exit(1) when a slack column has an unexpected length. This validation failure should return an error code to allow the caller to handle the error gracefully rather than terminating the process.
Based on coding guidelines: Verify error propagation from internal functions to user-facing APIs.
🤖 Prompt for AI Agents
In @cpp/src/dual_simplex/cuts.cpp around lines 1349 - 1354, The check for slack
column length currently calls exit(1) on failure; change this to return a proper
error code from the function containing this check (include context in the log
with slack and col_len), e.g., return a dedicated error enum or a negative int
(INVALID_SLACK / -1) instead of terminating; update the function signature if
needed to propagate an error code and update all callers to check and propagate
the returned error so the validation failure bubbles up to the user-facing API
rather than calling exit.
| const i_t col_len = col_end - col_start; | ||
| if (col_len != 1) { | ||
| printf("Remove cuts: Slack %d has %d nzs in column\n", j, col_len); | ||
| exit(1); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace exit(1) with error code return for slack validation.
Lines 1485-1486 call exit(1) when a slack column has an unexpected length. This validation failure should return an error code to allow the caller to handle the error gracefully rather than terminating the process.
Based on coding guidelines: Verify error propagation from internal functions to user-facing APIs.
🤖 Prompt for AI Agents
In @cpp/src/dual_simplex/cuts.cpp around lines 1483 - 1488, The block that
checks col_len (using col_start, col_end, col_len and printing with printf for
j) must not call exit(1); instead have the function return a non-zero error code
(e.g., return -1 or a defined ERROR_* constant) so the caller can handle the
failure — adjust the function signature/return type if needed and replace
exit(1) with that return, and then update all callers to check the returned
value and propagate or handle the error appropriately.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 10
Note
Due to the large number of review comments, Critical severity comments were prioritized as inline comments.
🤖 Fix all issues with AI agents
In @cpp/src/dual_simplex/basis_updates.cpp:
- Around line 1275-1278: The consistency check comparing L_nz and predicted_nz
currently calls exit(1) which aborts the process; instead propagate an error up
from the routine that performs this check (the basis update code that computes
L_nz/predicted_nz) by replacing the printf+exit with an error return or thrown
exception consistent with the project's error-handling convention, include the
mismatch details in the returned/raised error so callers can handle or log it,
and update callers of the function to propagate or handle that error instead of
relying on process termination.
In @cpp/src/dual_simplex/branch_and_bound.cpp:
- Around line 1844-1847: Replace the hard process termination in the
bound-strengthening failure branch — currently "if (!feasible) {
settings_.log.printf(...); exit(1); }" — with proper error propagation: log the
failure as you already do, set the solver/object error status (e.g., set status_
or last_error_ to a BoundStrengtheningFailed enum/value), and return an error
indicator from the current function (e.g., return false or a non-zero error
code) instead of calling exit(1); also update callers of this function to check
the returned error and propagate it up to the public API so the failure is
handled gracefully.
- Around line 1825-1828: The code currently calls exit(1) when add_cuts_status
!= 0 which must be replaced with error propagation; in the branch_and_bound
routine replace the exit(1) after settings_.log.printf("Failed to add cuts\n")
with returning an appropriate mip_status_t (e.g., return
mip_status_t::NUMERICAL) or propagate add_cuts_status mapped to the function's
error return type, and ensure the caller of the function (the branch_and_bound
entry point) checks and forwards that status instead of allowing a process exit.
- Around line 1880-1883: Replace the process-terminating exit(1) in the cut
generation block: when cut_status != dual::status_t::OPTIMAL, log the status as
you do with settings_.log.printf, then break out of the cut-generation loop
instead of calling exit; after breaking, propagate the failure to the caller by
returning an appropriate error/status or setting an existing error flag (e.g., a
cut_generation_failed or by returning a non-OK status from the enclosing
function) so callers can handle the condition instead of terminating the
process.
In @cpp/src/dual_simplex/cuts.cpp:
- Around line 1365-1369: The code currently calls new_A_row.append_rows(cuts)
and calls exit(1) on non-zero append_status; instead remove the unconditional
process termination and propagate the error: replace the exit(1) with returning
or throwing the appropriate error value from the current function (e.g., return
append_status or throw a descriptive exception) after logging via
settings.log.printf, ensuring the caller of the function (and its signature) is
updated to handle the propagated error from new_A_row.append_rows; keep the
existing log message but do not terminate the process.
- Around line 39-56: In cut_distance, avoid dividing by a near-zero cut_norm:
compute an eps = std::numeric_limits<f_t>::epsilon() (or a small scaled
tolerance) and if cut_norm > eps set distance = cut_violation / cut_norm,
otherwise handle the degenerate case deterministically (e.g., if cut_violation >
0 set distance = std::numeric_limits<f_t>::infinity(), else set distance = 0);
add the eps check before the division and include <limits> if needed,
referencing cut_distance, cut_norm, cut_violation and distance.
- Around line 68-84: The cut_orthogonality function divides by (norm_i * norm_j)
without guarding against zero/near-zero; compute f_t denom = norm_i * norm_j and
compare it to a small threshold (e.g. std::numeric_limits<f_t>::epsilon() or
epsilon scaled by norms) and if denom is <= epsilon return a safe fallback (e.g.
f_t(0) or f_t(1) per project convention) to avoid division by zero; update
cut_pool_t<i_t,f_t>::cut_orthogonality to perform this check before computing
1.0 - std::abs(dot) / denom.
In @cpp/src/dual_simplex/cuts.hpp:
- Around line 45-64: The function minimum_violation should not call exit(1) when
a cut fails; instead throw an exception (e.g., std::runtime_error) from
minimum_violation with a clear message including the cut index and values (k,
Cx[k], cut_rhs[k]) so callers can handle the validation failure; update the
throw site where exit(1) is currently used and ensure the translation unit
includes the appropriate header for exceptions (e.g., <stdexcept>).
In @cpp/src/dual_simplex/pseudo_costs.cpp:
- Around line 353-455: In reliable_variable_selection, avoid unlocking the
shared mutex before logging and ensure select cannot be -1: keep the mutex
locked (or re-lock) around accesses that read shared pseudo_cost_* state used in
the final log.printf (refer to mutex and log.printf), or move the mutex.unlock()
to after logging so shared state is protected; and guard against empty
fractional or no-score-selected by checking if num_fractional==0 or select==-1
and returning a safe sentinel (e.g., -1) or choosing a fallback branch_var
before using score[select] (references: fractional, score, select, branch_var).
🟠 Major comments (11)
cpp/src/dual_simplex/branch_and_bound.cpp-604-628 (1)
604-628: Replace C-style FILE with std::ofstream for proper resource cleanup.*The debug code uses C-style file I/O (
fopen/fclose), which is not exception-safe. If any function betweenfopenandfclosethrows an exception, the file handle will leak. Usestd::ofstreaminstead to ensure proper cleanup via RAII.🔎 Apply this diff
-#if 1 - FILE* fid = NULL; - fid = fopen("solution.dat", "w"); - if (fid != NULL) { +#ifdef DEBUG_WRITE_SOLUTION + { + std::ofstream fid("solution.dat"); + if (fid.is_open()) { printf("Writing solution.dat\n"); std::vector<f_t> residual = original_lp_.rhs; matrix_vector_multiply(original_lp_.A, 1.0, incumbent_.x, -1.0, residual); // ... existing code ... - fprintf(fid, "%d\n", n); + fid << n << "\n"; for (i_t j = 0; j < n; ++j) { - fprintf(fid, "%.17g\n", incumbent_.x[j]); + fid << std::setprecision(17) << incumbent_.x[j] << "\n"; seed = hash_combine_f(seed, incumbent_.x[j]); } printf("Solution hash: %20x\n", seed); - fclose(fid); + } // Destructor closes file automatically } #endifCommittable suggestion skipped: line range outside the PR's diff.
cpp/src/dual_simplex/branch_and_bound.cpp-1617-1702 (1)
1617-1702: Replace C-style FILE I/O with C++ streams for exception safety.Lines 1620-1702 use C-style
fopen/fclosefor reading and writing solution files. This is not exception-safe. If any operation throws betweenfopenandfclose, file handles will leak.🔎 Recommended fix
- FILE* fid = NULL; - fid = fopen("solution.dat", "r"); - if (fid != NULL) + std::ifstream fid("solution.dat"); + if (fid.is_open()) { i_t n_solution_dat; - i_t count = fscanf(fid, "%d\n", &n_solution_dat); + fid >> n_solution_dat; + if (fid.good() && n_solution_dat == original_lp_.num_cols) { // ... read solution ... for (i_t j = 0; j < n_solution_dat; j++) { - count = fscanf(fid, "%lf", &saved_solution[j]); + fid >> saved_solution[j]; } } - fclose(fid); + } // Destructor closes fileApply similar changes for the "solution.dat.2" write operation.
Based on coding guidelines: Check for unclosed file handles; ensure RAII patterns or proper cleanup in exception paths.
Committable suggestion skipped: line range outside the PR's diff.
cpp/src/dual_simplex/pseudo_costs.cpp-160-160 (1)
160-160: Convert debugprintfto use the logging system.Line 160 uses
printfdirectly instead of the settings logging system. This bypasses the configured log level and cannot be suppressed whenset_log(false)is called.🔎 Apply this diff
- printf("Trial branching on variable %d. Lo: %e Up: %e. Iter %d. Status %d. Obj %e\n", branch_var, child_problem.lower[branch_var], child_problem.upper[branch_var], iter, status, compute_objective(child_problem, solution.x)); + settings.log.debug("Trial branching on variable %d. Lo: %e Up: %e. Iter %d. Status %d. Obj %e\n", branch_var, child_problem.lower[branch_var], child_problem.upper[branch_var], iter, status, compute_objective(child_problem, solution.x));cpp/src/dual_simplex/cuts.cpp-1211-1219 (1)
1211-1219: Replaceexit(1)with error code return for validation failures.Lines 1213 and 1218 terminate the process on slack validation failures. These should return an error code to allow graceful error handling.
🔎 Apply this diff
if (slack_len != 1) { printf("Slack %d has %d nzs in colum\n", j, slack_len); - exit(1); + return; } const f_t alpha = lp.A.x[slack_start]; if (std::abs(alpha) != 1.0) { printf("Slack %d has non-unit coefficient %e\n", j, alpha); - exit(1); + return; }Based on coding guidelines: Verify error propagation from internal functions to user-facing APIs.
cpp/src/dual_simplex/phase2.cpp-2459-2474 (1)
2459-2474: Verify correctness of iteration continuation when no leaving variable found.When no leaving variable is found but primal infeasibility exists, this logic clamps nonpositive steepest-edge norms and continues iterating. Concerns:
- Infinite loop risk: If the underlying issue persists, iterations may continue indefinitely without making progress
- Masked optimality: The solver may have reached optimality but fails to detect it due to corrupted steepest-edge data
- Changed semantics: Previously this would proceed to optimality check; now it attempts recovery
Consider:
- Adding an iteration limit for this recovery loop
- Triggering a basis refactorization instead of just clamping norms
- Validating that
primal_infeasibilityis computed correctly after clampingBased on coding guidelines: Validate algorithm correctness in optimization logic.
cpp/src/dual_simplex/cuts.cpp-1537-1542 (1)
1537-1542: Replaceexit(1)with error code return for slack validation.Line 1540 terminates the process when a slack column has unexpected length. This is in the public
remove_cutsfunction which should not terminate the process.🔎 Apply this diff
if (col_len != 1) { printf("Remove cuts: Slack %d has %d nzs in column\n", j, col_len); - exit(1); + return; }Based on coding guidelines: Verify error propagation from internal functions to user-facing APIs.
Committable suggestion skipped: line range outside the PR's diff.
cpp/src/dual_simplex/cuts.cpp-585-591 (1)
585-591: Replaceexit(1)with error code return.Line 587 terminates the process when a slack column has unexpected length. This is inappropriate for library code.
🔎 Apply this diff to propagate error
if (col_len != 1) { printf("Generate MIR cuts: Slack %d has %d nzs in column\n", slack, col_len); - exit(1); + return; }Based on coding guidelines: Verify error propagation from internal functions to user-facing APIs.
Committable suggestion skipped: line range outside the PR's diff.
cpp/src/dual_simplex/cuts.cpp-946-949 (1)
946-949: Replaceexit(1)with error code return.Line 948 terminates the process when encountering a negative lower bound. Return an error code instead.
🔎 Apply this diff
if (lp.lower[j] < 0) { settings_.log.printf("Variable %d has negative lower bound %e\n", j, lp.lower[j]); - exit(1); + return; }Based on coding guidelines: Verify error propagation from internal functions to user-facing APIs.
Committable suggestion skipped: line range outside the PR's diff.
cpp/src/dual_simplex/cuts.cpp-938-941 (1)
938-941: Replaceexit(1)with error code return.Lines 940 terminates the process when encountering a non-unit slack coefficient. This validation failure should return an error code instead.
🔎 Apply this diff
if (std::abs(lp.A.x[col_start]) != 1.0) { printf("Initialize: Slack row %d has non-unit coefficient %e for variable %d\n", i, lp.A.x[col_start], j); - exit(1); + return; }Based on coding guidelines: Verify error propagation from internal functions to user-facing APIs.
cpp/src/dual_simplex/cuts.cpp-1403-1408 (1)
1403-1408: Replaceexit(1)with error code return for slack validation.Line 1406 terminates the process when a slack column has unexpected length. Return an error code instead.
🔎 Apply this diff
if (col_len != 1) { printf("Add cuts: Slack %d has %d nzs in column\n", slack, col_len); - exit(1); + return -1; }Based on coding guidelines: Verify error propagation from internal functions to user-facing APIs.
cpp/src/dual_simplex/phase2.cpp-2336-2351 (1)
2336-2351: Add diagnostic logging to initialization safeguard and address overly strict assertion.The clamping at line 2346 silently masks when steepest-edge norm initialization produces invalid values (≤ 0.0). This is inconsistent with the diagnostic approach at line 2468 during iteration selection, which logs these occurrences.
The root cause lies in the assertion at line 1221 (
assert(init > 0)), which is overly strict for degenerate cases (near-singular basis matrices, empty rows). Since assertions are disabled in Release builds, this initialization can produce invalid values without warning, and line 2346 becomes a silent safety net.Recommend:
- Add logging at line 2346 to match line 2468's diagnostic printf, including iteration context and norm values
- Replace the overly strict assertion at line 1221 with proper error handling for singular/near-singular bases
- Consider returning an error code to trigger refactorization when initialization fails
Based on coding guidelines: Identify assertions with overly strict numerical tolerances that fail on legitimate degenerate/edge cases and validate algorithm state before solving.
🧹 Nitpick comments (6)
cpp/src/linear_programming/optimization_problem.cu (3)
220-235: Consider fusing the two thrust operations into a single kernel.The two
thrust::for_each_ncalls perform similar scatter operations on disjoint arrays (indices and values). Combining them into a single kernel could reduce launch overhead and improve performance.♻️ Potential optimization: fused kernel
- thrust::for_each_n(rmm::exec_policy(stream_view_), - thrust::make_counting_iterator<i_t>(0), - H_nz, - [span_H_map = cuopt::make_span(d_H_map), - span_H_j = cuopt::make_span(d_H_j), - span_H_indices = cuopt::make_span(d_H_indices)] __device__(i_t k) { - span_H_indices[span_H_map[k]] = span_H_j[k]; - }); - thrust::for_each_n(rmm::exec_policy(stream_view_), - thrust::make_counting_iterator<i_t>(0), - H_nz, - [span_H_map = cuopt::make_span(d_H_map), - span_H_x = cuopt::make_span(d_H_x), - span_H_values = cuopt::make_span(d_H_values)] __device__(i_t k) { - span_H_values[span_H_map[k]] = span_H_x[k]; - }); + thrust::for_each_n(rmm::exec_policy(stream_view_), + thrust::make_counting_iterator<i_t>(0), + H_nz, + [span_H_map = cuopt::make_span(d_H_map), + span_H_j = cuopt::make_span(d_H_j), + span_H_x = cuopt::make_span(d_H_x), + span_H_indices = cuopt::make_span(d_H_indices), + span_H_values = cuopt::make_span(d_H_values)] __device__(i_t k) { + i_t pos = span_H_map[k]; + span_H_indices[pos] = span_H_j[k]; + span_H_values[pos] = span_H_x[k]; + });
244-270: Column indices within rows are not sorted after deduplication.The deduplication logic correctly removes duplicate entries, but the resulting CSR format may have unsorted column indices within each row. Most sparse matrix operations expect sorted column indices for optimal cache performance and correct behavior.
Consider sorting column indices within each row after deduplication:
♻️ Add column sorting per row
After line 267, add sorting logic:
Q_offsets_[qn] = nz; // finalize Q Q_indices_.resize(nz); Q_values_.resize(nz); // Sort columns within each row for (i_t i = 0; i < qn; ++i) { i_t row_start = Q_offsets_[i]; i_t row_end = Q_offsets_[i + 1]; // Create index array for sorting std::vector<std::pair<i_t, f_t>> row_entries; for (i_t p = row_start; p < row_end; ++p) { row_entries.emplace_back(Q_indices_[p], Q_values_[p]); } // Sort by column index std::sort(row_entries.begin(), row_entries.end()); // Write back sorted entries for (size_t k = 0; k < row_entries.size(); ++k) { Q_indices_[row_start + k] = row_entries[k].first; Q_values_[row_start + k] = row_entries[k].second; } }
272-272: PSD validation remains unimplemented.The parameter
validate_positive_semi_definiteis accepted but not used. If the downstream barrier solver requires a PSD matrix, incorrect results or solver failures could occur with indefinite matrices.Consider whether PSD validation should be implemented. If needed, this could be done using Cholesky factorization or eigenvalue checks.
Would you like me to open an issue to track implementing PSD validation, or should this validation be performed elsewhere in the solver pipeline?
cpp/src/dual_simplex/basis_updates.cpp (1)
2274-2274: Consider using.log()instead of.printf()for consistency.This line was changed from
.debug()to.printf(), which now always prints regardless of log level. If factorization failure should always be visible, consider using.log()for consistency with the logger's configured level.- settings.log.printf("Initial factorization failed\n"); + settings.log.log("Initial factorization failed\n");cpp/src/dual_simplex/branch_and_bound.cpp (1)
305-305: Consider using the logging system instead of printf.Lines 305, 355, and 372 use
printfdirectly. For consistency with the rest of the codebase and to respect log level settings, consider usingsettings_.log.printf()orsettings_.log.debug().Also applies to: 355-355, 372-372
cpp/src/dual_simplex/phase2.cpp (1)
2017-2017: Consider removing or documenting theinfoparameter.The
infoparameter added toprepare_optimalityis used only for diagnostic logging (line 2088) to distinguish which code path reached optimality.This adds parameter-passing overhead across multiple call sites (lines 2476, 2643, 2681) for minimal diagnostic value. Consider:
- Removing the parameter and using existing
settings.log.debug()with context at call sites- Or document its purpose if it will be used for more extensive diagnostics
Based on learnings: Reduce tight coupling between solver components.
Also applies to: 2086-2089
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
cpp/src/dual_simplex/basis_updates.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hppcpp/src/dual_simplex/phase2.cppcpp/src/dual_simplex/pseudo_costs.cppcpp/src/linear_programming/optimization_problem.cu
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{cu,cuh,cpp,hpp,h}
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
**/*.{cu,cuh,cpp,hpp,h}: Track GPU device memory allocations and deallocations to prevent memory leaks; ensure cudaMalloc/cudaFree balance and cleanup of streams/events
Validate algorithm correctness in optimization logic: simplex pivots, branch-and-bound decisions, routing heuristics, and constraint/objective handling must produce correct results
Check numerical stability: prevent overflow/underflow, precision loss, division by zero/near-zero, and use epsilon comparisons for floating-point equality checks
Validate correct initialization of variable bounds, constraint coefficients, and algorithm state before solving; ensure reset when transitioning between algorithm phases (presolve, simplex, diving, crossover)
Ensure variables and constraints are accessed from the correct problem context (original vs presolve vs folded vs postsolve); verify index mapping consistency across problem transformations
For concurrent CUDA operations (barriers, async operations), explicitly create and manage dedicated streams instead of reusing the default stream; document stream lifecycle
Eliminate unnecessary host-device synchronization (cudaDeviceSynchronize) in hot paths that blocks GPU pipeline; use streams and events for async execution
Assess algorithmic complexity for large-scale problems (millions of variables/constraints); ensure O(n log n) or better complexity, not O(n²) or worse
Verify correct problem size checks before expensive GPU/CPU operations; prevent resource exhaustion on oversized problems
Identify assertions with overly strict numerical tolerances that fail on legitimate degenerate/edge cases (near-zero pivots, singular matrices, empty problems)
Ensure race conditions are absent in multi-GPU code and multi-threaded server implementations; verify proper synchronization of shared state
Refactor code duplication in solver components (3+ occurrences) into shared utilities; for GPU kernels, use templated device functions to avoid duplication
Check that hard-coded GPU de...
Files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/basis_updates.cppcpp/src/dual_simplex/pseudo_costs.cppcpp/src/linear_programming/optimization_problem.cucpp/src/dual_simplex/phase2.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hpp
**/*.{cpp,hpp,h}
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
**/*.{cpp,hpp,h}: Check for unclosed file handles when reading MPS/QPS problem files; ensure RAII patterns or proper cleanup in exception paths
Validate input sanitization to prevent buffer overflows and resource exhaustion attacks; avoid unsafe deserialization of problem files
Prevent thread-unsafe use of global and static variables; use proper mutex/synchronization in server code accessing shared solver state
Files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/basis_updates.cppcpp/src/dual_simplex/pseudo_costs.cppcpp/src/dual_simplex/phase2.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hpp
**/*.{cu,cpp,hpp,h}
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
Avoid inappropriate use of exceptions in performance-critical GPU operation paths; prefer error codes or CUDA error checking for latency-sensitive code
Files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/basis_updates.cppcpp/src/dual_simplex/pseudo_costs.cppcpp/src/linear_programming/optimization_problem.cucpp/src/dual_simplex/phase2.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hpp
**/*.{cu,cuh}
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
**/*.{cu,cuh}: Every CUDA kernel launch and memory operation must have error checking with CUDA_CHECK or equivalent verification
Avoid reinventing functionality already available in Thrust, CCCL, or RMM libraries; prefer standard library utilities over custom implementations
Files:
cpp/src/linear_programming/optimization_problem.cu
**/*.cu
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
**/*.cu: Verify race conditions and correctness of GPU kernel shared memory, atomics, and warp-level operations
Detect inefficient GPU kernel launches with low occupancy or poor memory access patterns; optimize for coalesced memory access and minimize warp divergence in hot paths
Files:
cpp/src/linear_programming/optimization_problem.cu
**/*.{h,hpp,py}
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
Verify C API does not break ABI stability (no struct layout changes, field reordering); maintain backward compatibility in Python and server APIs with deprecation warnings
Files:
cpp/src/dual_simplex/cuts.hpp
🧠 Learnings (26)
📓 Common learnings
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Reduce tight coupling between solver components (presolve, simplex, basis, barrier); increase modularity and reusability of optimization algorithms
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Validate algorithm correctness in optimization logic: simplex pivots, branch-and-bound decisions, routing heuristics, and constraint/objective handling must produce correct results
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/basis_updates.cppcpp/src/dual_simplex/pseudo_costs.cppcpp/src/linear_programming/optimization_problem.cucpp/src/dual_simplex/phase2.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Reduce tight coupling between solver components (presolve, simplex, basis, barrier); increase modularity and reusability of optimization algorithms
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/basis_updates.cppcpp/src/dual_simplex/phase2.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Validate correct initialization of variable bounds, constraint coefficients, and algorithm state before solving; ensure reset when transitioning between algorithm phases (presolve, simplex, diving, crossover)
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/basis_updates.cppcpp/src/dual_simplex/pseudo_costs.cppcpp/src/linear_programming/optimization_problem.cucpp/src/dual_simplex/phase2.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*test*.{cpp,cu,py} : Add tests for algorithm phase transitions: verify correct initialization of bounds and state when transitioning from presolve to simplex to diving to crossover
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/basis_updates.cppcpp/src/dual_simplex/phase2.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Ensure variables and constraints are accessed from the correct problem context (original vs presolve vs folded vs postsolve); verify index mapping consistency across problem transformations
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/pseudo_costs.cppcpp/src/linear_programming/optimization_problem.cucpp/src/dual_simplex/phase2.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Assess algorithmic complexity for large-scale problems (millions of variables/constraints); ensure O(n log n) or better complexity, not O(n²) or worse
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/linear_programming/optimization_problem.cu
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*test*.{cpp,cu,py} : Test with free variables, singleton problems, and extreme problem dimensions near resource limits to validate edge case handling
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Refactor code duplication in solver components (3+ occurrences) into shared utilities; for GPU kernels, use templated device functions to avoid duplication
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/linear_programming/optimization_problem.cucpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*test*.{cpp,cu,py} : Add tests for problem transformations: verify correctness of original→transformed→postsolve mappings and index consistency across problem representations
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/linear_programming/optimization_problem.cu
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Identify assertions with overly strict numerical tolerances that fail on legitimate degenerate/edge cases (near-zero pivots, singular matrices, empty problems)
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/basis_updates.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-12-04T20:09:09.264Z
Learnt from: chris-maes
Repo: NVIDIA/cuopt PR: 602
File: cpp/src/linear_programming/solve.cu:732-742
Timestamp: 2025-12-04T20:09:09.264Z
Learning: In cpp/src/linear_programming/solve.cu, the barrier solver does not currently return INFEASIBLE or UNBOUNDED status. It only returns OPTIMAL, TIME_LIMIT, NUMERICAL_ISSUES, or CONCURRENT_LIMIT.
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-10-22T14:25:22.899Z
Learnt from: aliceb-nv
Repo: NVIDIA/cuopt PR: 527
File: cpp/src/mip/diversity/lns/rins.cu:167-175
Timestamp: 2025-10-22T14:25:22.899Z
Learning: In MIP (Mixed Integer Programming) problems in the cuOPT codebase, `n_integer_vars == 0` is impossible by definition—MIP problems must have at least one integer variable. If there are no integer variables, it would be a pure Linear Programming (LP) problem, not a MIP problem.
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cpp,hpp,h} : Check for unclosed file handles when reading MPS/QPS problem files; ensure RAII patterns or proper cleanup in exception paths
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/basis_updates.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Verify correct problem size checks before expensive GPU/CPU operations; prevent resource exhaustion on oversized problems
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/basis_updates.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*test*.{cpp,cu,py} : Write tests validating numerical correctness of optimization results (not just 'runs without error'); test degenerate cases (infeasible, unbounded, empty, singleton problems)
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cpp
📚 Learning: 2025-12-03T23:29:26.391Z
Learnt from: chris-maes
Repo: NVIDIA/cuopt PR: 500
File: cpp/src/dual_simplex/sparse_matrix.cpp:519-524
Timestamp: 2025-12-03T23:29:26.391Z
Learning: In cpp/src/dual_simplex/sparse_matrix.cpp, the check_matrix() function is debug/diagnostic code (wrapped in #ifdef CHECK_MATRIX) that intentionally prints errors without necessarily returning early. The return codes from this debug code are not actively checked; the purpose is to print all validation errors in one pass for better diagnostics.
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Check numerical stability: prevent overflow/underflow, precision loss, division by zero/near-zero, and use epsilon comparisons for floating-point equality checks
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cpp,hpp,h} : Avoid inappropriate use of exceptions in performance-critical GPU operation paths; prefer error codes or CUDA error checking for latency-sensitive code
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-12-04T04:11:12.640Z
Learnt from: chris-maes
Repo: NVIDIA/cuopt PR: 500
File: cpp/src/dual_simplex/scaling.cpp:68-76
Timestamp: 2025-12-04T04:11:12.640Z
Learning: In the cuOPT dual simplex solver, CSR/CSC matrices (including the quadratic objective matrix Q) are required to have valid dimensions and indices by construction. Runtime bounds checking in performance-critical paths like matrix scaling is avoided to prevent slowdowns. Validation is performed via debug-only check_matrix() calls wrapped in #ifdef CHECK_MATRIX.
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/basis_updates.cppcpp/src/linear_programming/optimization_problem.cucpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Verify error propagation from CUDA to user-facing APIs is complete; ensure CUDA errors are caught and mapped to meaningful user error codes
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/basis_updates.cppcpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cpp,hpp,h} : Prevent thread-unsafe use of global and static variables; use proper mutex/synchronization in server code accessing shared solver state
Applied to files:
cpp/src/dual_simplex/pseudo_costs.cpp
📚 Learning: 2025-12-06T00:22:48.638Z
Learnt from: chris-maes
Repo: NVIDIA/cuopt PR: 500
File: cpp/tests/linear_programming/c_api_tests/c_api_test.c:1033-1048
Timestamp: 2025-12-06T00:22:48.638Z
Learning: In cuOPT's quadratic programming API, when a user provides a quadratic objective matrix Q via set_quadratic_objective_matrix or the C API functions cuOptCreateQuadraticProblem/cuOptCreateQuadraticRangedProblem, the API internally computes Q_symmetric = Q + Q^T and the barrier solver uses 0.5 * x^T * Q_symmetric * x. From the user's perspective, the convention is x^T Q x. For a diagonal Q with values [q1, q2, ...], the resulting quadratic terms are q1*x1^2 + q2*x2^2 + ...
Applied to files:
cpp/src/linear_programming/optimization_problem.cu
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.cu : Detect inefficient GPU kernel launches with low occupancy or poor memory access patterns; optimize for coalesced memory access and minimize warp divergence in hot paths
Applied to files:
cpp/src/linear_programming/optimization_problem.cu
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh} : Avoid reinventing functionality already available in Thrust, CCCL, or RMM libraries; prefer standard library utilities over custom implementations
Applied to files:
cpp/src/linear_programming/optimization_problem.cu
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to docs/**/*.{rst,md} : Document numerical tolerances, parameter ranges, and algorithm phase descriptions in API documentation when solver behavior or tolerances change
Applied to files:
cpp/src/dual_simplex/phase2.cpp
🧬 Code graph analysis (4)
cpp/src/dual_simplex/basis_updates.cpp (2)
cpp/src/dual_simplex/basis_updates.hpp (5)
L0_(116-120)L0_(367-371)nz(149-149)nz(401-401)nz(415-415)cpp/src/dual_simplex/sparse_matrix.hpp (6)
matrix_transpose_vector_multiply(240-270)matrix_transpose_vector_multiply(240-244)nz(80-80)i(163-174)matrix_vector_multiply(274-306)matrix_vector_multiply(274-278)
cpp/src/dual_simplex/pseudo_costs.cpp (1)
cpp/src/dual_simplex/pseudo_costs.hpp (4)
lp(50-58)fractional(46-48)fractional(60-61)num_initialized_down(41-44)
cpp/src/dual_simplex/phase2.cpp (1)
cpp/src/dual_simplex/crossover.cpp (2)
primal_infeasibility(159-199)primal_infeasibility(159-162)
cpp/src/dual_simplex/cuts.cpp (3)
cpp/src/dual_simplex/cuts.hpp (5)
cut_type(83-83)cut_storage_(94-94)i(99-99)a(255-261)best_cuts(88-88)cpp/src/dual_simplex/sparse_vector.hpp (4)
p(40-40)p(42-42)x(44-44)y(54-54)cpp/src/dual_simplex/sparse_matrix.hpp (1)
col_start(51-51)
🪛 Cppcheck (2.19.0)
cpp/src/dual_simplex/branch_and_bound.cpp
[error] 615-615: Resource leak
(resourceLeak)
cpp/src/dual_simplex/pseudo_costs.cpp
[warning] 451-451: Array index -1 is out of bounds.
(negativeContainerIndex)
🔇 Additional comments (2)
cpp/src/linear_programming/optimization_problem.cu (1)
163-189: LGTM! Triplet construction correctly builds H = Q + Q^T.The logic correctly adds both (i,j,x) and (j,i,x) for each entry in Q, ensuring the resulting matrix H is symmetric. The diagonal doubling is intentional and correct per the API convention where the solver uses 0.5 * x^T * (Q + Q^T) * x.
cpp/src/dual_simplex/branch_and_bound.cpp (1)
1797-1801: Replaceexit(1)with error propagation.Line 1799 calls
exit(1)when a saved solution violates a cut. This terminates the entire process. Return an error status instead so the caller can handle the failure gracefully.Based on coding guidelines: Verify error propagation from internal functions to user-facing APIs is complete.
Likely an incorrect or invalid review comment.
| if (L_nz != predicted_nz) { | ||
| printf("L_nz %d predicted_nz %d\n", L_nz, predicted_nz); | ||
| exit(1); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace exit(1) with error propagation.
Lines 1276-1277 unconditionally call exit(1) when an internal consistency check fails. This terminates the application in production builds and prevents proper error recovery.
🔎 Apply this diff to propagate the error
if (L_nz != predicted_nz) {
- printf("L_nz %d predicted_nz %d\n", L_nz, predicted_nz);
- exit(1);
+ settings.log.printf("Internal consistency error: L_nz %d != predicted_nz %d\n", L_nz, predicted_nz);
+ return -1;
}Based on coding guidelines: Verify error propagation from internal functions to user-facing APIs.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In @cpp/src/dual_simplex/basis_updates.cpp around lines 1275 - 1278, The
consistency check comparing L_nz and predicted_nz currently calls exit(1) which
aborts the process; instead propagate an error up from the routine that performs
this check (the basis update code that computes L_nz/predicted_nz) by replacing
the printf+exit with an error return or thrown exception consistent with the
project's error-handling convention, include the mismatch details in the
returned/raised error so callers can handle or log it, and update callers of the
function to propagate or handle that error instead of relying on process
termination.
| for (i_t slack : new_slacks_) { | ||
| const i_t col_start = original_lp_.A.col_start[slack]; | ||
| const i_t col_end = original_lp_.A.col_start[slack + 1]; | ||
| const i_t col_len = col_end - col_start; | ||
| if (col_len != 1) { | ||
| printf("Slack %d has %d nzs\n", slack, col_len); | ||
| exit(1); | ||
| } | ||
| const i_t i = original_lp_.A.i[col_start]; | ||
| const f_t x = original_lp_.A.x[col_start]; | ||
| if (std::abs(x) != 1.0) { | ||
| printf("Slack %d row %d has non-unit coefficient %e\n", slack, i, x); | ||
| exit(1); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace exit(1) calls with error propagation.
Lines 254 and 260 call exit(1) when slack validation fails, terminating the entire process. Library code should not call exit().
🔎 Recommended approach
For debug-only checks, wrap in #ifdef DEBUG:
+#ifdef DEBUG
if (col_len != 1) {
printf("Slack %d has %d nzs\n", slack, col_len);
- exit(1);
+ assert(false && "Invalid slack column length");
}
+#endifOr propagate errors from the constructor by throwing an exception with a descriptive message.
Based on coding guidelines: Verify error propagation from internal functions to user-facing APIs.
Committable suggestion skipped: line range outside the PR's diff.
| if (add_cuts_status != 0) { | ||
| settings_.log.printf("Failed to add cuts\n"); | ||
| exit(1); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace exit(1) with error return.
Line 1827 calls exit(1) when add_cuts fails. Library code must not terminate the process. Return mip_status_t::NUMERICAL or propagate the error status.
Based on coding guidelines: Verify error propagation to user-facing APIs is complete.
🤖 Prompt for AI Agents
In @cpp/src/dual_simplex/branch_and_bound.cpp around lines 1825 - 1828, The code
currently calls exit(1) when add_cuts_status != 0 which must be replaced with
error propagation; in the branch_and_bound routine replace the exit(1) after
settings_.log.printf("Failed to add cuts\n") with returning an appropriate
mip_status_t (e.g., return mip_status_t::NUMERICAL) or propagate add_cuts_status
mapped to the function's error return type, and ensure the caller of the
function (the branch_and_bound entry point) checks and forwards that status
instead of allowing a process exit.
| if (!feasible) { | ||
| settings_.log.printf("Bound strengthening failed\n"); | ||
| exit(1); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace exit(1) with error return.
Line 1846 calls exit(1) when bound strengthening fails. Convert this to proper error handling by setting an error status and returning gracefully.
Based on coding guidelines: Verify error propagation from internal functions to user-facing APIs.
🤖 Prompt for AI Agents
In @cpp/src/dual_simplex/branch_and_bound.cpp around lines 1844 - 1847, Replace
the hard process termination in the bound-strengthening failure branch —
currently "if (!feasible) { settings_.log.printf(...); exit(1); }" — with proper
error propagation: log the failure as you already do, set the solver/object
error status (e.g., set status_ or last_error_ to a BoundStrengtheningFailed
enum/value), and return an error indicator from the current function (e.g.,
return false or a non-zero error code) instead of calling exit(1); also update
callers of this function to check the returned error and propagate it up to the
public API so the failure is handled gracefully.
| if (cut_status != dual::status_t::OPTIMAL) { | ||
| settings_.log.printf("Cut status %d\n", cut_status); | ||
| exit(1); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace exit(1) with error handling.
Line 1882 calls exit(1) when cut_status != OPTIMAL. Break out of the cut generation loop instead of terminating the process.
if (cut_status != dual::status_t::OPTIMAL) {
settings_.log.printf("Cut status %d\n", cut_status);
- exit(1);
+ break; // Exit cut generation and proceed with B&B
}Based on coding guidelines: Avoid terminating the process from library code; propagate errors to callers.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In @cpp/src/dual_simplex/branch_and_bound.cpp around lines 1880 - 1883, Replace
the process-terminating exit(1) in the cut generation block: when cut_status !=
dual::status_t::OPTIMAL, log the status as you do with settings_.log.printf,
then break out of the cut-generation loop instead of calling exit; after
breaking, propagate the failure to the caller by returning an appropriate
error/status or setting an existing error flag (e.g., a cut_generation_failed or
by returning a non-OK status from the enclosing function) so callers can handle
the condition instead of terminating the process.
| template <typename i_t, typename f_t> | ||
| f_t cut_pool_t<i_t, f_t>::cut_orthogonality(i_t i, i_t j) | ||
| { | ||
| const i_t i_start = cut_storage_.row_start[i]; | ||
| const i_t i_end = cut_storage_.row_start[i + 1]; | ||
| const i_t i_nz = i_end - i_start; | ||
| const i_t j_start = cut_storage_.row_start[j]; | ||
| const i_t j_end = cut_storage_.row_start[j + 1]; | ||
| const i_t j_nz = j_end - j_start; | ||
|
|
||
| f_t dot = sparse_dot(cut_storage_.j.data() + i_start, cut_storage_.x.data() + i_start, i_nz, | ||
| cut_storage_.j.data() + j_start, cut_storage_.x.data() + j_start, j_nz); | ||
|
|
||
| f_t norm_i = cut_norms_[i]; | ||
| f_t norm_j = cut_norms_[j]; | ||
| return 1.0 - std::abs(dot) / (norm_i * norm_j); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add epsilon check to prevent division by zero in orthogonality calculation.
Line 83 divides by (norm_i * norm_j) without checking if either norm is zero or near-zero. For degenerate cuts with near-zero coefficients, this causes division by zero.
🔎 Apply this diff to add safety check
f_t norm_i = cut_norms_[i];
f_t norm_j = cut_norms_[j];
+ const f_t denom = norm_i * norm_j;
+ if (denom < 1e-12) {
+ return 1.0;
+ }
- return 1.0 - std::abs(dot) / (norm_i * norm_j);
+ return 1.0 - std::abs(dot) / denom;Based on coding guidelines: Check numerical stability to prevent division by zero/near-zero.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| template <typename i_t, typename f_t> | |
| f_t cut_pool_t<i_t, f_t>::cut_orthogonality(i_t i, i_t j) | |
| { | |
| const i_t i_start = cut_storage_.row_start[i]; | |
| const i_t i_end = cut_storage_.row_start[i + 1]; | |
| const i_t i_nz = i_end - i_start; | |
| const i_t j_start = cut_storage_.row_start[j]; | |
| const i_t j_end = cut_storage_.row_start[j + 1]; | |
| const i_t j_nz = j_end - j_start; | |
| f_t dot = sparse_dot(cut_storage_.j.data() + i_start, cut_storage_.x.data() + i_start, i_nz, | |
| cut_storage_.j.data() + j_start, cut_storage_.x.data() + j_start, j_nz); | |
| f_t norm_i = cut_norms_[i]; | |
| f_t norm_j = cut_norms_[j]; | |
| return 1.0 - std::abs(dot) / (norm_i * norm_j); | |
| } | |
| template <typename i_t, typename f_t> | |
| f_t cut_pool_t<i_t, f_t>::cut_orthogonality(i_t i, i_t j) | |
| { | |
| const i_t i_start = cut_storage_.row_start[i]; | |
| const i_t i_end = cut_storage_.row_start[i + 1]; | |
| const i_t i_nz = i_end - i_start; | |
| const i_t j_start = cut_storage_.row_start[j]; | |
| const i_t j_end = cut_storage_.row_start[j + 1]; | |
| const i_t j_nz = j_end - j_start; | |
| f_t dot = sparse_dot(cut_storage_.j.data() + i_start, cut_storage_.x.data() + i_start, i_nz, | |
| cut_storage_.j.data() + j_start, cut_storage_.x.data() + j_start, j_nz); | |
| f_t norm_i = cut_norms_[i]; | |
| f_t norm_j = cut_norms_[j]; | |
| const f_t denom = norm_i * norm_j; | |
| if (denom < 1e-12) { | |
| return 1.0; | |
| } | |
| return 1.0 - std::abs(dot) / denom; | |
| } |
🤖 Prompt for AI Agents
In @cpp/src/dual_simplex/cuts.cpp around lines 68 - 84, The cut_orthogonality
function divides by (norm_i * norm_j) without guarding against zero/near-zero;
compute f_t denom = norm_i * norm_j and compare it to a small threshold (e.g.
std::numeric_limits<f_t>::epsilon() or epsilon scaled by norms) and if denom is
<= epsilon return a safe fallback (e.g. f_t(0) or f_t(1) per project convention)
to avoid division by zero; update cut_pool_t<i_t,f_t>::cut_orthogonality to
perform this check before computing 1.0 - std::abs(dot) / denom.
| i_t append_status = new_A_row.append_rows(cuts); | ||
| if (append_status != 0) { | ||
| settings.log.printf("append_rows error: %d\n", append_status); | ||
| exit(1); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Propagate error instead of terminating with exit(1).
Line 1368 unconditionally terminates the process when append_rows fails. This prevents proper error handling in library code.
🔎 Apply this diff to propagate error
i_t append_status = new_A_row.append_rows(cuts);
if (append_status != 0) {
settings.log.printf("append_rows error: %d\n", append_status);
- exit(1);
+ return append_status;
}Based on coding guidelines: Verify error propagation from internal functions to user-facing APIs.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| i_t append_status = new_A_row.append_rows(cuts); | |
| if (append_status != 0) { | |
| settings.log.printf("append_rows error: %d\n", append_status); | |
| exit(1); | |
| } | |
| i_t append_status = new_A_row.append_rows(cuts); | |
| if (append_status != 0) { | |
| settings.log.printf("append_rows error: %d\n", append_status); | |
| return append_status; | |
| } |
🤖 Prompt for AI Agents
In @cpp/src/dual_simplex/cuts.cpp around lines 1365 - 1369, The code currently
calls new_A_row.append_rows(cuts) and calls exit(1) on non-zero append_status;
instead remove the unconditional process termination and propagate the error:
replace the exit(1) with returning or throwing the appropriate error value from
the current function (e.g., return append_status or throw a descriptive
exception) after logging via settings.log.printf, ensuring the caller of the
function (and its signature) is updated to handle the propagated error from
new_A_row.append_rows; keep the existing log message but do not terminate the
process.
| template <typename i_t, typename f_t> | ||
| i_t pseudo_costs_t<i_t, f_t>::reliable_variable_selection(const lp_problem_t<i_t, f_t>& lp, | ||
| const simplex_solver_settings_t<i_t, f_t>& settings, | ||
| const std::vector<variable_type_t>& var_types, | ||
| const std::vector<variable_status_t>& vstatus, | ||
| const std::vector<f_t>& edge_norms, | ||
| const std::vector<i_t>& fractional, | ||
| const std::vector<f_t>& solution, | ||
| f_t current_obj, | ||
| logger_t& log) | ||
| { | ||
| mutex.lock(); | ||
|
|
||
| const i_t num_fractional = fractional.size(); | ||
| std::vector<f_t> pseudo_cost_up(num_fractional); | ||
| std::vector<f_t> pseudo_cost_down(num_fractional); | ||
| std::vector<f_t> score(num_fractional); | ||
|
|
||
| i_t num_initialized_down; | ||
| i_t num_initialized_up; | ||
| f_t pseudo_cost_down_avg; | ||
| f_t pseudo_cost_up_avg; | ||
|
|
||
| initialized(num_initialized_down, num_initialized_up, pseudo_cost_down_avg, pseudo_cost_up_avg); | ||
|
|
||
| mutex.unlock(); | ||
|
|
||
| log.printf("PC: num initialized down %d up %d avg down %e up %e\n", | ||
| num_initialized_down, | ||
| num_initialized_up, | ||
| pseudo_cost_down_avg, | ||
| pseudo_cost_up_avg); | ||
|
|
||
|
|
||
| const i_t reliable_threshold = 1; | ||
|
|
||
| for (i_t k = 0; k < num_fractional; k++) { | ||
| const i_t j = fractional[k]; | ||
| mutex.lock(); | ||
| bool down_reliable = pseudo_cost_num_down[j] >= reliable_threshold; | ||
| mutex.unlock(); | ||
| if (down_reliable) { | ||
| mutex.lock(); | ||
| pseudo_cost_down[k] = pseudo_cost_sum_down[j] / pseudo_cost_num_down[j]; | ||
| mutex.unlock(); | ||
| } else { | ||
| // Do trial branching on the down branch | ||
| f_t obj = trial_branching(lp, settings, var_types, vstatus, edge_norms, j, lp.lower[j], std::floor(solution[j])); | ||
| if (!std::isnan(obj)) { | ||
| f_t change_in_obj = obj - current_obj; | ||
| f_t change_in_x = solution[j] - std::floor(solution[j]); | ||
| mutex.lock(); | ||
| pseudo_cost_sum_down[j] += change_in_obj / change_in_x; | ||
| pseudo_cost_num_down[j]++; | ||
| mutex.unlock(); | ||
| pseudo_cost_down[k] = pseudo_cost_sum_down[j] / pseudo_cost_num_down[j]; | ||
| } | ||
| } | ||
|
|
||
| mutex.lock(); | ||
| bool up_reliable = pseudo_cost_num_up[j] >= reliable_threshold; | ||
| mutex.unlock(); | ||
| if (up_reliable) { | ||
| mutex.lock(); | ||
| pseudo_cost_up[k] = pseudo_cost_sum_up[j] / pseudo_cost_num_up[j]; | ||
| mutex.unlock(); | ||
| } else { | ||
| // Do trial branching on the up branch | ||
| f_t obj = trial_branching(lp, settings, var_types, vstatus, edge_norms, j, std::ceil(solution[j]), lp.upper[j]); | ||
| if (!std::isnan(obj)) { | ||
| f_t change_in_obj = obj - current_obj; | ||
| f_t change_in_x = std::ceil(solution[j]) - solution[j]; | ||
| mutex.lock(); | ||
| pseudo_cost_sum_up[j] += change_in_obj / change_in_x; | ||
| pseudo_cost_num_up[j]++; | ||
| pseudo_cost_up[k] = pseudo_cost_sum_up[j] / pseudo_cost_num_up[j]; | ||
| mutex.unlock(); | ||
| } | ||
| } | ||
| constexpr f_t eps = 1e-6; | ||
| const f_t f_down = solution[j] - std::floor(solution[j]); | ||
| const f_t f_up = std::ceil(solution[j]) - solution[j]; | ||
| score[k] = | ||
| std::max(f_down * pseudo_cost_down[k], eps) * std::max(f_up * pseudo_cost_up[k], eps); | ||
| } | ||
|
|
||
| i_t branch_var = fractional[0]; | ||
| f_t max_score = -1; | ||
| i_t select = -1; | ||
| for (i_t k = 0; k < num_fractional; k++) { | ||
| if (score[k] > max_score) { | ||
| max_score = score[k]; | ||
| branch_var = fractional[k]; | ||
| select = k; | ||
| } | ||
| } | ||
|
|
||
| log.printf( | ||
| "pc branching on %d. Value %e. Score %e\n", branch_var, solution[branch_var], score[select]); | ||
|
|
||
|
|
||
| return branch_var; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix mutex synchronization and prevent out-of-bounds access.
Two critical issues in this method:
-
Mutex state: The mutex is unlocked at line 378 and never re-locked before the logging at line 450, which accesses shared state. The per-variable lock/unlock operations (lines 391-427) leave the method in an unlocked state.
-
Array bounds: If
fractionalis empty or all scores remain at their initial value,selectstays at-1, causingscore[select]at line 451 to access an invalid index.
🔎 Apply this diff to fix both issues
i_t branch_var = fractional[0];
f_t max_score = -1;
i_t select = -1;
for (i_t k = 0; k < num_fractional; k++) {
if (score[k] > max_score) {
max_score = score[k];
branch_var = fractional[k];
select = k;
}
}
- log.printf(
- "pc branching on %d. Value %e. Score %e\n", branch_var, solution[branch_var], score[select]);
-
+ if (select >= 0) {
+ log.printf(
+ "pc branching on %d. Value %e. Score %e\n", branch_var, solution[branch_var], score[select]);
+ }
return branch_var;
}Based on coding guidelines: Prevent thread-unsafe use of synchronization primitives and validate array bounds.
🧰 Tools
🪛 Cppcheck (2.19.0)
[warning] 451-451: Array index -1 is out of bounds.
(negativeContainerIndex)
🤖 Prompt for AI Agents
In @cpp/src/dual_simplex/pseudo_costs.cpp around lines 353 - 455, In
reliable_variable_selection, avoid unlocking the shared mutex before logging and
ensure select cannot be -1: keep the mutex locked (or re-lock) around accesses
that read shared pseudo_cost_* state used in the final log.printf (refer to
mutex and log.printf), or move the mutex.unlock() to after logging so shared
state is protected; and guard against empty fractional or no-score-selected by
checking if num_fractional==0 or select==-1 and returning a safe sentinel (e.g.,
-1) or choosing a fallback branch_var before using score[select] (references:
fractional, score, select, branch_var).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 10
Note
Due to the large number of review comments, Critical severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (6)
cpp/src/dual_simplex/phase2.cpp (2)
621-651: Primal infeasibility bookkeeping is inconsistent (L1 vs sum-of-squares) and can break non-steepest-edge pricing/termination
compute_initial_primal_infeasibilitiesnow outputs both (a)primal_inf_squared(return) and (b)primal_infas a linear sum (Line 635-648). But the incremental update path still treats its accumulator as a sum of squares (Line 669-686), and call sites mixprimal_infeasibility_squaredandprimal_infeasibilitywhen calling the same update routine (Line 2990-3021). This will corruptprimal_infeasibilityafter the first iteration in the non-steepest-edge path (and also makes the “Sum Inf.” column ambiguous). Based on learnings, phase transitions and solver state must be correct.Proposed direction (make metrics explicit and update both correctly)
- f_t primal_infeasibility; - f_t primal_infeasibility_squared = + f_t primal_infeas_l1 = 0.0; + f_t primal_infeas_l2_sq = phase2::compute_initial_primal_infeasibilities(..., - primal_infeasibility); + primal_infeas_l1); ... - phase2::update_primal_infeasibilities(..., primal_infeasibility_squared); + phase2::update_primal_infeasibilities(..., primal_infeas_l2_sq /* and also primal_infeas_l1 */); ... - phase2::update_single_primal_infeasibility(..., entering_index, primal_infeasibility); + phase2::update_single_primal_infeasibility(..., entering_index, primal_infeas_l2_sq /* and also primal_infeas_l1 */);And in
update_single_primal_infeasibility, update L1 usingold_infeas = std::sqrt(old_val)rather than(new_val-old_val).Also applies to: 654-691, 694-729, 2470-2479, 2990-3021, 3143-3149
3121-3126: Minor: fix typo + macro-only compile hazard
- “using exisiting steepest edge …” typo (Line 2439).
#ifdef STEEPEST_EDGE_DEBUGblock has an extra)inif (iter < 100 || iter % 100 == 0))(Line 3122) which will break debug builds.Also applies to: 2439-2441
cpp/src/dual_simplex/branch_and_bound.cpp (3)
1403-1517: Critical:solve_root_relaxation()corruptsbasic_list/nonbasic_listbypush_backon pre-sized vectors.Caller constructs
std::vector<i_t> basic_list(original_lp_.num_rows);, but this function does:for (...) if (BASIC) basic_list.push_back(j);That guarantees
basic_list.size() != mand will poisonbasis_update.refactor_basis(...)(and everything downstream).Proposed fix (reuse existing helper and clear lists)
- // Get the basic list and nonbasic list from the vstatus - for (i_t j = 0; j < original_lp_.num_cols; j++) { - if (crossover_vstatus_[j] == variable_status_t::BASIC) { - basic_list.push_back(j); - } else { - nonbasic_list.push_back(j); - } - } + basic_list.assign(original_lp_.num_rows, -1); + nonbasic_list.clear(); + std::vector<i_t> superbasic_list; + get_basis_from_vstatus( + original_lp_.num_rows, crossover_vstatus_, basic_list, nonbasic_list, superbasic_list); + if (!superbasic_list.empty()) { + settings_.log.printf("Unexpected superbasic variables after crossover\n"); + return lp_status_t::NUMERICAL_ISSUES; + }
1529-1980: Cut-pass loop contains production-blocking debug IO/exit(1)and mutates global LP under a mutex with forced process aborts.This block currently:
- reads/writes
solution.datunconditionally,- uses multiple
exit(1)on validation failures,- emits extensive
printfin multi-threaded code paths,- sets
lp_settings.concurrent_halt = NULL(prefernullptr),- hard-disables scaling (
scale_columns = false) which may harm numerics unless intentionally scoped.Please gate diagnostics behind compile-time flags, return solver statuses instead of aborting, and keep all logging through
settings_.log. Based on learnings, correctness + phase-transition state must be robust without debug artifacts.
381-451: Lock-order deadlock confirmed:find_reduced_cost_fixings()is called while holdingmutex_upper_inadd_feasible_solution(), but attempts to acquiremutex_original_lp_.This creates a deadlock scenario with
set_new_solution(), which acquires locks in the opposite order:
add_feasible_solution()(lines 678–707): Holdsmutex_upper_, then callsfind_reduced_cost_fixings()(line 697) which tries to acquiremutex_original_lp_.set_new_solution()(lines 383–422): Acquiresmutex_original_lp_(line 383), then later acquiresmutex_upper_(line 395).Fix: Refactor
find_reduced_cost_fixings()to accept the needed data without holdingmutex_upper_, or move the call outside the critical section. Usestd::scoped_lockto enforce a consistent global lock order across all methods.cpp/src/dual_simplex/basis_solves.cpp (1)
614-672: Handle fixed variables inbasis_repair()(currently misclassifieslower==upperas LOWER/UPPER).If
bad_jis fixed (|upper-lower| <= settings.fixed_tol), it should becomeNONBASIC_FIXED(and downstream code sometimes explicitly skips fixed variables in dual feasibility checks). Also consider asserting bounds vector sizes.Proposed fix
- if (lower[bad_j] == -inf && upper[bad_j] == inf) { + const f_t fixed_tol = settings.fixed_tol; + if (std::abs(upper[bad_j] - lower[bad_j]) <= fixed_tol) { + vstatus[bad_j] = variable_status_t::NONBASIC_FIXED; + } else if (lower[bad_j] == -inf && upper[bad_j] == inf) { vstatus[bad_j] = variable_status_t::NONBASIC_FREE; } else if (lower[bad_j] > -inf) { vstatus[bad_j] = variable_status_t::NONBASIC_LOWER; } else if (upper[bad_j] < inf) { vstatus[bad_j] = variable_status_t::NONBASIC_UPPER; } else { assert(1 == 0); }Also applies to: 860-868
🤖 Fix all issues with AI agents
In @cpp/src/dual_simplex/branch_and_bound.cpp:
- Around line 231-263: The code currently emits unconditional printf debugging
lines (e.g., the "num_integer_variables %d" print after full_variable_types and
the "slacks size ..." and per-slack prints inside the new_slacks_ loop) and
calls exit(1) on validation failures; remove these side-effecting prints and
exits or guard them behind a compile-time debug flag (e.g.,
DEBUG_BRANCH_AND_BOUND). Replace printf calls with settings_.log.debug(...) (or
settings_.log.printf only under the debug macro) and instead of calling exit(1)
on invalid slack checks, propagate the failure: if this is an initializer
function return an error code/false, otherwise if inside a constructor throw a
std::runtime_error with a clear message. Update code around
convert_user_problem, full_variable_types, the num_integer_variables_ counting
and the new_slacks_ validation loop to use the debug macro + settings_.log.debug
and to propagate errors rather than calling exit.
In @cpp/src/dual_simplex/crossover.cpp:
- Around line 1381-1396: The code reuses the loop counter iter across Phase 1
and Phase 2 causing incorrect starting iteration, wrong slack_basis choice (iter
== 0 ? 1 : 0) and miscounted solution.iterations; introduce a separate counter
(e.g., iter_phase2) for the Phase 2 call and pass that to dual_phase2 (use
iter_phase2 == 0 for the slack_basis logic), update solution.iterations by
adding iter_phase2 not the original iter, and ensure any checks (time_limit,
concurrent_halt) use the correct phase-specific iteration variable; locate and
modify calls/uses of dual_phase2, iter, and solution.iterations in the
dual_infeas / dual::status_t block to implement this separation.
In @cpp/src/dual_simplex/cuts.cpp:
- Around line 39-56: The functions cut_distance and cut_orthogonality currently
divide by norms without checks; add a small epsilon guard using
std::numeric_limits<f_t>::epsilon() (or a tiny constant like 1e-12) to avoid
division-by-zero/NaNs: after computing dot and before computing
cut_norm/ortho_norm, if dot <= eps*eps treat the norm as zero (set
cut_norm/ortho_norm = 0) and set the resulting distance/orthogonality score to a
safe value (e.g., 0.0) instead of performing the division; update
cut_violation/cut_norm return behavior accordingly and apply the same guard
pattern in both cut_distance and cut_orthogonality (referencing variables
cut_norm, ortho_norm, cut_violation, distance, ortho_score and the functions
cut_distance and cut_orthogonality).
- Around line 570-643: The slack_map in generate_mir_cuts is default-initialized
to 0 causing rows without slacks to treat column 0 as a slack; initialize
slack_map with a sentinel (e.g., -1) instead, set slack_map[i] = slack for each
slack in new_slacks, and then, before using slack inside the row loop (in the
loop that constructs inequality), skip or continue the row when slack_map[i] ==
-1 (or otherwise validate the slack index is valid) so you don't incorrectly
zero variable 0's coefficient; update any logic that assumes a slack exists to
handle the missing-slack case safely.
In @cpp/src/dual_simplex/cuts.hpp:
- Around line 21-42: print_cut_types uses settings.log.printf with "%d" for
variables of template integer type i_t, which is UB if i_t != int; fix by
converting the printed counters (num_gomory_cuts, num_mir_cuts,
num_knapsack_cuts) to a concrete integer type when calling settings.log.printf
(e.g., static_cast<long long>(...)) and change the format specifier to match
(e.g., "%lld"), or alternatively cast to intmax_t and use "%jd"/PRIdMAX; update
the printf call in print_cut_types to use the chosen cast and matching specifier
to avoid undefined behavior.
In @cpp/src/dual_simplex/pseudo_costs.cpp:
- Around line 448-460: The loop assumes num_fractional>0 but the code
initializes branch_var = fractional[0] and later uses score[select] even when
select stays -1; add an early guard checking if num_fractional == 0 (or
fractional.empty()) before those assignments and handle it (e.g., return, skip
branching, or set a safe default and log) to avoid out-of-bounds access; adjust
code around variables branch_var, select, fractional, score, and the printf that
prints solution[branch_var] and score[select] so they are only used when a valid
selection was made (select >= 0).
🟠 Major comments (11)
cpp/src/dual_simplex/pseudo_costs.cpp-136-167 (1)
136-167: Remove debug code and add time limit enforcement.The function has the following issues:
- Line 160: Commented-out
printfdebug statement should be removed.- Missing time limit check: Unlike
strong_branch_helper(lines 54-57), this function doesn't check or enforce the solver'stime_limit. Trial branching could consume excessive time, especially when called multiple times fromreliable_variable_selection.🔧 Proposed fixes
Remove the debug statement and add time limit handling:
dual::status_t status = dual_phase2( 2, 0, lp_start_time, child_problem, child_settings, vstatus, solution, iter, child_edge_norms); - //printf("Trial branching on variable %d. Lo: %e Up: %e. Iter %d. Status %d. Obj %e\n", branch_var, child_problem.lower[branch_var], child_problem.upper[branch_var], iter, status, compute_objective(child_problem, solution.x));Consider adding time limit parameters and checks similar to
strong_branch_helper:template <typename i_t, typename f_t> f_t trial_branching(f_t start_time, const lp_problem_t<i_t, f_t>& original_lp, const simplex_solver_settings_t<i_t, f_t>& settings, // ... other parameters { f_t elapsed_time = toc(start_time); if (elapsed_time > settings.time_limit) { return std::numeric_limits<f_t>::quiet_NaN(); } // ... rest of function child_settings.time_limit = std::max(0.0, settings.time_limit - elapsed_time);Based on coding guidelines for preventing resource exhaustion.
cpp/src/dual_simplex/pseudo_costs.cpp-392-446 (1)
392-446: Add time limit enforcement to prevent unbounded trial branching.The loop performs trial branching for each unreliable variable without checking if the solver's
time_limithas been exceeded. Combined with the missing time limit intrial_branchingitself (see earlier comment), this could cause the reliable variable selection to run far longer than intended, especially with many fractional variables.
strong_branch_helper(lines 56, 115, 117, 132) checks the time limit at multiple points and breaks early. This function should do the same.⏱️ Proposed fix to add time limit checks
Add
f_t start_timeparameter to the function signature, then add periodic checks:for (i_t k = 0; k < num_fractional; k++) { + if (toc(start_time) > settings.time_limit) { + log.printf("Reliable branching terminated: time limit exceeded after %d trials\n", trial_branches); + break; + } const i_t j = fractional[k];Also pass
start_timetotrial_branchingas suggested in the earlier comment.Based on coding guidelines for resource exhaustion prevention.
Committable suggestion skipped: line range outside the PR's diff.
cpp/src/dual_simplex/cuts.hpp-21-25 (1)
21-25: Fix conflicting cut inequality direction documentation.The
add_cutfunction documents cuts ascut'*x >= rhs(violated when< rhs), butget_best_cutsreturns cuts in formbest_cuts*x <= best_rhs. These opposite conventions can cause sign errors when integrating cuts into the LP problem. Align to a single convention (recommend>=to matchminimum_violationimplementation expectations) and rename parameters likerhstolhs_sensefor clarity.cpp/src/dual_simplex/cuts.cpp-254-361 (1)
254-361: Knapsack cut generation has hard assumptions that can go OOB / mis-separate
values/weights/solutionare sized asknapsack_inequality.i.size() - 1(Line 298-316), which assumes exactly one slack is present in the row representation. If the row has 0 or >1 slack-like cols (or if the slack is already removed upstream),hwill mismatch andsolution[h]accesses become unsafe (Line 341-346).cpp/src/dual_simplex/cuts.cpp-417-519 (1)
417-519: Knapsack DP mixes float weights/rhs with integer DP (floor(weights)); verify correctness
The DP transition usesfloor(weights[j-1])(Line 491-496) and later comparesdp(n,v) <= rhswhererhsisf_t(Line 503-504). Ifweights/rhsare intended to be integral, consider enforcing that invariant (or casting once) rather than flooring in the core recurrence.cpp/src/dual_simplex/phase2.cpp-2423-2441 (1)
2423-2441: Clamping nonpositive steepest-edge norms to 1e-4 can hide corruption; recompute instead
Settingdelta_y_steepest_edge[j] = 1e-4for basic vars (Line 2423-2438) avoids division-by-zero but can silently degrade pricing. If this is truly fixing “invalid norms”, consider recomputing the specific norm(s) viab_transpose_solvefor those basics (or forcing a full re-init) and logging once. Based on learnings, simplex pivot correctness/robustness matters.cpp/src/dual_simplex/cuts.cpp-158-178 (1)
158-178:get_best_cutsshould clearbest_rhs/best_cut_types(currently appends across calls)
You clearbest_cutsbut not the companion vectors, so repeated calls accumulate stale data (Line 160-175).Patch sketch
i_t cut_pool_t<i_t, f_t>::get_best_cuts(csr_matrix_t<i_t, f_t>& best_cuts, std::vector<f_t>& best_rhs, std::vector<cut_type_t>& best_cut_types) { best_cuts.m = 0; best_cuts.n = original_vars_; best_cuts.row_start.clear(); best_cuts.j.clear(); best_cuts.x.clear(); + best_rhs.clear(); + best_cut_types.clear(); ... }cpp/src/dual_simplex/phase2.cpp-1234-1236 (2)
1234-1236: Steepest-edge init early-exit: don’t collapse CONCURRENT_LIMIT into TIME_LIMIT
initialize_steepest_edge_normsreturns-1for both “time limit” and “concurrent halt” (Line 1234-1236), but the caller maps that todual::status_t::TIME_LIMITunconditionally (Line 2416-2421).Minimal fix sketch
- if (phase2::initialize_steepest_edge_norms(...) == -1) { - printf("Bad return from initialize steepest edge norms\n"); - return dual::status_t::TIME_LIMIT; - } + const i_t se_rc = phase2::initialize_steepest_edge_norms(...); + if (se_rc == -1) { + if (settings.concurrent_halt != nullptr && *settings.concurrent_halt == 1) { + return dual::status_t::CONCURRENT_LIMIT; + } + return dual::status_t::TIME_LIMIT; + }Also applies to: 2416-2421
1234-1236: Avoid unconditionalprintfin solver hot paths; usesettings.log+ verbosity gates
These prints will flood stdout and can materially slow phase2 on hard instances (Line 1234-1236, 2166-2175, 2418-2421). Prefersettings.log.debug(...)and/or#ifdef-guarded diagnostics. As per coding guidelines, correctness + performance in simplex loops is critical.Also applies to: 2089-2175, 2418-2421
cpp/src/dual_simplex/cuts.cpp-1520-1663 (1)
1520-1663:remove_cuts: add basis invariant check beforerefactor_basiscallThe basis invariant
basic_list.size() == lp.num_rowsis required byrefactor_basisand factorization routines. After rebuildingnew_basic_listfrom remaining columns (line 1621–1627), you do not validate this invariant before callingrefactor_basisat line 1662. Other code paths (e.g.,phase2.cpp,branch_and_bound.cpp) explicitly assert this invariant withassert(nonbasic_list.size() == n - m)or runtime checks before callingrefactor_basis. If a removed slack was the only basic variable for a row, the invariant is violated and the refactorization can fail silently or produce an invalid basis.Add a validation check after assigning
basic_list:basic_list = new_basic_list; nonbasic_list = new_nonbasic_list; vstatus = new_vstatus; x = new_solution_x; y = new_solution_y; z = new_solution_z; + if (static_cast<i_t>(basic_list.size()) != lp.num_rows) { + settings.log.printf("remove_cuts: invalid basis size %d (rows=%d)\n", + static_cast<int>(basic_list.size()), + static_cast<int>(lp.num_rows)); + return; + }cpp/src/dual_simplex/branch_and_bound.cpp-747-756 (1)
747-756: Removeprintfdebug dumps insolve_node()hot path (and avoid dumping full parent chain by default).These prints will interleave across threads, destroy performance, and can leak user model details. Gate behind debug flags and route via
log.debug.Also applies to: 869-884
🟡 Minor comments (1)
cpp/src/dual_simplex/pseudo_costs.cpp-459-460 (1)
459-460: Use logger instead of printf for consistency.Line 459 uses
printfdirectly, while the rest of the codebase useslog.printf. This bypasses the logging infrastructure and could interfere with log filtering, redirection, or multi-threaded output ordering.📝 Proposed fix
- printf( + log.printf( "pc reliability branching on %d. Value %e. Score %e. Iter %d. Trial branches %d\n", branch_var, solution[branch_var], score[select], iter, trial_branches);Committable suggestion skipped: line range outside the PR's diff.
🧹 Nitpick comments (7)
cpp/src/dual_simplex/simplex_solver_settings.hpp (1)
72-79: Add basic validation/docs formax_cut_passesandreliability_branching.Defaults are fine, but it’d be good to ensure downstream code clamps/guards invalid values (e.g.,
max_cut_passes < 0,reliability_branching < -1) and that the semantics ofreliability_branching > 0are consistently interpreted across B&B components. As per coding guidelines, keep phase transitions/state initialization unambiguous when enabling cut passes and branching modes.Also applies to: 144-147
cpp/include/cuopt/linear_programming/mip/solver_settings.hpp (1)
81-87: Public API: add Doxygen + confirm ABI expectations for added settings fields.Since this is in
cpp/include/..., please add brief Doxygen comments fornode_limit,reliability_branching, andmax_cut_passes(expected ranges,-1meaning, etc.). Also confirm whethermip_solver_settings_tis ever used across a binary boundary such that layout changes matter; if yes, consider an ABI-stable approach.cpp/src/dual_simplex/phase2.cpp (1)
1736-1802:primal_infeasibility_breakdownduplicates logic already present; risk of drift
This is very close tophase2::primal_infeasibility(same file) and also theprimal_infeasibility(...)helper shown incpp/src/dual_simplex/primal.cpp(relevant snippet). Consider consolidating behind a single implementation that optionally reports the breakdown.Also applies to: 1804-1847
cpp/src/dual_simplex/cuts.cpp (1)
16-36: TODOs left in core cut-pool lifecycle (dedup + drop policy)
add_cutexplicitly skips dedup (Line 18) anddrop_cuts()is unimplemented (Line 190-193). If these are intentionally deferred, please ensure call sites won’t let the pool grow unbounded and degrade performance over long MIP runs.Also applies to: 189-193
cpp/src/dual_simplex/basis_solves.hpp (1)
42-51: Add size invariants for newlower/upperparameters (avoid silent OOB).Now that
basis_repair()takeslower/upper, please ensure the implementation assertslower.size() == A.nandupper.size() == A.n(andvstatus.size() == A.n) so misuse fails fast. As per coding guidelines, correct initialization/state validation should be explicit.cpp/src/dual_simplex/basis_updates.cpp (1)
2252-2321: Bounds-awarerefactor_basis()forwarding is good; add size assertions forlower/upper.Since
refactor_basis()now forwards bounds intobasis_repair(), please assertlower.size() == A.nandupper.size() == A.n(and that lists/vstatus are sized consistently) to keep failures diagnosable. As per coding guidelines, validate state before solve/phase transitions.cpp/src/dual_simplex/branch_and_bound.cpp (1)
302-379:find_reduced_cost_fixings()needs max/min handling + should not mutate bounds without consistent solver-state integration.Concerns:
- Uses
printfand manual mutex lock/unlock; should use logger + RAII.- Should guard against
upper_bound == inf/ negative or NaN gaps, and maximization (obj_scale < 0) since reduced-cost logic flips sign conventions.- Currently computes/strengthens bounds but does not actually apply them back into the live search tree / node LPs, so it risks being “diagnostic-only” (or inconsistent if later wired).
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (16)
cpp/include/cuopt/linear_programming/constants.hcpp/include/cuopt/linear_programming/mip/solver_settings.hppcpp/src/dual_simplex/basis_solves.cppcpp/src/dual_simplex/basis_solves.hppcpp/src/dual_simplex/basis_updates.cppcpp/src/dual_simplex/basis_updates.hppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/crossover.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hppcpp/src/dual_simplex/phase2.cppcpp/src/dual_simplex/primal.cppcpp/src/dual_simplex/pseudo_costs.cppcpp/src/dual_simplex/simplex_solver_settings.hppcpp/src/math_optimization/solver_settings.cucpp/src/mip/solver.cu
🚧 Files skipped from review as they are similar to previous changes (3)
- cpp/src/math_optimization/solver_settings.cu
- cpp/src/dual_simplex/basis_updates.hpp
- cpp/src/mip/solver.cu
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{cu,cuh,cpp,hpp,h}
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
**/*.{cu,cuh,cpp,hpp,h}: Track GPU device memory allocations and deallocations to prevent memory leaks; ensure cudaMalloc/cudaFree balance and cleanup of streams/events
Validate algorithm correctness in optimization logic: simplex pivots, branch-and-bound decisions, routing heuristics, and constraint/objective handling must produce correct results
Check numerical stability: prevent overflow/underflow, precision loss, division by zero/near-zero, and use epsilon comparisons for floating-point equality checks
Validate correct initialization of variable bounds, constraint coefficients, and algorithm state before solving; ensure reset when transitioning between algorithm phases (presolve, simplex, diving, crossover)
Ensure variables and constraints are accessed from the correct problem context (original vs presolve vs folded vs postsolve); verify index mapping consistency across problem transformations
For concurrent CUDA operations (barriers, async operations), explicitly create and manage dedicated streams instead of reusing the default stream; document stream lifecycle
Eliminate unnecessary host-device synchronization (cudaDeviceSynchronize) in hot paths that blocks GPU pipeline; use streams and events for async execution
Assess algorithmic complexity for large-scale problems (millions of variables/constraints); ensure O(n log n) or better complexity, not O(n²) or worse
Verify correct problem size checks before expensive GPU/CPU operations; prevent resource exhaustion on oversized problems
Identify assertions with overly strict numerical tolerances that fail on legitimate degenerate/edge cases (near-zero pivots, singular matrices, empty problems)
Ensure race conditions are absent in multi-GPU code and multi-threaded server implementations; verify proper synchronization of shared state
Refactor code duplication in solver components (3+ occurrences) into shared utilities; for GPU kernels, use templated device functions to avoid duplication
Check that hard-coded GPU de...
Files:
cpp/include/cuopt/linear_programming/constants.hcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/primal.cppcpp/src/dual_simplex/pseudo_costs.cppcpp/src/dual_simplex/simplex_solver_settings.hppcpp/src/dual_simplex/basis_solves.hppcpp/src/dual_simplex/basis_updates.cppcpp/include/cuopt/linear_programming/mip/solver_settings.hppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/basis_solves.cppcpp/src/dual_simplex/crossover.cppcpp/src/dual_simplex/cuts.hppcpp/src/dual_simplex/phase2.cpp
**/*.{h,hpp,py}
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
Verify C API does not break ABI stability (no struct layout changes, field reordering); maintain backward compatibility in Python and server APIs with deprecation warnings
Files:
cpp/include/cuopt/linear_programming/constants.hcpp/src/dual_simplex/simplex_solver_settings.hppcpp/src/dual_simplex/basis_solves.hppcpp/include/cuopt/linear_programming/mip/solver_settings.hppcpp/src/dual_simplex/cuts.hpp
**/*.{cpp,hpp,h}
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
**/*.{cpp,hpp,h}: Check for unclosed file handles when reading MPS/QPS problem files; ensure RAII patterns or proper cleanup in exception paths
Validate input sanitization to prevent buffer overflows and resource exhaustion attacks; avoid unsafe deserialization of problem files
Prevent thread-unsafe use of global and static variables; use proper mutex/synchronization in server code accessing shared solver state
Files:
cpp/include/cuopt/linear_programming/constants.hcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/primal.cppcpp/src/dual_simplex/pseudo_costs.cppcpp/src/dual_simplex/simplex_solver_settings.hppcpp/src/dual_simplex/basis_solves.hppcpp/src/dual_simplex/basis_updates.cppcpp/include/cuopt/linear_programming/mip/solver_settings.hppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/basis_solves.cppcpp/src/dual_simplex/crossover.cppcpp/src/dual_simplex/cuts.hppcpp/src/dual_simplex/phase2.cpp
**/*.{cu,cpp,hpp,h}
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
Avoid inappropriate use of exceptions in performance-critical GPU operation paths; prefer error codes or CUDA error checking for latency-sensitive code
Files:
cpp/include/cuopt/linear_programming/constants.hcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/primal.cppcpp/src/dual_simplex/pseudo_costs.cppcpp/src/dual_simplex/simplex_solver_settings.hppcpp/src/dual_simplex/basis_solves.hppcpp/src/dual_simplex/basis_updates.cppcpp/include/cuopt/linear_programming/mip/solver_settings.hppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/basis_solves.cppcpp/src/dual_simplex/crossover.cppcpp/src/dual_simplex/cuts.hppcpp/src/dual_simplex/phase2.cpp
cpp/include/cuopt/**/*
⚙️ CodeRabbit configuration file
cpp/include/cuopt/**/*: For public header files (C++ API):
- Check if new public functions/classes have documentation comments (Doxygen format)
- Flag API changes that may need corresponding docs/ updates
- Verify parameter descriptions match actual types/behavior
- Suggest documenting thread-safety, GPU requirements, and numerical behavior
- For breaking changes, recommend updating docs and migration guides
Files:
cpp/include/cuopt/linear_programming/constants.hcpp/include/cuopt/linear_programming/mip/solver_settings.hpp
🧠 Learnings (22)
📓 Common learnings
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Validate algorithm correctness in optimization logic: simplex pivots, branch-and-bound decisions, routing heuristics, and constraint/objective handling must produce correct results
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Reduce tight coupling between solver components (presolve, simplex, basis, barrier); increase modularity and reusability of optimization algorithms
📚 Learning: 2025-10-22T14:25:22.899Z
Learnt from: aliceb-nv
Repo: NVIDIA/cuopt PR: 527
File: cpp/src/mip/diversity/lns/rins.cu:167-175
Timestamp: 2025-10-22T14:25:22.899Z
Learning: In MIP (Mixed Integer Programming) problems in the cuOPT codebase, `n_integer_vars == 0` is impossible by definition—MIP problems must have at least one integer variable. If there are no integer variables, it would be a pure Linear Programming (LP) problem, not a MIP problem.
Applied to files:
cpp/include/cuopt/linear_programming/constants.hcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Validate algorithm correctness in optimization logic: simplex pivots, branch-and-bound decisions, routing heuristics, and constraint/objective handling must produce correct results
Applied to files:
cpp/include/cuopt/linear_programming/constants.hcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/primal.cppcpp/src/dual_simplex/pseudo_costs.cppcpp/src/dual_simplex/simplex_solver_settings.hppcpp/src/dual_simplex/basis_updates.cppcpp/include/cuopt/linear_programming/mip/solver_settings.hppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/basis_solves.cppcpp/src/dual_simplex/crossover.cppcpp/src/dual_simplex/cuts.hppcpp/src/dual_simplex/phase2.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Ensure variables and constraints are accessed from the correct problem context (original vs presolve vs folded vs postsolve); verify index mapping consistency across problem transformations
Applied to files:
cpp/include/cuopt/linear_programming/constants.hcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/pseudo_costs.cppcpp/src/dual_simplex/basis_solves.cppcpp/src/dual_simplex/cuts.hppcpp/src/dual_simplex/phase2.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Check that hard-coded GPU device IDs and resource limits are made configurable; abstract multi-backend support for different CUDA versions
Applied to files:
cpp/include/cuopt/linear_programming/constants.h
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Validate correct initialization of variable bounds, constraint coefficients, and algorithm state before solving; ensure reset when transitioning between algorithm phases (presolve, simplex, diving, crossover)
Applied to files:
cpp/include/cuopt/linear_programming/constants.hcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/primal.cppcpp/src/dual_simplex/pseudo_costs.cppcpp/src/dual_simplex/simplex_solver_settings.hppcpp/src/dual_simplex/basis_solves.hppcpp/src/dual_simplex/basis_updates.cppcpp/include/cuopt/linear_programming/mip/solver_settings.hppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/basis_solves.cppcpp/src/dual_simplex/crossover.cppcpp/src/dual_simplex/cuts.hppcpp/src/dual_simplex/phase2.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Refactor code duplication in solver components (3+ occurrences) into shared utilities; for GPU kernels, use templated device functions to avoid duplication
Applied to files:
cpp/include/cuopt/linear_programming/constants.hcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/pseudo_costs.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Reduce tight coupling between solver components (presolve, simplex, basis, barrier); increase modularity and reusability of optimization algorithms
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/primal.cppcpp/src/dual_simplex/basis_solves.hppcpp/src/dual_simplex/basis_updates.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/basis_solves.cppcpp/src/dual_simplex/crossover.cppcpp/src/dual_simplex/cuts.hppcpp/src/dual_simplex/phase2.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*test*.{cpp,cu,py} : Add tests for algorithm phase transitions: verify correct initialization of bounds and state when transitioning from presolve to simplex to diving to crossover
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/pseudo_costs.cppcpp/src/dual_simplex/simplex_solver_settings.hppcpp/src/dual_simplex/basis_updates.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/basis_solves.cppcpp/src/dual_simplex/crossover.cppcpp/src/dual_simplex/cuts.hppcpp/src/dual_simplex/phase2.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Assess algorithmic complexity for large-scale problems (millions of variables/constraints); ensure O(n log n) or better complexity, not O(n²) or worse
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Identify assertions with overly strict numerical tolerances that fail on legitimate degenerate/edge cases (near-zero pivots, singular matrices, empty problems)
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/basis_updates.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/basis_solves.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-12-04T20:09:09.264Z
Learnt from: chris-maes
Repo: NVIDIA/cuopt PR: 602
File: cpp/src/linear_programming/solve.cu:732-742
Timestamp: 2025-12-04T20:09:09.264Z
Learning: In cpp/src/linear_programming/solve.cu, the barrier solver does not currently return INFEASIBLE or UNBOUNDED status. It only returns OPTIMAL, TIME_LIMIT, NUMERICAL_ISSUES, or CONCURRENT_LIMIT.
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/crossover.cppcpp/src/dual_simplex/cuts.hppcpp/src/dual_simplex/phase2.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*test*.{cpp,cu,py} : Test with free variables, singleton problems, and extreme problem dimensions near resource limits to validate edge case handling
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/pseudo_costs.cppcpp/src/dual_simplex/basis_solves.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cpp,hpp,h} : Check for unclosed file handles when reading MPS/QPS problem files; ensure RAII patterns or proper cleanup in exception paths
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/basis_updates.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Verify correct problem size checks before expensive GPU/CPU operations; prevent resource exhaustion on oversized problems
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*test*.{cpp,cu,py} : Write tests validating numerical correctness of optimization results (not just 'runs without error'); test degenerate cases (infeasible, unbounded, empty, singleton problems)
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cpp
📚 Learning: 2025-12-03T23:29:26.391Z
Learnt from: chris-maes
Repo: NVIDIA/cuopt PR: 500
File: cpp/src/dual_simplex/sparse_matrix.cpp:519-524
Timestamp: 2025-12-03T23:29:26.391Z
Learning: In cpp/src/dual_simplex/sparse_matrix.cpp, the check_matrix() function is debug/diagnostic code (wrapped in #ifdef CHECK_MATRIX) that intentionally prints errors without necessarily returning early. The return codes from this debug code are not actively checked; the purpose is to print all validation errors in one pass for better diagnostics.
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Check numerical stability: prevent overflow/underflow, precision loss, division by zero/near-zero, and use epsilon comparisons for floating-point equality checks
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cpp,hpp,h} : Avoid inappropriate use of exceptions in performance-critical GPU operation paths; prefer error codes or CUDA error checking for latency-sensitive code
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-12-04T04:11:12.640Z
Learnt from: chris-maes
Repo: NVIDIA/cuopt PR: 500
File: cpp/src/dual_simplex/scaling.cpp:68-76
Timestamp: 2025-12-04T04:11:12.640Z
Learning: In the cuOPT dual simplex solver, CSR/CSC matrices (including the quadratic objective matrix Q) are required to have valid dimensions and indices by construction. Runtime bounds checking in performance-critical paths like matrix scaling is avoided to prevent slowdowns. Validation is performed via debug-only check_matrix() calls wrapped in #ifdef CHECK_MATRIX.
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/basis_updates.cppcpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Verify error propagation from CUDA to user-facing APIs is complete; ensure CUDA errors are caught and mapped to meaningful user error codes
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/basis_updates.cppcpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cpp,hpp,h} : Prevent thread-unsafe use of global and static variables; use proper mutex/synchronization in server code accessing shared solver state
Applied to files:
cpp/src/dual_simplex/pseudo_costs.cpp
🧬 Code graph analysis (8)
cpp/src/dual_simplex/primal.cpp (2)
cpp/src/dual_simplex/basis_solves.cpp (3)
basis_repair(614-675)basis_repair(614-622)basis_repair(860-868)cpp/src/dual_simplex/basis_solves.hpp (1)
basis_repair(43-51)
cpp/src/dual_simplex/pseudo_costs.cpp (2)
cpp/src/dual_simplex/phase2.cpp (5)
settings(2211-2233)settings(2211-2211)dual_phase2(2256-2286)dual_phase2(2256-2264)dual_phase2(3187-3196)cpp/src/dual_simplex/pseudo_costs.hpp (3)
lp(50-58)fractional(46-48)fractional(60-61)
cpp/src/dual_simplex/basis_updates.cpp (2)
cpp/src/dual_simplex/basis_solves.cpp (3)
basis_repair(614-675)basis_repair(614-622)basis_repair(860-868)cpp/src/dual_simplex/basis_solves.hpp (1)
basis_repair(43-51)
cpp/src/dual_simplex/cuts.cpp (1)
cpp/src/dual_simplex/cuts.hpp (19)
cut_type(83-83)cut_storage_(94-94)row(97-97)row(98-98)i(99-99)x_relax(85-85)a(259-265)best_cuts(88-88)knapsack_generation_t(120-124)lp(126-134)lp(166-174)lp(177-185)lp(187-192)lp(194-199)lp(222-232)lp(255-257)lp(267-270)values(140-143)values(144-147)
cpp/src/dual_simplex/basis_solves.cpp (1)
cpp/src/dual_simplex/presolve.cpp (3)
lower(240-240)upper(85-85)upper(241-241)
cpp/src/dual_simplex/crossover.cpp (3)
cpp/src/dual_simplex/basis_solves.hpp (1)
basis_repair(43-51)cpp/src/dual_simplex/phase2.cpp (8)
settings(2211-2233)settings(2211-2211)basic_list(2268-2268)dual_infeasibility(1657-1734)dual_infeasibility(1657-1662)dual_phase2(2256-2286)dual_phase2(2256-2264)dual_phase2(3187-3196)cpp/src/dual_simplex/primal.cpp (3)
basic_list(267-267)dual_infeasibility(59-107)dual_infeasibility(59-61)
cpp/src/dual_simplex/cuts.hpp (7)
cpp/src/dual_simplex/phase2.cpp (2)
settings(2211-2233)settings(2211-2211)cpp/src/dual_simplex/sparse_matrix.hpp (1)
C(155-155)cpp/src/dual_simplex/sparse_vector.hpp (2)
x(44-44)y(54-54)cpp/src/dual_simplex/sparse_matrix.cpp (1)
matrix_vector_multiply(975-980)cpp/src/dual_simplex/basis_updates.cpp (2)
rhs(1124-1124)rhs(1172-1172)cpp/src/dual_simplex/branch_and_bound.hpp (4)
Arow(252-252)solution(102-102)solution(129-129)solution(215-215)cpp/tests/dual_simplex/unit_tests/solve.cpp (1)
solution(156-156)
cpp/src/dual_simplex/phase2.cpp (1)
cpp/src/dual_simplex/primal.cpp (2)
primal_infeasibility(202-240)primal_infeasibility(202-205)
🪛 Clang (14.0.6)
cpp/src/dual_simplex/cuts.cpp
[error] 8-8: 'dual_simplex/cuts.hpp' file not found
(clang-diagnostic-error)
cpp/src/dual_simplex/cuts.hpp
[error] 9-9: 'dual_simplex/basis_updates.hpp' file not found
(clang-diagnostic-error)
🪛 Cppcheck (2.19.0)
cpp/src/dual_simplex/branch_and_bound.cpp
[error] 617-617: Resource leak
(resourceLeak)
cpp/src/dual_simplex/pseudo_costs.cpp
[warning] 460-460: Array index -1 is out of bounds.
(negativeContainerIndex)
🔇 Additional comments (8)
cpp/src/dual_simplex/cuts.hpp (1)
1-15: Make the new header self-sufficient + verify include paths.This header uses
int8_tandexitbut doesn’t include<cstdint>/<cstdlib>(it may compile only via transitive includes). Also, static analysis reportsdual_simplex/basis_updates.hppnot found—please confirm include directories forcpp/src/dual_simplexheaders are correctly configured for all targets that includecuts.hpp.cpp/src/dual_simplex/primal.cpp (1)
299-310: Good:basis_repairnow receives bounds +vstatus.This aligns the repair step with bound-aware status assignment (NONBASIC_LOWER/UPPER/FREE). Please just double-check
lp.lower/lp.upperare the correct “current problem” bounds at this point in the solve (not original/pre-solved bounds), per coding guidelines on context/index mapping.cpp/include/cuopt/linear_programming/constants.h (1)
60-62: LGTM: new MIP parameter keys.Please verify the settings parser/mapping layer uses these exact strings (including spelling) and add/extend a small config round-trip test if one exists (set → internal fields).
cpp/src/dual_simplex/phase2.cpp (1)
2336-2339: Bound-awarerefactor_basiscall sites look consistent
Passinglp.lower/lp.upperintoft.refactor_basis(...)aligns with the stated PR objective to make refactors bound-aware (Line 2336-2339, 3072-3081).Also applies to: 3072-3081
cpp/src/dual_simplex/cuts.cpp (3)
545-566: Library hygiene: removeexit(1)and mostprintffrom non-debug code paths
This file contains multiple unconditionalexit(1)andprintfstatements in cut generation and LP mutation (e.g., Line 585-588, 938-941, 1212-1219, 1368-1369, 1505-1509, 1549-1552). In a library solver this is a hard kill of the host process and is generally unacceptable—return an error code and let the caller decide. As per coding guidelines, error propagation to user-facing APIs must be complete.Also applies to: 570-588, 915-942, 1176-1320, 1321-1518, 1520-1663
⛔ Skipped due to learnings
Learnt from: CR Repo: NVIDIA/cuopt PR: 0 File: .github/.coderabbit_review_guide.md:0-0 Timestamp: 2025-11-25T10:20:49.822Z Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Validate algorithm correctness in optimization logic: simplex pivots, branch-and-bound decisions, routing heuristics, and constraint/objective handling must produce correct resultsLearnt from: CR Repo: NVIDIA/cuopt PR: 0 File: .github/.coderabbit_review_guide.md:0-0 Timestamp: 2025-11-25T10:20:49.822Z Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Validate correct initialization of variable bounds, constraint coefficients, and algorithm state before solving; ensure reset when transitioning between algorithm phases (presolve, simplex, diving, crossover)
8-10: The header filecuts.hppexists atcpp/src/dual_simplex/cuts.hppand is properly configured in CMakeLists.txt. The include directory"${CMAKE_CURRENT_SOURCE_DIR}/src"is set as PRIVATE for the cuopt target (line 252-259), so#include <dual_simplex/cuts.hpp>correctly resolves. No build break should occur.Likely an incorrect or invalid review comment.
1321-1518: This review comment contains two critical concerns that are not supported by the code.
Solution state updates are handled correctly. Immediately after
add_cutsreturns (lines 1885–1887 inbranch_and_bound.cpp), the code explicitly resizessolution.x,solution.y, andsolution.zto match the newlp.num_colsandlp.num_rows. Line 1890 clearsedge_norms_, which is then passed to the next solve with the updated LP dimensions. The solution vectors remain consistent with the LP state.Dimension mismatch is not a risk. The
cutsmatrix hasn = original_vars_(set once at initialization and never modified), which represents only the original problem variables, not slack columns. At eachadd_cutscall,lp.num_colshas been reset tooriginal_vars_by the precedingremove_cutscall. Theappend_rowsvalidation (sparse_matrix.cpp line 373–375) checkscuts.n <= lp.num_cols, which always holds because both equaloriginal_vars_at entry. The add-solve-remove cycle correctly maintains dimensions: cuts are appended to the original problem size, slacks are added during solving, and both are removed before the next iteration.Likely an incorrect or invalid review comment.
cpp/src/dual_simplex/crossover.cpp (1)
788-797:basis_repair()call updates look consistent with new bounds-aware signature.One follow-up: after repair, ensure
vstatusclassifications for fixed vars (lower==upper) are preserved (seebasis_repairimplementation) so crossover invariants (verify_basis) remain valid across phase transitions. Based on learnings, phase-transition initialization must be correct.Also applies to: 1142-1150, 1341-1349
| // Start of middle product form: basis_update_mpf_t | ||
|
|
||
| template <typename i_t, typename f_t> | ||
| i_t basis_update_mpf_t<i_t, f_t>::append_cuts(const csr_matrix_t<i_t, f_t>& cuts_basic) | ||
| { | ||
| const i_t m = L0_.m; | ||
|
|
||
| // Solve for U^T W^T = C_B^T | ||
| // We do this one row at a time of C_B | ||
| csc_matrix_t<i_t, f_t> WT(m, cuts_basic.m, 0); | ||
|
|
||
| i_t WT_nz = 0; | ||
| for (i_t k = 0; k < cuts_basic.m; k++) { | ||
| sparse_vector_t<i_t, f_t> rhs(cuts_basic, k); | ||
| u_transpose_solve(rhs); | ||
| WT.col_start[k] = WT_nz; | ||
| for (i_t q = 0; q < rhs.i.size(); q++) { | ||
| WT.i.push_back(rhs.i[q]); | ||
| WT.x.push_back(rhs.x[q]); | ||
| WT_nz++; | ||
| } | ||
| } | ||
| WT.col_start[cuts_basic.m] = WT_nz; | ||
|
|
||
|
|
||
| #ifdef CHECK_W | ||
| { | ||
| for (i_t k = 0; k < cuts_basic.m; k++) { | ||
| std::vector<f_t> WT_col(m, 0.0); | ||
| WT.load_a_column(k, WT_col); | ||
| std::vector<f_t> CBT_col(m, 0.0); | ||
| matrix_transpose_vector_multiply(U0_, 1.0, WT_col, 0.0, CBT_col); | ||
| sparse_vector_t<i_t, f_t> CBT_col_sparse(cuts_basic, k); | ||
| std::vector<f_t> CBT_col_dense(m); | ||
| CBT_col_sparse.to_dense(CBT_col_dense); | ||
| for (i_t h = 0; h < m; h++) { | ||
| if (std::abs(CBT_col_dense[h] - CBT_col[h]) > 1e-6) { | ||
| printf("col %d CBT_col_dense[%d] = %e CBT_col[%d] = %e\n", k, h, CBT_col_dense[h], h, CBT_col[h]); | ||
| exit(1); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| #endif | ||
|
|
||
| csc_matrix_t<i_t, f_t> V(cuts_basic.m, m, 0); | ||
| if (num_updates_ > 0) { | ||
| // W = V T_0 ... T_{num_updates_ - 1} | ||
| // or V = W T_{num_updates_ - 1}^{-1} ... T_0^{-1} | ||
| // or V^T = T_0^{-T} ... T_{num_updates_ - 1}^{-T} W^T | ||
| // We can compute V^T column by column so that we have | ||
| // V^T(:, h) = T_0^{-T} ... T_{num_updates_ - 1}^{-T} W^T(:, h) | ||
| // or | ||
| // V(h, :) = T_0^{-T} ... T_{num_updates_ - 1}^{-T} W^T(:, h) | ||
| // So we can form V row by row in CSR and then covert it to CSC | ||
| // for appending to L0 | ||
|
|
||
| csr_matrix_t<i_t, f_t> V_row(cuts_basic.m, m, 0); | ||
| i_t V_nz = 0; | ||
| const f_t zero_tol = 1e-13; | ||
| for (i_t h = 0; h < cuts_basic.m; h++) { | ||
| sparse_vector_t<i_t, f_t> rhs(WT, h); | ||
| scatter_into_workspace(rhs); | ||
| i_t nz = rhs.i.size(); | ||
| for (i_t k = num_updates_ - 1; k >= 0; --k) { | ||
| // T_k^{-T} = ( I - v u^T/(1 + u^T v)) | ||
| // T_k^{-T} * b = b - v * (u^T * b) / (1 + u^T * v) = b - theta * v, theta = u^T b / mu | ||
|
|
||
| const i_t u_col = 2 * k; | ||
| const i_t v_col = 2 * k + 1; | ||
| const f_t mu = mu_values_[k]; | ||
|
|
||
| // dot = u^T * b | ||
| f_t dot = dot_product(u_col, xi_workspace_, x_workspace_); | ||
| const f_t theta = dot / mu; | ||
| if (std::abs(theta) > zero_tol) { | ||
| add_sparse_column(S_, v_col, -theta, xi_workspace_, nz, x_workspace_); | ||
| } | ||
| } | ||
| gather_into_sparse_vector(nz, rhs); | ||
| V_row.row_start[h] = V_nz; | ||
| for (i_t q = 0; q < rhs.i.size(); q++) { | ||
| V_row.j.push_back(rhs.i[q]); | ||
| V_row.x.push_back(rhs.x[q]); | ||
| V_nz++; | ||
| } | ||
| } | ||
| V_row.row_start[cuts_basic.m] = V_nz; | ||
|
|
||
| V_row.to_compressed_col(V); | ||
|
|
||
|
|
||
| #ifdef CHECK_V | ||
| csc_matrix_t<i_t, f_t> CB_col(cuts_basic.m, m, 0); | ||
| cuts_basic.to_compressed_col(CB_col); | ||
| for (i_t k = 0; k < m; k++) { | ||
| std::vector<f_t> U_col(m, 0.0); | ||
| U0_.load_a_column(k, U_col); | ||
| for (i_t h = num_updates_ - 1; h >= 0; --h) { | ||
| // T_h = ( I + u_h v_h^T) | ||
| // T_h * x = x + u_h * v_h^T * x = x + theta * u_h | ||
| const i_t u_col = 2 * h; | ||
| const i_t v_col = 2 * h + 1; | ||
| f_t theta = dot_product(v_col, U_col); | ||
| const i_t col_start = S_.col_start[u_col]; | ||
| const i_t col_end = S_.col_start[u_col + 1]; | ||
| for (i_t p = col_start; p < col_end; ++p) { | ||
| const i_t i = S_.i[p]; | ||
| U_col[i] += theta * S_.x[p]; | ||
| } | ||
| } | ||
| std::vector<f_t> CB_column(cuts_basic.m, 0.0); | ||
| matrix_vector_multiply(V, 1.0, U_col, 0.0, CB_column); | ||
| std::vector<f_t> CB_col_dense(cuts_basic.m); | ||
| CB_col.load_a_column(k, CB_col_dense); | ||
| for (i_t l = 0; l < cuts_basic.m; l++) { | ||
| if (std::abs(CB_col_dense[l] - CB_column[l]) > 1e-6) { | ||
| printf("col %d CB_col_dense[%d] = %e CB_column[%d] = %e\n", k, l, CB_col_dense[l], l, CB_column[l]); | ||
| exit(1); | ||
| } | ||
| } | ||
| } | ||
| #endif | ||
| } else { | ||
| // W = V | ||
| WT.transpose(V); | ||
| } | ||
|
|
||
| // Extend u_i, v_i for i = 0, ..., num_updates_ - 1 | ||
| S_.m += cuts_basic.m; | ||
|
|
||
| // Adjust L and U | ||
| // L = [ L0 0 ] | ||
| // [ V I ] | ||
|
|
||
| i_t V_nz = V.col_start[m]; | ||
| i_t L_nz = L0_.col_start[m]; | ||
| csc_matrix_t<i_t, f_t> new_L(m + cuts_basic.m, m + cuts_basic.m, L_nz + V_nz + cuts_basic.m); | ||
| i_t predicted_nz = L_nz + V_nz + cuts_basic.m; | ||
| L_nz = 0; | ||
| for (i_t j = 0; j < m; ++j) { | ||
| new_L.col_start[j] = L_nz; | ||
| const i_t col_start = L0_.col_start[j]; | ||
| const i_t col_end = L0_.col_start[j + 1]; | ||
| for (i_t p = col_start; p < col_end; ++p) { | ||
| new_L.i[L_nz] = L0_.i[p]; | ||
| new_L.x[L_nz] = L0_.x[p]; | ||
| L_nz++; | ||
| } | ||
| const i_t V_col_start = V.col_start[j]; | ||
| const i_t V_col_end = V.col_start[j + 1]; | ||
| for (i_t p = V_col_start; p < V_col_end; ++p) { | ||
| new_L.i[L_nz] = V.i[p] + m; | ||
| new_L.x[L_nz] = V.x[p]; | ||
| L_nz++; | ||
| } | ||
| } | ||
| for (i_t j = m; j < m + cuts_basic.m; ++j) { | ||
| new_L.col_start[j] = L_nz; | ||
| new_L.i[L_nz] = j; | ||
| new_L.x[L_nz] = 1.0; | ||
| L_nz++; | ||
| } | ||
| new_L.col_start[m + cuts_basic.m] = L_nz; | ||
| if (L_nz != predicted_nz) { | ||
| printf("L_nz %d predicted_nz %d\n", L_nz, predicted_nz); | ||
| exit(1); | ||
| } | ||
|
|
||
| L0_ = new_L; | ||
|
|
||
| // Adjust U | ||
| // U = [ U0 0 ] | ||
| // [ 0 I ] | ||
|
|
||
| i_t U_nz = U0_.col_start[m]; | ||
| U0_.col_start.resize(m + cuts_basic.m + 1); | ||
| U0_.i.resize(U_nz + cuts_basic.m); | ||
| U0_.x.resize(U_nz + cuts_basic.m); | ||
| for (i_t k = m; k < m + cuts_basic.m; ++k) { | ||
| U0_.col_start[k] = U_nz; | ||
| U0_.i[U_nz] = k; | ||
| U0_.x[U_nz] = 1.0; | ||
| U_nz++; | ||
| } | ||
| U0_.col_start[m + cuts_basic.m] = U_nz; | ||
| U0_.n = m + cuts_basic.m; | ||
| U0_.m = m + cuts_basic.m; | ||
|
|
||
| compute_transposes(); | ||
|
|
||
| // Adjust row_permutation_ and inverse_row_permutation_ | ||
| row_permutation_.resize(m + cuts_basic.m); | ||
| inverse_row_permutation_.resize(m + cuts_basic.m); | ||
| for (i_t k = m; k < m + cuts_basic.m; ++k) { | ||
| row_permutation_[k] = k; | ||
| } | ||
| inverse_permutation(row_permutation_, inverse_row_permutation_); | ||
|
|
||
| // Adjust workspace sizes | ||
| xi_workspace_.resize(2 * (m + cuts_basic.m), 0); | ||
| x_workspace_.resize(m + cuts_basic.m, 0.0); | ||
|
|
||
| return 0; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace exit(1)/printf in append_cuts() with proper error propagation (and validate dimensions).
This is solver-core code and can run in multi-threaded contexts; exit(1) will hard-kill the host process and is not acceptable for a library. Please:
- replace
exit(1)with returning a non-zero status (and let callers map it tolp_status_t::NUMERICAL_ISSUESor similar), - route diagnostics via
settings.log(or gateprintfbehind a dedicated debug macro), - add fast precondition checks (debug asserts are fine) like
cuts_basic.n == L0_.mand consistent index bounds.
Sketch (convert hard exits to status returns)
- printf("col %d CBT_col_dense[%d] = %e CBT_col[%d] = %e\n", k, h, CBT_col_dense[h], h, CBT_col[h]);
- exit(1);
+ settings.log.printf(
+ "append_cuts CHECK_W failed: col %d row %d expected %e got %e\n",
+ k, h, CBT_col_dense[h], CBT_col[h]);
+ return 1;
...
- printf("L_nz %d predicted_nz %d\n", L_nz, predicted_nz);
- exit(1);
+ settings.log.printf("append_cuts: L nnz mismatch %d predicted %d\n", L_nz, predicted_nz);
+ return 1;Committable suggestion skipped: line range outside the PR's diff.
| #ifdef PRINT_A | ||
| settings_.log.printf("A"); | ||
| original_problem_.A.print_matrix(); | ||
| #endif | ||
| convert_user_problem(original_problem_, settings_, original_lp_, new_slacks_, dualize_info); | ||
| full_variable_types(original_problem_, original_lp_, var_types_); | ||
|
|
||
| num_integer_variables_ = 0; | ||
| for (i_t j = 0; j < original_lp_.num_cols; j++) { | ||
| if (var_types_[j] == variable_type_t::INTEGER) { | ||
| num_integer_variables_++; | ||
| } | ||
| } | ||
| printf("num_integer_variables %d\n", num_integer_variables_); | ||
|
|
||
| // Check slack | ||
| printf("slacks size %ld m %d\n", new_slacks_.size(), original_lp_.num_rows); | ||
| for (i_t slack : new_slacks_) { | ||
| const i_t col_start = original_lp_.A.col_start[slack]; | ||
| const i_t col_end = original_lp_.A.col_start[slack + 1]; | ||
| const i_t col_len = col_end - col_start; | ||
| if (col_len != 1) { | ||
| printf("Slack %d has %d nzs\n", slack, col_len); | ||
| exit(1); | ||
| } | ||
| const i_t i = original_lp_.A.i[col_start]; | ||
| const f_t x = original_lp_.A.x[col_start]; | ||
| if (std::abs(x) != 1.0) { | ||
| printf("Slack %d row %d has non-unit coefficient %e\n", slack, i, x); | ||
| exit(1); | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove unconditional debug prints and exit(1) from constructor (or gate behind a debug macro).
printf(...) + exit(1) in library init will kill production processes on benign inputs and is not thread-safe. Please gate this under a dedicated compile-time debug flag and use settings_.log.debug(...) + error returns instead.
🤖 Prompt for AI Agents
In @cpp/src/dual_simplex/branch_and_bound.cpp around lines 231 - 263, The code
currently emits unconditional printf debugging lines (e.g., the
"num_integer_variables %d" print after full_variable_types and the "slacks size
..." and per-slack prints inside the new_slacks_ loop) and calls exit(1) on
validation failures; remove these side-effecting prints and exits or guard them
behind a compile-time debug flag (e.g., DEBUG_BRANCH_AND_BOUND). Replace printf
calls with settings_.log.debug(...) (or settings_.log.printf only under the
debug macro) and instead of calling exit(1) on invalid slack checks, propagate
the failure: if this is an initializer function return an error code/false,
otherwise if inside a constructor throw a std::runtime_error with a clear
message. Update code around convert_user_problem, full_variable_types, the
num_integer_variables_ counting and the new_slacks_ validation loop to use the
debug macro + settings_.log.debug and to propagate errors rather than calling
exit.
| #if 1 | ||
| FILE* fid = NULL; | ||
| fid = fopen("solution.dat", "w"); | ||
| if (fid != NULL) { | ||
| printf("Writing solution.dat\n"); | ||
|
|
||
| std::vector<f_t> residual = original_lp_.rhs; | ||
| matrix_vector_multiply(original_lp_.A, 1.0, incumbent_.x, -1.0, residual); | ||
| printf("|| A*x - b ||_inf %e\n", vector_norm_inf<i_t, f_t>(residual)); | ||
| auto hash_combine_f = [](size_t seed, f_t x) { | ||
| seed ^= std::hash<f_t>{}(x) + 0x9e3779b9 + (seed << 6) + (seed >> 2); | ||
| return seed; | ||
| }; | ||
| printf("incumbent size %ld original lp cols %d\n", incumbent_.x.size(), original_lp_.num_cols); | ||
| i_t n = original_lp_.num_cols; | ||
| size_t seed = n; | ||
| fprintf(fid, "%d\n", n); | ||
| for (i_t j = 0; j < n; ++j) { | ||
| fprintf(fid, "%.17g\n", incumbent_.x[j]); | ||
| seed = hash_combine_f(seed, incumbent_.x[j]); | ||
| } | ||
| printf("Solution hash: %20x\n", seed); | ||
| fclose(fid); | ||
| } | ||
| #endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Debug file write (solution.dat) must not be enabled by default; also fix FILE RAII and format specifiers.
#if 1forces file IO during normal solves.- Use RAII to guarantee close (and avoid static analysis “resourceLeak” reports).
printf("Solution hash: %20x\n", seed);is UB forsize_ton many platforms (use%zxor cast).
🧰 Tools
🪛 Cppcheck (2.19.0)
[error] 617-617: Resource leak
(resourceLeak)
| dual_infeas = dual_infeasibility(lp, settings, vstatus, solution.z); | ||
| dual::status_t status = dual::status_t::NUMERICAL; | ||
| if (dual_infeas <= settings.dual_tol) { | ||
| std::vector<f_t> edge_norms; | ||
| status = dual_phase2( | ||
| 2, iter == 0 ? 1 : 0, start_time, lp, settings, vstatus, solution, iter, edge_norms); | ||
| if (toc(start_time) > settings.time_limit) { | ||
| settings.log.printf("Time limit exceeded\n"); | ||
| return crossover_status_t::TIME_LIMIT; | ||
| } | ||
| if (settings.concurrent_halt != nullptr && *settings.concurrent_halt == 1) { | ||
| settings.log.printf("Concurrent halt\n"); | ||
| return crossover_status_t::CONCURRENT_LIMIT; | ||
| } | ||
| solution.iterations += iter; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix iter reuse across phase1→phase2 (likely wrong iteration accounting / wrong slack_basis).
iter is used for the Phase 1 dual_phase2(...) call, then reused as the in/out iteration counter for the Phase 2 call without being reset. That likely:
- passes a non-zero starting
iterinto Phase 2 (if Phase 1 iterated), and - makes
slack_basisselection viaiter == 0 ? 1 : 0unreliable, - risks double-counting or miscounting
solution.iterations.
Proposed fix (reset a separate Phase 2 counter)
- i_t iter = 0;
+ i_t iter_phase1 = 0;
...
- dual::status_t phase1_status = dual_phase2(
- 1, 1, start_time, phase1_problem, settings, phase1_vstatus, phase1_solution, iter, junk);
+ dual::status_t phase1_status = dual_phase2(
+ 1, 1, start_time, phase1_problem, settings, phase1_vstatus, phase1_solution, iter_phase1, junk);
...
- dual_infeas = dual_infeasibility(lp, settings, vstatus, solution.z);
- dual::status_t status = dual::status_t::NUMERICAL;
+ dual_infeas = dual_infeasibility(lp, settings, vstatus, solution.z);
+ dual::status_t status = dual::status_t::NUMERICAL;
if (dual_infeas <= settings.dual_tol) {
+ i_t iter_phase2 = 0;
std::vector<f_t> edge_norms;
status = dual_phase2(
- 2, iter == 0 ? 1 : 0, start_time, lp, settings, vstatus, solution, iter, edge_norms);
+ 2, /*slack_basis=*/0, start_time, lp, settings, vstatus, solution, iter_phase2, edge_norms);
...
- solution.iterations += iter;
+ solution.iterations += iter_phase2;
}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In @cpp/src/dual_simplex/crossover.cpp around lines 1381 - 1396, The code reuses
the loop counter iter across Phase 1 and Phase 2 causing incorrect starting
iteration, wrong slack_basis choice (iter == 0 ? 1 : 0) and miscounted
solution.iterations; introduce a separate counter (e.g., iter_phase2) for the
Phase 2 call and pass that to dual_phase2 (use iter_phase2 == 0 for the
slack_basis logic), update solution.iterations by adding iter_phase2 not the
original iter, and ensure any checks (time_limit, concurrent_halt) use the
correct phase-specific iteration variable; locate and modify calls/uses of
dual_phase2, iter, and solution.iterations in the dual_infeas / dual::status_t
block to implement this separation.
| template <typename i_t, typename f_t> | ||
| f_t cut_pool_t<i_t, f_t>::cut_distance(i_t row, const std::vector<f_t>& x, f_t& cut_violation, f_t &cut_norm) | ||
| { | ||
| const i_t row_start = cut_storage_.row_start[row]; | ||
| const i_t row_end = cut_storage_.row_start[row + 1]; | ||
| f_t cut_x = 0.0; | ||
| f_t dot = 0.0; | ||
| for (i_t p = row_start; p < row_end; p++) { | ||
| const i_t j = cut_storage_.j[p]; | ||
| const f_t cut_coeff = cut_storage_.x[p]; | ||
| cut_x += cut_coeff * x[j]; | ||
| dot += cut_coeff * cut_coeff; | ||
| } | ||
| cut_violation = rhs_storage_[row] - cut_x; | ||
| cut_norm = std::sqrt(dot); | ||
| const f_t distance = cut_violation / cut_norm; | ||
| return distance; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guard against zero-norm cuts (division-by-zero / NaNs) in distance + orthogonality scoring
cut_distance and cut_orthogonality divide by norms without checking for 0 (Line 53-55, 81-84). A zero (or denormal) norm can poison sorting and selection. As per coding guidelines, numerical stability needs epsilon checks.
Patch sketch
f_t cut_pool_t<i_t, f_t>::cut_distance(i_t row, const std::vector<f_t>& x, f_t& cut_violation, f_t &cut_norm)
{
+ constexpr f_t kMinNorm = 1e-12;
...
cut_norm = std::sqrt(dot);
+ if (!(cut_norm > kMinNorm)) {
+ cut_violation = 0.0;
+ cut_norm = 0.0;
+ return 0.0;
+ }
const f_t distance = cut_violation / cut_norm;
return distance;
}
f_t cut_pool_t<i_t, f_t>::cut_orthogonality(i_t i, i_t j)
{
+ constexpr f_t kMinNorm = 1e-12;
...
f_t norm_i = cut_norms_[i];
f_t norm_j = cut_norms_[j];
+ if (!(norm_i > kMinNorm) || !(norm_j > kMinNorm)) { return 0.0; }
return 1.0 - std::abs(dot) / (norm_i * norm_j);
}Also applies to: 68-84, 87-155
🤖 Prompt for AI Agents
In @cpp/src/dual_simplex/cuts.cpp around lines 39 - 56, The functions
cut_distance and cut_orthogonality currently divide by norms without checks; add
a small epsilon guard using std::numeric_limits<f_t>::epsilon() (or a tiny
constant like 1e-12) to avoid division-by-zero/NaNs: after computing dot and
before computing cut_norm/ortho_norm, if dot <= eps*eps treat the norm as zero
(set cut_norm/ortho_norm = 0) and set the resulting distance/orthogonality score
to a safe value (e.g., 0.0) instead of performing the division; update
cut_violation/cut_norm return behavior accordingly and apply the same guard
pattern in both cut_distance and cut_orthogonality (referencing variables
cut_norm, ortho_norm, cut_violation, distance, ortho_score and the functions
cut_distance and cut_orthogonality).
| void cut_generation_t<i_t, f_t>::generate_mir_cuts(const lp_problem_t<i_t, f_t>& lp, | ||
| const simplex_solver_settings_t<i_t, f_t>& settings, | ||
| csr_matrix_t<i_t, f_t>& Arow, | ||
| const std::vector<i_t>& new_slacks, | ||
| const std::vector<variable_type_t>& var_types, | ||
| const std::vector<f_t>& xstar) | ||
| { | ||
| mixed_integer_rounding_cut_t<i_t, f_t> mir(lp.num_cols, settings); | ||
| mir.initialize(lp, new_slacks, xstar); | ||
|
|
||
| std::vector<i_t> slack_map(lp.num_rows); | ||
| for (i_t slack : new_slacks) { | ||
| const i_t col_start = lp.A.col_start[slack]; | ||
| const i_t col_end = lp.A.col_start[slack + 1]; | ||
| const i_t col_len = col_end - col_start; | ||
| if (col_len != 1) { | ||
| printf("Generate MIR cuts: Slack %d has %d nzs in column\n", slack, col_len); | ||
| exit(1); | ||
| } | ||
| const i_t i = lp.A.i[col_start]; | ||
| slack_map[i] = slack; | ||
| } | ||
|
|
||
| for (i_t i = 0; i < lp.num_rows; i++) { | ||
| sparse_vector_t<i_t, f_t> inequality(Arow, i); | ||
| f_t inequality_rhs = lp.rhs[i]; | ||
|
|
||
| const i_t row_start = Arow.row_start[i]; | ||
| const i_t row_end = Arow.row_start[i + 1]; | ||
| i_t slack = slack_map[i]; | ||
|
|
||
| // Remove the slack from the equality to get an inequality | ||
| for (i_t k = 0; k < inequality.i.size(); k++) { | ||
| const i_t j = inequality.i[k]; | ||
| if (j == slack) { inequality.x[k] = 0.0; } | ||
| } | ||
|
|
||
| // inequaility'*x <= inequality_rhs | ||
| // But for MIR we need: inequality'*x >= inequality_rhs | ||
| inequality_rhs *= -1; | ||
| inequality.negate(); | ||
|
|
||
| sparse_vector_t<i_t, f_t> cut(lp.num_cols, 0); | ||
| f_t cut_rhs; | ||
| i_t mir_status = | ||
| mir.generate_cut(inequality, inequality_rhs, lp.upper, lp.lower, var_types, cut, cut_rhs); | ||
| bool add_cut = false; | ||
| const f_t min_cut_distance = 1e-4; | ||
| if (mir_status == 0) { | ||
| if (cut.i.size() == 0) { | ||
| continue; | ||
| } | ||
| mir.substitute_slacks(lp, Arow, cut, cut_rhs); | ||
| if (cut.i.size() == 0) { | ||
| continue; | ||
| } | ||
| // Check that the cut is violated | ||
| // The cut is of the form cut'*x >= cut_rhs | ||
| // We need that cut'*xstar < cut_rhs for the cut to be violated by the current relaxation solution xstar | ||
| f_t dot = cut.dot(xstar); | ||
| f_t cut_norm = cut.norm2_squared(); | ||
| if (dot < cut_rhs && cut_norm > 0.0) { | ||
| // Cut is violated. Compute it's distance | ||
| f_t cut_distance = (cut_rhs - dot) / std::sqrt(cut_norm); | ||
| if (cut_distance > min_cut_distance) { | ||
| add_cut = true; | ||
| } | ||
| } | ||
| } | ||
| if (add_cut) { | ||
| cut_pool_.add_cut(cut_type_t::MIXED_INTEGER_ROUNDING, cut, cut_rhs); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Search for generate_mir_cuts calls and new_slacks creation
echo "=== Searching for generate_mir_cuts calls ==="
rg -n "generate_mir_cuts\(" cpp/src --context=5 -t cpp -t cu
echo -e "\n=== Searching for new_slacks creation ==="
rg -n "new_slacks" cpp/src --context=3 -t cpp -t cu | head -100Repository: NVIDIA/cuopt
Length of output: 145
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Look at the full file to understand context
echo "=== Viewing cuts.cpp around the function ==="
wc -l cpp/src/dual_simplex/cuts.cppRepository: NVIDIA/cuopt
Length of output: 135
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Search for slack validation patterns and how slacks are created
echo "=== Searching for slack creation/management patterns ==="
rg -n "slack_map|new_slacks" cpp/src/dual_simplex/cuts.cpp -B2 -A2 | head -80Repository: NVIDIA/cuopt
Length of output: 3456
🏁 Script executed:
#!/bin/bash
set -euo pipeflag
# Search for where new_slacks is created/passed
echo "=== Tracing new_slacks creation ==="
rg -n "new_slacks\s*=" cpp/src --context=4 -t cpp | head -150
echo -e "\n=== Searching for slack variable handling in presolve/initialization ==="
rg -n "slack|num_rows" cpp/src/presolve -t cpp | grep -i "slack\|add.*row\|extend" | head -50Repository: NVIDIA/cuopt
Length of output: 109
🏁 Script executed:
#!/bin/bash
set -euo pipeflag
# Look at the actual context where generate_mir_cuts is called
sed -n '520,545p' cpp/src/dual_simplex/cuts.cppRepository: NVIDIA/cuopt
Length of output: 109
🏁 Script executed:
#!/bin/bash
set -euo pipeflag
# Examine what happens with rows that don't have slacks
echo "=== Check if there are ANY tests or assertions about slack completeness ==="
rg -n "slack_map|new_slacks" cpp/src --context=1 -t cpp | grep -E "assert|check|valid|error|size|empty" | head -30Repository: NVIDIA/cuopt
Length of output: 109
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Search for where new_slacks is created
echo "=== Searching for where new_slacks is populated ==="
rg -n "new_slacks\s*=" cpp/src -t cpp -B3 -A3 | head -200Repository: NVIDIA/cuopt
Length of output: 107
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Get context around the generate_mir_cuts call
sed -n '520,545p' cpp/src/dual_simplex/cuts.cppRepository: NVIDIA/cuopt
Length of output: 1366
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Search for assertions or validation about slack completeness
rg -n "assert|CHECK.*slack|validate.*slack" cpp/src -t cpp | head -30Repository: NVIDIA/cuopt
Length of output: 3141
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Look for where slacks are created during presolve
echo "=== Searching presolve and solver initialization ==="
find cpp/src -name "*.cpp" -o -name "*.hpp" | xargs grep -l "slack" | head -10Repository: NVIDIA/cuopt
Length of output: 472
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find all calls to generate_cuts
echo "=== Calls to generate_cuts ==="
rg -n "\.generate_cuts\(" cpp/src -t cpp -B5 -A3 | head -200Repository: NVIDIA/cuopt
Length of output: 959
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Look at phase2.hpp where it's likely called
echo "=== Checking phase2.hpp for new_slacks handling ==="
grep -n "new_slacks\|generate_cuts" cpp/src/dual_simplex/phase2.hpp | head -30Repository: NVIDIA/cuopt
Length of output: 107
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Check if there's any validation that all rows have slacks
echo "=== Looking for slack completeness validation ==="
rg -n "num_rows.*slack\|slack.*num_rows" cpp/src -t cpp -B2 -A2 | head -80Repository: NVIDIA/cuopt
Length of output: 105
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Search for new_slacks_ in branch_and_bound
echo "=== Searching for new_slacks_ definition and initialization ==="
rg -n "new_slacks_" cpp/src/dual_simplex/branch_and_bound.cpp -B2 -A2 | head -150Repository: NVIDIA/cuopt
Length of output: 2687
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Check the member variable declaration
echo "=== Searching for new_slacks_ in header files ==="
rg -n "new_slacks_" cpp/src/dual_simplex -t cpp -t hpp | head -50Repository: NVIDIA/cuopt
Length of output: 150
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Get more context around line 1779
sed -n '1700,1800p' cpp/src/dual_simplex/branch_and_bound.cpp | grep -n "new_slacks\|slack\|Arow" | head -40Repository: NVIDIA/cuopt
Length of output: 211
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Search for convert_user_problem definition
echo "=== Finding convert_user_problem ==="
rg -n "convert_user_problem" cpp/src -t cpp --no-heading -lRepository: NVIDIA/cuopt
Length of output: 233
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Look at convert_user_problem to see how slacks are created
rg -n "def convert_user_problem|void convert_user_problem" cpp/src -A 150 -B 5 -t cpp | head -300Repository: NVIDIA/cuopt
Length of output: 24789
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# More targeted search in presolve
grep -n "convert_user_problem" cpp/src/dual_simplex/presolve.cpp -A 100 | head -150Repository: NVIDIA/cuopt
Length of output: 6601
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Get the complete generate_mir_cuts function
sed -n '571,643p' cpp/src/dual_simplex/cuts.cppRepository: NVIDIA/cuopt
Length of output: 2751
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Check the convert_range_rows function to see how slacks are created
rg -n "convert_range_rows" cpp/src/dual_simplex/presolve.cpp -A 50 | head -100Repository: NVIDIA/cuopt
Length of output: 4108
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Verify: are slacks added for normal inequality constraints?
echo "=== Check if <= constraints get slack variables added ==="
grep -n "slack\|<= \|constraint" cpp/src/dual_simplex/presolve.cpp | grep -E "599|600|601|602|603|604|605|620" | head -20Repository: NVIDIA/cuopt
Length of output: 113
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Look at the entire convert_user_problem to see where new_slacks is populated
sed -n '559,820p' cpp/src/dual_simplex/presolve.cpp | grep -n "new_slacks\|push_back"Repository: NVIDIA/cuopt
Length of output: 478
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Check if any other place adds to new_slacks besides convert_range_rows
rg -n "new_slacks.push_back\|new_slacks.*=" cpp/src/dual_simplex/presolve.cpp -B2 -A2Repository: NVIDIA/cuopt
Length of output: 38
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Check how many rows the original LP might have vs how many slack variables are added
# Look at a typical use case
sed -n '245,252p' cpp/src/dual_simplex/branch_and_bound.cppRepository: NVIDIA/cuopt
Length of output: 375
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Look at convert_less_than_to_equal to see if it adds a slack for every <= row
rg -n "convert_less_than_to_equal" cpp/src/dual_simplex/presolve.cpp -A 80 | head -150Repository: NVIDIA/cuopt
Length of output: 6076
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Check add_artificial_variables
rg -n "add_artifical_variables" cpp/src/dual_simplex/presolve.cpp -A 50 | head -100Repository: NVIDIA/cuopt
Length of output: 4128
🏁 Script executed:
#!/bin/bash
set -euso pipefail
# Key question: are all inequality constraints converted to equality with slacks?
# Look at the high level flow
sed -n '559,830p' cpp/src/dual_simplex/presolve.cpp | tail -200Repository: NVIDIA/cuopt
Length of output: 8300
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Check if there's any documentation about the contract for generate_mir_cuts
# and whether all rows must have slacks
rg -n "precondition\|assumption\|require\|guarantee" cpp/src/dual_simplex/cuts.cpp -B2 -A2 | head -50Repository: NVIDIA/cuopt
Length of output: 38
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Check if there are any tests or assertions that validate slack completeness
rg -n "new_slacks.*size\|slack_map" cpp/src -B3 -A3 | head -100Repository: NVIDIA/cuopt
Length of output: 38
Initialize slack_map with sentinel value and validate row slack presence
slack_map is default-initialized to 0, then only populated for rows with slack variables in new_slacks. When the loop processes every row without validation, rows lacking a mapped slack remain at index 0, causing if (j == slack) to incorrectly zero variable 0's coefficient. Initialize with a sentinel (e.g., -1) and skip/validate rows without slacks:
Suggested fix
- std::vector<i_t> slack_map(lp.num_rows);
+ std::vector<i_t> slack_map(lp.num_rows, static_cast<i_t>(-1));
...
for (i_t i = 0; i < lp.num_rows; i++) {
...
- i_t slack = slack_map[i];
+ const i_t slack = slack_map[i];
+ if (slack == static_cast<i_t>(-1)) { continue; }
...
}🤖 Prompt for AI Agents
In @cpp/src/dual_simplex/cuts.cpp around lines 570 - 643, The slack_map in
generate_mir_cuts is default-initialized to 0 causing rows without slacks to
treat column 0 as a slack; initialize slack_map with a sentinel (e.g., -1)
instead, set slack_map[i] = slack for each slack in new_slacks, and then, before
using slack inside the row loop (in the loop that constructs inequality), skip
or continue the row when slack_map[i] == -1 (or otherwise validate the slack
index is valid) so you don't incorrectly zero variable 0's coefficient; update
any logic that assumes a slack exists to handle the missing-slack case safely.
| enum cut_type_t : int8_t { | ||
| MIXED_INTEGER_GOMORY = 0, | ||
| MIXED_INTEGER_ROUNDING = 1, | ||
| KNAPSACK = 2, | ||
| }; | ||
|
|
||
| template <typename i_t, typename f_t> | ||
| void print_cut_types(const std::vector<cut_type_t>& cut_types, const simplex_solver_settings_t<i_t, f_t>& settings) { | ||
| i_t num_gomory_cuts = 0; | ||
| i_t num_mir_cuts = 0; | ||
| i_t num_knapsack_cuts = 0; | ||
| for (i_t i = 0; i < cut_types.size(); i++) { | ||
| if (cut_types[i] == cut_type_t::MIXED_INTEGER_GOMORY) { | ||
| num_gomory_cuts++; | ||
| } else if (cut_types[i] == cut_type_t::MIXED_INTEGER_ROUNDING) { | ||
| num_mir_cuts++; | ||
| } else if (cut_types[i] == cut_type_t::KNAPSACK) { | ||
| num_knapsack_cuts++; | ||
| } | ||
| } | ||
| settings.log.printf("Gomory cuts: %d, MIR cuts: %d, Knapsack cuts: %d\n", num_gomory_cuts, num_mir_cuts, num_knapsack_cuts); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix printf-format UB in print_cut_types (don’t use %d with i_t).
settings.log.printf("...%d...", num_gomory_cuts, ...) is undefined behavior when i_t isn’t int (e.g., int64_t). Please cast to a known type + use the right specifier, or switch to a type-safe formatting API.
Proposed fix
- settings.log.printf("Gomory cuts: %d, MIR cuts: %d, Knapsack cuts: %d\n", num_gomory_cuts, num_mir_cuts, num_knapsack_cuts);
+ settings.log.printf("Gomory cuts: %lld, MIR cuts: %lld, Knapsack cuts: %lld\n",
+ static_cast<long long>(num_gomory_cuts),
+ static_cast<long long>(num_mir_cuts),
+ static_cast<long long>(num_knapsack_cuts));📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| enum cut_type_t : int8_t { | |
| MIXED_INTEGER_GOMORY = 0, | |
| MIXED_INTEGER_ROUNDING = 1, | |
| KNAPSACK = 2, | |
| }; | |
| template <typename i_t, typename f_t> | |
| void print_cut_types(const std::vector<cut_type_t>& cut_types, const simplex_solver_settings_t<i_t, f_t>& settings) { | |
| i_t num_gomory_cuts = 0; | |
| i_t num_mir_cuts = 0; | |
| i_t num_knapsack_cuts = 0; | |
| for (i_t i = 0; i < cut_types.size(); i++) { | |
| if (cut_types[i] == cut_type_t::MIXED_INTEGER_GOMORY) { | |
| num_gomory_cuts++; | |
| } else if (cut_types[i] == cut_type_t::MIXED_INTEGER_ROUNDING) { | |
| num_mir_cuts++; | |
| } else if (cut_types[i] == cut_type_t::KNAPSACK) { | |
| num_knapsack_cuts++; | |
| } | |
| } | |
| settings.log.printf("Gomory cuts: %d, MIR cuts: %d, Knapsack cuts: %d\n", num_gomory_cuts, num_mir_cuts, num_knapsack_cuts); | |
| } | |
| enum cut_type_t : int8_t { | |
| MIXED_INTEGER_GOMORY = 0, | |
| MIXED_INTEGER_ROUNDING = 1, | |
| KNAPSACK = 2, | |
| }; | |
| template <typename i_t, typename f_t> | |
| void print_cut_types(const std::vector<cut_type_t>& cut_types, const simplex_solver_settings_t<i_t, f_t>& settings) { | |
| i_t num_gomory_cuts = 0; | |
| i_t num_mir_cuts = 0; | |
| i_t num_knapsack_cuts = 0; | |
| for (i_t i = 0; i < cut_types.size(); i++) { | |
| if (cut_types[i] == cut_type_t::MIXED_INTEGER_GOMORY) { | |
| num_gomory_cuts++; | |
| } else if (cut_types[i] == cut_type_t::MIXED_INTEGER_ROUNDING) { | |
| num_mir_cuts++; | |
| } else if (cut_types[i] == cut_type_t::KNAPSACK) { | |
| num_knapsack_cuts++; | |
| } | |
| } | |
| settings.log.printf("Gomory cuts: %lld, MIR cuts: %lld, Knapsack cuts: %lld\n", | |
| static_cast<long long>(num_gomory_cuts), | |
| static_cast<long long>(num_mir_cuts), | |
| static_cast<long long>(num_knapsack_cuts)); | |
| } |
🤖 Prompt for AI Agents
In @cpp/src/dual_simplex/cuts.hpp around lines 21 - 42, print_cut_types uses
settings.log.printf with "%d" for variables of template integer type i_t, which
is UB if i_t != int; fix by converting the printed counters (num_gomory_cuts,
num_mir_cuts, num_knapsack_cuts) to a concrete integer type when calling
settings.log.printf (e.g., static_cast<long long>(...)) and change the format
specifier to match (e.g., "%lld"), or alternatively cast to intmax_t and use
"%jd"/PRIdMAX; update the printf call in print_cut_types to use the chosen cast
and matching specifier to avoid undefined behavior.
| if (!std::isnan(obj)) { | ||
| f_t change_in_obj = obj - current_obj; | ||
| f_t change_in_x = solution[j] - std::floor(solution[j]); | ||
| mutex.lock(); | ||
| pseudo_cost_sum_down[j] += change_in_obj / change_in_x; | ||
| pseudo_cost_num_down[j]++; | ||
| mutex.unlock(); | ||
| pseudo_cost_down[k] = pseudo_cost_sum_down[j] / pseudo_cost_num_down[j]; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Race condition: data accessed outside mutex protection.
Line 414 reads pseudo_cost_sum_down[j] and pseudo_cost_num_down[j] after releasing the mutex at line 413. This creates a race condition—another thread could update these shared variables between the unlock and the read, leading to inconsistent pseudo-cost calculations and incorrect branch variable selection.
Compare with the up-branch case (lines 434-438), where line 437 correctly reads the values inside the mutex before unlocking at line 438.
🔒 Proposed fix to move the read inside the mutex
mutex.lock();
pseudo_cost_sum_down[j] += change_in_obj / change_in_x;
pseudo_cost_num_down[j]++;
+ pseudo_cost_down[k] = pseudo_cost_sum_down[j] / pseudo_cost_num_down[j];
mutex.unlock();
- pseudo_cost_down[k] = pseudo_cost_sum_down[j] / pseudo_cost_num_down[j];
}Based on coding guidelines for thread-safe access to shared solver state.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (!std::isnan(obj)) { | |
| f_t change_in_obj = obj - current_obj; | |
| f_t change_in_x = solution[j] - std::floor(solution[j]); | |
| mutex.lock(); | |
| pseudo_cost_sum_down[j] += change_in_obj / change_in_x; | |
| pseudo_cost_num_down[j]++; | |
| mutex.unlock(); | |
| pseudo_cost_down[k] = pseudo_cost_sum_down[j] / pseudo_cost_num_down[j]; | |
| } | |
| } | |
| if (!std::isnan(obj)) { | |
| f_t change_in_obj = obj - current_obj; | |
| f_t change_in_x = solution[j] - std::floor(solution[j]); | |
| mutex.lock(); | |
| pseudo_cost_sum_down[j] += change_in_obj / change_in_x; | |
| pseudo_cost_num_down[j]++; | |
| pseudo_cost_down[k] = pseudo_cost_sum_down[j] / pseudo_cost_num_down[j]; | |
| mutex.unlock(); | |
| } |
| i_t branch_var = fractional[0]; | ||
| f_t max_score = -1; | ||
| i_t select = -1; | ||
| for (i_t k = 0; k < num_fractional; k++) { | ||
| if (score[k] > max_score) { | ||
| max_score = score[k]; | ||
| branch_var = fractional[k]; | ||
| select = k; | ||
| } | ||
| } | ||
|
|
||
| printf( | ||
| "pc reliability branching on %d. Value %e. Score %e. Iter %d. Trial branches %d\n", branch_var, solution[branch_var], score[select], iter, trial_branches); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Out-of-bounds access when no fractional variables exist.
If num_fractional is 0 (empty fractional vector):
- Line 448:
branch_var = fractional[0]accesses out of bounds - Line 450:
selectremains -1 - Line 460:
score[select]accesses index -1, causing undefined behavior
This matches the static analysis warning. Add a guard to handle empty input.
🛡️ Proposed fix to guard against empty fractional vector
+ if (num_fractional == 0) {
+ log.printf("No fractional variables for reliable branching\n");
+ return -1; // or throw/assert depending on contract
+ }
+
i_t branch_var = fractional[0];
f_t max_score = -1;
i_t select = -1;Based on coding guidelines for input validation and static analysis hint.
Committable suggestion skipped: line range outside the PR's diff.
🧰 Tools
🪛 Cppcheck (2.19.0)
[warning] 460-460: Array index -1 is out of bounds.
(negativeContainerIndex)
🤖 Prompt for AI Agents
In @cpp/src/dual_simplex/pseudo_costs.cpp around lines 448 - 460, The loop
assumes num_fractional>0 but the code initializes branch_var = fractional[0] and
later uses score[select] even when select stays -1; add an early guard checking
if num_fractional == 0 (or fractional.empty()) before those assignments and
handle it (e.g., return, skip branching, or set a safe default and log) to avoid
out-of-bounds access; adjust code around variables branch_var, select,
fractional, score, and the printf that prints solution[branch_var] and
score[select] so they are only used when a valid selection was made (select >=
0).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
cpp/src/dual_simplex/sparse_matrix.cpp (1)
571-607: Fix off-by-one / wrong guard in CSC check_matrix (and format specifier).You read
this->col_start[j + 1]but the guard isj >= col_start.size(). This can still OOB whenj == size-1. Also,col_start.size()issize_tbut you print with%ld.Proposed fix
#ifdef CHECK_MATRIX std::vector<i_t> row_marker(this->m, -1); for (i_t j = 0; j < this->n; ++j) { - if (j >= col_start.size()) { - printf("Col start too small size %ld n %d\n", col_start.size(), this->n); + if (static_cast<size_t>(j + 1) >= this->col_start.size()) { + printf("Col start too small size %zu n %d\n", this->col_start.size(), this->n); return -1; } const i_t col_start = this->col_start[j]; const i_t col_end = this->col_start[j + 1];Based on learnings,
check_matrix()is diagnostic code; consider accumulating an error count and returning at end (to keep “print all errors in one pass”).
🤖 Fix all issues with AI agents
In @cpp/src/dual_simplex/branch_and_bound.cpp:
- Around line 238-263: The debug stdout prints and hard exits in
branch_and_bound.cpp (e.g., the num_integer_variables_ count print, the slack
verification printf/exit blocks that iterate new_slacks_, and any
fopen("solution.dat"...) I/O) must be removed or wrapped in a compile-time or
runtime debug gate (e.g., #ifdef DEBUG_PRINTS or a SolverOptions.verbose/debug
flag); replace exit(1) with returning an error status or throwing a well-defined
exception from the containing function (do not terminate the process), and
convert any ad-hoc FILE* usage to RAII (std::ofstream or a small wrapper)
ensuring proper close on all paths; specifically change the slack checks inside
the constructor/solve path to either be omitted in release or to return a
bool/error code (propagate up via the function that owns new_slacks_ validation)
and guard printing of num_integer_variables_ behind the debug flag.
- Around line 1478-1507: basic_list and nonbasic_list are pre-sized then filled
with push_back in solve_root_relaxation(), causing incorrect lengths; before the
loop that reads crossover_vstatus_ in solve_root_relaxation() (the block that
iterates j and push_back into basic_list/nonbasic_list) clear both vectors (or
reinitialize them to empty) and optionally reserve their expected capacities
(original_lp_.num_rows and original_lp_.num_cols - original_lp_.num_rows) so
push_back produces the correct sizes used later by basis_update.refactor_basis
and the subsequent size checks.
In @cpp/src/dual_simplex/cuts.cpp:
- Around line 1528-1537: The repeated-index check in cuts.cpp is a no-op because
it uses the equality operator instead of assignment; in the loop that iterates
over cut.i (the block using std::vector<i_t> check and for (i_t p = 0; p <
cut.i.size(); p++)), replace the statement check[cut.i[p]] == 1; with an
assignment so the check array is updated (i.e., set the slot to 1 after
confirming it was zero). Apply the same fix to the identical block referenced
around lines 1736-1746 so duplicates are actually recorded and detected.
- Around line 58-74: In cut_distance, guard the division by zero when computing
distance = cut_violation / cut_norm: after computing dot and setting cut_norm =
std::sqrt(dot) (in function cut_pool_t<i_t, f_t>::cut_distance), check if
cut_norm <= eps (use a small threshold like std::numeric_limits<f_t>::epsilon()
or 1e-12); if so set cut_norm = 0, set cut_violation appropriately and return a
safe value (e.g., 0.0) instead of dividing, and ensure cut_violation and
cut_norm are left consistent for callers; reference cut_storage_, rhs_storage_,
cut_violation, cut_norm and the cut_distance function when making the change.
- Around line 175-196: get_best_cuts currently resets best_cuts but never clears
the output vectors best_rhs and best_cut_types, causing repeated calls to
accumulate stale entries; fix by clearing best_rhs and best_cut_types at the
start of cut_pool_t<i_t, f_t>::get_best_cuts (and optionally reserve capacity
based on best_cuts_.size()), then proceed to push_back values as before (use
rhs_storage_ and cut_type_ references as already used).
🧹 Nitpick comments (1)
cpp/src/dual_simplex/sparse_matrix.cpp (1)
634-652: Keep CSR check_matrix behavior consistent (diagnostics vs early-exit).Now that
csr_matrix_t::check_matrix()returnsi_tand early-returns on the first bad column index / duplicate, it changes the earlier “report everything” diagnostic behavior. If that’s intentional—great; otherwise prefer counting errors and returning-1at the end to preserve full diagnostics (especially valuable for malformed cut rows). Based on learnings, check_matrix is debug/diagnostic and callers may not check return codes consistently.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hppcpp/src/dual_simplex/sparse_matrix.cppcpp/src/dual_simplex/sparse_matrix.hpp
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{cu,cuh,cpp,hpp,h}
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
**/*.{cu,cuh,cpp,hpp,h}: Track GPU device memory allocations and deallocations to prevent memory leaks; ensure cudaMalloc/cudaFree balance and cleanup of streams/events
Validate algorithm correctness in optimization logic: simplex pivots, branch-and-bound decisions, routing heuristics, and constraint/objective handling must produce correct results
Check numerical stability: prevent overflow/underflow, precision loss, division by zero/near-zero, and use epsilon comparisons for floating-point equality checks
Validate correct initialization of variable bounds, constraint coefficients, and algorithm state before solving; ensure reset when transitioning between algorithm phases (presolve, simplex, diving, crossover)
Ensure variables and constraints are accessed from the correct problem context (original vs presolve vs folded vs postsolve); verify index mapping consistency across problem transformations
For concurrent CUDA operations (barriers, async operations), explicitly create and manage dedicated streams instead of reusing the default stream; document stream lifecycle
Eliminate unnecessary host-device synchronization (cudaDeviceSynchronize) in hot paths that blocks GPU pipeline; use streams and events for async execution
Assess algorithmic complexity for large-scale problems (millions of variables/constraints); ensure O(n log n) or better complexity, not O(n²) or worse
Verify correct problem size checks before expensive GPU/CPU operations; prevent resource exhaustion on oversized problems
Identify assertions with overly strict numerical tolerances that fail on legitimate degenerate/edge cases (near-zero pivots, singular matrices, empty problems)
Ensure race conditions are absent in multi-GPU code and multi-threaded server implementations; verify proper synchronization of shared state
Refactor code duplication in solver components (3+ occurrences) into shared utilities; for GPU kernels, use templated device functions to avoid duplication
Check that hard-coded GPU de...
Files:
cpp/src/dual_simplex/sparse_matrix.hppcpp/src/dual_simplex/sparse_matrix.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hpp
**/*.{h,hpp,py}
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
Verify C API does not break ABI stability (no struct layout changes, field reordering); maintain backward compatibility in Python and server APIs with deprecation warnings
Files:
cpp/src/dual_simplex/sparse_matrix.hppcpp/src/dual_simplex/cuts.hpp
**/*.{cpp,hpp,h}
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
**/*.{cpp,hpp,h}: Check for unclosed file handles when reading MPS/QPS problem files; ensure RAII patterns or proper cleanup in exception paths
Validate input sanitization to prevent buffer overflows and resource exhaustion attacks; avoid unsafe deserialization of problem files
Prevent thread-unsafe use of global and static variables; use proper mutex/synchronization in server code accessing shared solver state
Files:
cpp/src/dual_simplex/sparse_matrix.hppcpp/src/dual_simplex/sparse_matrix.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hpp
**/*.{cu,cpp,hpp,h}
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
Avoid inappropriate use of exceptions in performance-critical GPU operation paths; prefer error codes or CUDA error checking for latency-sensitive code
Files:
cpp/src/dual_simplex/sparse_matrix.hppcpp/src/dual_simplex/sparse_matrix.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hpp
🧠 Learnings (22)
📓 Common learnings
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Reduce tight coupling between solver components (presolve, simplex, basis, barrier); increase modularity and reusability of optimization algorithms
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Validate correct initialization of variable bounds, constraint coefficients, and algorithm state before solving; ensure reset when transitioning between algorithm phases (presolve, simplex, diving, crossover)
📚 Learning: 2025-12-03T23:29:26.391Z
Learnt from: chris-maes
Repo: NVIDIA/cuopt PR: 500
File: cpp/src/dual_simplex/sparse_matrix.cpp:519-524
Timestamp: 2025-12-03T23:29:26.391Z
Learning: In cpp/src/dual_simplex/sparse_matrix.cpp, the check_matrix() function is debug/diagnostic code (wrapped in #ifdef CHECK_MATRIX) that intentionally prints errors without necessarily returning early. The return codes from this debug code are not actively checked; the purpose is to print all validation errors in one pass for better diagnostics.
Applied to files:
cpp/src/dual_simplex/sparse_matrix.hppcpp/src/dual_simplex/sparse_matrix.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-12-04T04:11:12.640Z
Learnt from: chris-maes
Repo: NVIDIA/cuopt PR: 500
File: cpp/src/dual_simplex/scaling.cpp:68-76
Timestamp: 2025-12-04T04:11:12.640Z
Learning: In the cuOPT dual simplex solver, CSR/CSC matrices (including the quadratic objective matrix Q) are required to have valid dimensions and indices by construction. Runtime bounds checking in performance-critical paths like matrix scaling is avoided to prevent slowdowns. Validation is performed via debug-only check_matrix() calls wrapped in #ifdef CHECK_MATRIX.
Applied to files:
cpp/src/dual_simplex/sparse_matrix.hppcpp/src/dual_simplex/sparse_matrix.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Identify assertions with overly strict numerical tolerances that fail on legitimate degenerate/edge cases (near-zero pivots, singular matrices, empty problems)
Applied to files:
cpp/src/dual_simplex/sparse_matrix.cppcpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Reduce tight coupling between solver components (presolve, simplex, basis, barrier); increase modularity and reusability of optimization algorithms
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Validate algorithm correctness in optimization logic: simplex pivots, branch-and-bound decisions, routing heuristics, and constraint/objective handling must produce correct results
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Validate correct initialization of variable bounds, constraint coefficients, and algorithm state before solving; ensure reset when transitioning between algorithm phases (presolve, simplex, diving, crossover)
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*test*.{cpp,cu,py} : Add tests for algorithm phase transitions: verify correct initialization of bounds and state when transitioning from presolve to simplex to diving to crossover
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Ensure variables and constraints are accessed from the correct problem context (original vs presolve vs folded vs postsolve); verify index mapping consistency across problem transformations
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Assess algorithmic complexity for large-scale problems (millions of variables/constraints); ensure O(n log n) or better complexity, not O(n²) or worse
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cpp
📚 Learning: 2025-12-04T20:09:09.264Z
Learnt from: chris-maes
Repo: NVIDIA/cuopt PR: 602
File: cpp/src/linear_programming/solve.cu:732-742
Timestamp: 2025-12-04T20:09:09.264Z
Learning: In cpp/src/linear_programming/solve.cu, the barrier solver does not currently return INFEASIBLE or UNBOUNDED status. It only returns OPTIMAL, TIME_LIMIT, NUMERICAL_ISSUES, or CONCURRENT_LIMIT.
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*test*.{cpp,cu,py} : Test with free variables, singleton problems, and extreme problem dimensions near resource limits to validate edge case handling
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cpp
📚 Learning: 2025-10-22T14:25:22.899Z
Learnt from: aliceb-nv
Repo: NVIDIA/cuopt PR: 527
File: cpp/src/mip/diversity/lns/rins.cu:167-175
Timestamp: 2025-10-22T14:25:22.899Z
Learning: In MIP (Mixed Integer Programming) problems in the cuOPT codebase, `n_integer_vars == 0` is impossible by definition—MIP problems must have at least one integer variable. If there are no integer variables, it would be a pure Linear Programming (LP) problem, not a MIP problem.
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cpp,hpp,h} : Check for unclosed file handles when reading MPS/QPS problem files; ensure RAII patterns or proper cleanup in exception paths
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Verify correct problem size checks before expensive GPU/CPU operations; prevent resource exhaustion on oversized problems
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*test*.{cpp,cu,py} : Write tests validating numerical correctness of optimization results (not just 'runs without error'); test degenerate cases (infeasible, unbounded, empty, singleton problems)
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Check numerical stability: prevent overflow/underflow, precision loss, division by zero/near-zero, and use epsilon comparisons for floating-point equality checks
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cpp,hpp,h} : Avoid inappropriate use of exceptions in performance-critical GPU operation paths; prefer error codes or CUDA error checking for latency-sensitive code
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Verify error propagation from CUDA to user-facing APIs is complete; ensure CUDA errors are caught and mapped to meaningful user error codes
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cpp,hpp,h} : Prevent thread-unsafe use of global and static variables; use proper mutex/synchronization in server code accessing shared solver state
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cpp,hpp,h} : Validate input sanitization to prevent buffer overflows and resource exhaustion attacks; avoid unsafe deserialization of problem files
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Refactor code duplication in solver components (3+ occurrences) into shared utilities; for GPU kernels, use templated device functions to avoid duplication
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cppcpp/src/dual_simplex/cuts.hpp
🧬 Code graph analysis (2)
cpp/src/dual_simplex/branch_and_bound.cpp (3)
cpp/src/dual_simplex/branch_and_bound.hpp (5)
upper_bound(126-126)solution(102-102)solution(129-129)solution(215-215)lp_settings(118-124)cpp/src/dual_simplex/solve.hpp (2)
compute_objective(34-34)solve_linear_program_with_advanced_basis(53-62)cpp/src/dual_simplex/solve.cpp (7)
compute_objective(77-86)compute_objective(77-77)compute_objective(659-660)basic_list(114-114)solve_linear_program_with_advanced_basis(129-295)solve_linear_program_with_advanced_basis(129-138)solve_linear_program_with_advanced_basis(675-684)
cpp/src/dual_simplex/cuts.cpp (1)
cpp/src/dual_simplex/cuts.hpp (24)
cut_type(83-83)cut(330-332)cut_storage_(94-94)row(97-97)row(98-98)i(99-99)x_relax(85-85)a(324-328)a(334-340)best_cuts(88-88)lp(126-134)lp(173-181)lp(185-193)lp(196-201)lp(204-209)lp(213-222)lp(248-258)lp(283-285)lp(294-296)lp(298-300)lp(309-311)lp(342-345)lp(349-355)lower_bounds(316-319)
🪛 Clang (14.0.6)
cpp/src/dual_simplex/cuts.cpp
[error] 8-8: 'dual_simplex/cuts.hpp' file not found
(clang-diagnostic-error)
cpp/src/dual_simplex/cuts.hpp
[error] 9-9: 'dual_simplex/basis_updates.hpp' file not found
(clang-diagnostic-error)
🪛 Cppcheck (2.19.0)
cpp/src/dual_simplex/branch_and_bound.cpp
[error] 617-617: Resource leak
(resourceLeak)
🔇 Additional comments (7)
cpp/src/dual_simplex/sparse_matrix.hpp (1)
154-162: Clarify/standardize the new CSR mutation + check_matrix return contract.
csr_matrix_t::check_matrix()now returnsi_t; please ensure all call sites either (a) ignore it intentionally, or (b) handle it consistently (e.g.,if (A.check_matrix(...) != 0) return error;).- For
append_rows/append_row, it’d help to document the invariants expected/guaranteed (e.g., whether input rows require sorted unique column indices, and whethernz_maxis “capacity” vs “actual nnz”).
As per coding guidelines, validate correct initialization of constraint coefficients and algorithm state before solving; ensure reset when transitioning between phases.cpp/src/dual_simplex/cuts.hpp (1)
9-15: Verify include paths for new dual_simplex headers (basis_updates.hpp).Clang reports
'dual_simplex/basis_updates.hpp' file not found. This is often a build include-path issue (vs. code). Please confirm the header exists and the target exportingcpp/src/dual_simplexheaders sets the correct include directories.cpp/src/dual_simplex/branch_and_bound.cpp (2)
302-378: Reduced-cost fixing: verify objective sense + guard infinite/negative gaps.
find_reduced_cost_fixings()usesabs_gap = upper_bound - root_objand applies bound tightening based on reduced costs. This typically depends on minimization/maximization conventions and requires finiteupper_bound. Please:
- Skip if
upper_boundisinf(or gap < 0).- Confirm reduced-cost sign conventions under objective scaling / maximization (
original_lp_.obj_scale < 0) are handled; otherwise bounds can tighten in the wrong direction.
24-25: OpenMP dependency: verify build flags + portability.Including
<omp.h>makes this TU depend on OpenMP headers being available in all build configurations. Please confirm CI/build images enable OpenMP (or guard the include and code with a compile-time flag).cpp/src/dual_simplex/cuts.cpp (3)
8-10: Verify include paths for dual_simplex/cuts.hpp (build config).Clang reports
'dual_simplex/cuts.hpp' file not found. Please confirm the header is exported and include directories are set correctly for this target.
86-103: Verify sparse_dot overload used here exists (pointer+length signature).
cut_orthogonality()callssparse_dot(ptr, ptr, len, ptr, ptr, len); in the provided context I only see vector-based overloads. If the pointer/len overload is defined elsewhere, great—otherwise this won’t compile.
15-55: Replace exit(1)/printf with error returns (core cut/basis mutation paths).This file is part of the solver’s mainline (cut generation + LP mutation).
exit(1)inside these routines is a hard blocker (kills host process, bypasses cleanup, breaks integration). Please convert to status returns (already usingi_treturn codes in many APIs) and route diagnostics viasettings.log.
As per coding guidelines, verify error propagation from CUDA/solver code to user-facing APIs is complete and mapped to meaningful error codes.Also applies to: 2015-2211, 2214-2356
⛔ Skipped due to learnings
Learnt from: CR Repo: NVIDIA/cuopt PR: 0 File: .github/.coderabbit_review_guide.md:0-0 Timestamp: 2025-11-25T10:20:49.822Z Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Verify error propagation from CUDA to user-facing APIs is complete; ensure CUDA errors are caught and mapped to meaningful user error codesLearnt from: CR Repo: NVIDIA/cuopt PR: 0 File: .github/.coderabbit_review_guide.md:0-0 Timestamp: 2025-11-25T10:20:49.822Z Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Validate algorithm correctness in optimization logic: simplex pivots, branch-and-bound decisions, routing heuristics, and constraint/objective handling must produce correct resultsLearnt from: CR Repo: NVIDIA/cuopt PR: 0 File: .github/.coderabbit_review_guide.md:0-0 Timestamp: 2025-11-25T10:20:49.822Z Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Validate correct initialization of variable bounds, constraint coefficients, and algorithm state before solving; ensure reset when transitioning between algorithm phases (presolve, simplex, diving, crossover)Learnt from: CR Repo: NVIDIA/cuopt PR: 0 File: .github/.coderabbit_review_guide.md:0-0 Timestamp: 2025-11-25T10:20:49.822Z Learning: Applies to **/*.{cu,cpp,hpp,h} : Avoid inappropriate use of exceptions in performance-critical GPU operation paths; prefer error codes or CUDA error checking for latency-sensitive codeLearnt from: CR Repo: NVIDIA/cuopt PR: 0 File: .github/.coderabbit_review_guide.md:0-0 Timestamp: 2025-11-25T10:20:49.822Z Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Ensure variables and constraints are accessed from the correct problem context (original vs presolve vs folded vs postsolve); verify index mapping consistency across problem transformationsLearnt from: CR Repo: NVIDIA/cuopt PR: 0 File: .github/.coderabbit_review_guide.md:0-0 Timestamp: 2025-11-25T10:20:49.822Z Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Refactor code duplication in solver components (3+ occurrences) into shared utilities; for GPU kernels, use templated device functions to avoid duplicationLearnt from: CR Repo: NVIDIA/cuopt PR: 0 File: .github/.coderabbit_review_guide.md:0-0 Timestamp: 2025-11-25T10:20:49.822Z Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Verify correct problem size checks before expensive GPU/CPU operations; prevent resource exhaustion on oversized problemsLearnt from: CR Repo: NVIDIA/cuopt PR: 0 File: .github/.coderabbit_review_guide.md:0-0 Timestamp: 2025-11-25T10:20:49.822Z Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Identify assertions with overly strict numerical tolerances that fail on legitimate degenerate/edge cases (near-zero pivots, singular matrices, empty problems)Learnt from: CR Repo: NVIDIA/cuopt PR: 0 File: .github/.coderabbit_review_guide.md:0-0 Timestamp: 2025-11-25T10:20:49.822Z Learning: Applies to **/*.{cu,cuh} : Every CUDA kernel launch and memory operation must have error checking with CUDA_CHECK or equivalent verificationLearnt from: CR Repo: NVIDIA/cuopt PR: 0 File: .github/.coderabbit_review_guide.md:0-0 Timestamp: 2025-11-25T10:20:49.822Z Learning: Applies to **/*.{cpp,hpp,h} : Check for unclosed file handles when reading MPS/QPS problem files; ensure RAII patterns or proper cleanup in exception paths
| num_integer_variables_ = 0; | ||
| for (i_t j = 0; j < original_lp_.num_cols; j++) { | ||
| if (var_types_[j] == variable_type_t::INTEGER) { | ||
| num_integer_variables_++; | ||
| } | ||
| } | ||
| printf("num_integer_variables %d\n", num_integer_variables_); | ||
|
|
||
| // Check slack | ||
| printf("slacks size %ld m %d\n", new_slacks_.size(), original_lp_.num_rows); | ||
| for (i_t slack : new_slacks_) { | ||
| const i_t col_start = original_lp_.A.col_start[slack]; | ||
| const i_t col_end = original_lp_.A.col_start[slack + 1]; | ||
| const i_t col_len = col_end - col_start; | ||
| if (col_len != 1) { | ||
| printf("Slack %d has %d nzs\n", slack, col_len); | ||
| exit(1); | ||
| } | ||
| const i_t i = original_lp_.A.i[col_start]; | ||
| const f_t x = original_lp_.A.x[col_start]; | ||
| if (std::abs(x) != 1.0) { | ||
| printf("Slack %d row %d has non-unit coefficient %e\n", slack, i, x); | ||
| exit(1); | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove or macro-gate debug prints, exit(1), and solution.dat I/O from solver flow.
These blocks (printf, exit(1), fopen("solution.dat"...) are currently on hot/production paths (constructor + solve). This is a release blocker: it breaks library behavior, can leak resources (exit bypasses cleanup), and adds non-deterministic filesystem coupling. Please gate behind #ifdef (or settings flag) and avoid exit() entirely (return error/status instead).
As per coding guidelines, check for unclosed file handles and ensure RAII patterns or proper cleanup in exception paths.
Also applies to: 604-630, 1648-1734
🤖 Prompt for AI Agents
In @cpp/src/dual_simplex/branch_and_bound.cpp around lines 238 - 263, The debug
stdout prints and hard exits in branch_and_bound.cpp (e.g., the
num_integer_variables_ count print, the slack verification printf/exit blocks
that iterate new_slacks_, and any fopen("solution.dat"...) I/O) must be removed
or wrapped in a compile-time or runtime debug gate (e.g., #ifdef DEBUG_PRINTS or
a SolverOptions.verbose/debug flag); replace exit(1) with returning an error
status or throwing a well-defined exception from the containing function (do not
terminate the process), and convert any ad-hoc FILE* usage to RAII
(std::ofstream or a small wrapper) ensuring proper close on all paths;
specifically change the slack checks inside the constructor/solve path to either
be omitted in release or to return a bool/error code (propagate up via the
function that owns new_slacks_ validation) and guard printing of
num_integer_variables_ behind the debug flag.
| // Get the basic list and nonbasic list from the vstatus | ||
| for (i_t j = 0; j < original_lp_.num_cols; j++) { | ||
| if (crossover_vstatus_[j] == variable_status_t::BASIC) { | ||
| basic_list.push_back(j); | ||
| } else { | ||
| nonbasic_list.push_back(j); | ||
| } | ||
| } | ||
| if (basic_list.size() != original_lp_.num_rows) { | ||
| printf("basic_list size %d != m %d\n", basic_list.size(), original_lp_.num_rows); | ||
| exit(1); | ||
| } | ||
| if (nonbasic_list.size() != original_lp_.num_cols - original_lp_.num_rows) { | ||
| printf("nonbasic_list size %d != n - m %d\n", nonbasic_list.size(), original_lp_.num_cols - original_lp_.num_rows); | ||
| exit(1); | ||
| } | ||
| root_crossover_settings.max_cut_passes = 3; | ||
| // Populate the basis_update from the crossover vstatus | ||
| basis_update.refactor_basis(original_lp_.A, | ||
| root_crossover_settings, | ||
| original_lp_.lower, | ||
| original_lp_.upper, | ||
| basic_list, | ||
| nonbasic_list, | ||
| crossover_vstatus_); | ||
|
|
||
| // Set the edge norms to a default value | ||
| edge_norms.resize(original_lp_.num_cols, -1.0); | ||
| set_uninitialized_steepest_edge_norms<i_t, f_t>(edge_norms); | ||
| printf("Using crossover solution\n"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix basic_list/nonbasic_list construction (currently broken due to missing clear/reserve).
In solve(), basic_list is constructed with size original_lp_.num_rows, then solve_root_relaxation() does basic_list.push_back(j) (same for nonbasic_list) and later asserts sizes. This will fail or produce incorrect basis data.
Proposed fix
- std::vector<i_t> basic_list(original_lp_.num_rows);
- std::vector<i_t> nonbasic_list;
+ std::vector<i_t> basic_list;
+ basic_list.reserve(original_lp_.num_rows);
+ std::vector<i_t> nonbasic_list;
+ nonbasic_list.reserve(original_lp_.num_cols - original_lp_.num_rows);And in solve_root_relaxation() before filling from crossover_vstatus_:
- // Get the basic list and nonbasic list from the vstatus
+ // Get the basic list and nonbasic list from the vstatus
+ basic_list.clear();
+ nonbasic_list.clear();
+ basic_list.reserve(original_lp_.num_rows);
+ nonbasic_list.reserve(original_lp_.num_cols - original_lp_.num_rows);
for (i_t j = 0; j < original_lp_.num_cols; j++) {
if (crossover_vstatus_[j] == variable_status_t::BASIC) {
basic_list.push_back(j);
} else {
nonbasic_list.push_back(j);
}
}Also applies to: 1556-1579
🤖 Prompt for AI Agents
In @cpp/src/dual_simplex/branch_and_bound.cpp around lines 1478 - 1507,
basic_list and nonbasic_list are pre-sized then filled with push_back in
solve_root_relaxation(), causing incorrect lengths; before the loop that reads
crossover_vstatus_ in solve_root_relaxation() (the block that iterates j and
push_back into basic_list/nonbasic_list) clear both vectors (or reinitialize
them to empty) and optionally reserve their expected capacities
(original_lp_.num_rows and original_lp_.num_cols - original_lp_.num_rows) so
push_back produces the correct sizes used later by basis_update.refactor_basis
and the subsequent size checks.
| f_t cut_pool_t<i_t, f_t>::cut_distance(i_t row, const std::vector<f_t>& x, f_t& cut_violation, f_t &cut_norm) | ||
| { | ||
| const i_t row_start = cut_storage_.row_start[row]; | ||
| const i_t row_end = cut_storage_.row_start[row + 1]; | ||
| f_t cut_x = 0.0; | ||
| f_t dot = 0.0; | ||
| for (i_t p = row_start; p < row_end; p++) { | ||
| const i_t j = cut_storage_.j[p]; | ||
| const f_t cut_coeff = cut_storage_.x[p]; | ||
| cut_x += cut_coeff * x[j]; | ||
| dot += cut_coeff * cut_coeff; | ||
| } | ||
| cut_violation = rhs_storage_[row] - cut_x; | ||
| cut_norm = std::sqrt(dot); | ||
| const f_t distance = cut_violation / cut_norm; | ||
| return distance; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guard divide-by-zero in cut_distance (cut_norm can be 0).
distance = cut_violation / cut_norm will explode if a cut row becomes all-zeros after squeezing/substitution (possible with aggressive coefficient filtering). Consider returning 0 (or -inf) when cut_norm <= eps.
As per coding guidelines, check numerical stability and prevent division by zero/near-zero.
🤖 Prompt for AI Agents
In @cpp/src/dual_simplex/cuts.cpp around lines 58 - 74, In cut_distance, guard
the division by zero when computing distance = cut_violation / cut_norm: after
computing dot and setting cut_norm = std::sqrt(dot) (in function cut_pool_t<i_t,
f_t>::cut_distance), check if cut_norm <= eps (use a small threshold like
std::numeric_limits<f_t>::epsilon() or 1e-12); if so set cut_norm = 0, set
cut_violation appropriately and return a safe value (e.g., 0.0) instead of
dividing, and ensure cut_violation and cut_norm are left consistent for callers;
reference cut_storage_, rhs_storage_, cut_violation, cut_norm and the
cut_distance function when making the change.
| template <typename i_t, typename f_t> | ||
| i_t cut_pool_t<i_t, f_t>::get_best_cuts(csr_matrix_t<i_t, f_t>& best_cuts, std::vector<f_t>& best_rhs, std::vector<cut_type_t>& best_cut_types) | ||
| { | ||
| best_cuts.m = 0; | ||
| best_cuts.n = original_vars_; | ||
| best_cuts.row_start.clear(); | ||
| best_cuts.j.clear(); | ||
| best_cuts.x.clear(); | ||
| best_cuts.row_start.reserve(scored_cuts_ + 1); | ||
| best_cuts.row_start.push_back(0); | ||
|
|
||
| for (i_t i: best_cuts_) { | ||
| sparse_vector_t<i_t, f_t> cut(cut_storage_, i); | ||
| cut.negate(); | ||
| best_cuts.append_row(cut); | ||
| //settings_.log.printf("Best cuts nz %d\n", best_cuts.row_start[best_cuts.m]); | ||
| best_rhs.push_back(-rhs_storage_[i]); | ||
| best_cut_types.push_back(cut_type_[i]); | ||
| } | ||
|
|
||
| return static_cast<i_t>(best_cuts_.size()); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
get_best_cuts must clear output vectors (best_rhs/best_cut_types).
get_best_cuts() resets best_cuts but does not clear best_rhs / best_cut_types, so repeated calls will accumulate stale entries.
Proposed fix
i_t cut_pool_t<i_t, f_t>::get_best_cuts(csr_matrix_t<i_t, f_t>& best_cuts,
std::vector<f_t>& best_rhs,
std::vector<cut_type_t>& best_cut_types)
{
+ best_rhs.clear();
+ best_cut_types.clear();
best_cuts.m = 0;
best_cuts.n = original_vars_;
...🤖 Prompt for AI Agents
In @cpp/src/dual_simplex/cuts.cpp around lines 175 - 196, get_best_cuts
currently resets best_cuts but never clears the output vectors best_rhs and
best_cut_types, causing repeated calls to accumulate stale entries; fix by
clearing best_rhs and best_cut_types at the start of cut_pool_t<i_t,
f_t>::get_best_cuts (and optionally reserve capacity based on
best_cuts_.size()), then proceed to push_back values as before (use rhs_storage_
and cut_type_ references as already used).
| std::vector<i_t> slack_map(lp.num_rows); | ||
| for (i_t slack : new_slacks) { | ||
| const i_t col_start = lp.A.col_start[slack]; | ||
| const i_t col_end = lp.A.col_start[slack + 1]; | ||
| const i_t col_len = col_end - col_start; | ||
| if (col_len != 1) { | ||
| printf("Generate MIR cuts: Slack %d has %d nzs in column\n", slack, col_len); | ||
| exit(1); | ||
| } | ||
| const i_t i = lp.A.i[col_start]; | ||
| slack_map[i] = slack; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Initialize slack_map and validate every row has a slack before use.
std::vector<i_t> slack_map(lp.num_rows); default-initializes to 0. Any row not explicitly mapped will silently treat slack variable as column 0 (and then read xstar[slack]). This will corrupt MIR scoring/cut construction.
Suggestion: initialize with -1 and skip rows with slack_map[i] == -1 (or assert if invariant is “every row has a slack”).
Also applies to: 635-639
| std::vector<i_t> check(num_vars_, 0); | ||
| for (i_t p = 0; p < cut.i.size(); p++) | ||
| { | ||
| if (check[cut.i[p]] != 0) | ||
| { | ||
| printf("repeated index in generated cut\n"); | ||
| exit(1); | ||
| } | ||
| check[cut.i[p]] == 1; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix == vs = bug: repeated-index check is currently a no-op.
Both checks do check[cut.i[p]] == 1; which does not mutate the vector (so duplicates won’t be detected).
Proposed fix
- check[cut.i[p]] == 1;
+ check[cut.i[p]] = 1;Also applies to: 1736-1746
🤖 Prompt for AI Agents
In @cpp/src/dual_simplex/cuts.cpp around lines 1528 - 1537, The repeated-index
check in cuts.cpp is a no-op because it uses the equality operator instead of
assignment; in the loop that iterates over cut.i (the block using
std::vector<i_t> check and for (i_t p = 0; p < cut.i.size(); p++)), replace the
statement check[cut.i[p]] == 1; with an assignment so the check array is updated
(i.e., set the slot to 1 after confirming it was zero). Apply the same fix to
the identical block referenced around lines 1736-1746 so duplicates are actually
recorded and detected.
| template <typename i_t, typename f_t> | ||
| f_t minimum_violation(const csr_matrix_t<i_t, f_t>& C, | ||
| const std::vector<f_t>& cut_rhs, | ||
| const std::vector<f_t>& x) | ||
| { | ||
| // Check to see that this is a cut i.e C*x > d | ||
| std::vector<f_t> Cx(C.m); | ||
| csc_matrix_t<i_t, f_t> C_col(C.m, C.n, 0); | ||
| C.to_compressed_col(C_col); | ||
| matrix_vector_multiply(C_col, 1.0, x, 0.0, Cx); | ||
| f_t min_cut_violation = inf; | ||
| for (i_t k = 0; k < Cx.size(); k++) { | ||
| if (Cx[k] <= cut_rhs[k]) { | ||
| printf("C*x <= d for cut %d. C*x %e rhs %e\n", k, Cx[k], cut_rhs[k]); | ||
| exit(1); | ||
| } | ||
| min_cut_violation = std::min(min_cut_violation, Cx[k] - cut_rhs[k]); | ||
| } | ||
| return min_cut_violation; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove exit(1) / printf from cuts.hpp (library header).
minimum_violation() calls printf(...) then exit(1) when a cut isn’t violated. That’s not acceptable for a solver library (kills the host process, bypasses cleanup, breaks callers). Prefer returning an error code / std::optional<f_t> / std::numeric_limits<f_t>::quiet_NaN() and letting the caller decide.
As per coding guidelines, validate algorithm correctness and ensure error propagation is mapped to meaningful user error codes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@cpp/src/dual_simplex/branch_and_bound.cpp`:
- Around line 302-378: find_reduced_cost_fixings is currently diagnostic-only
and expensive (allocates/copies vectors, runs bounds_strengthening) while
printing via printf and never applying new_lp bounds back to the node/global LP;
either gate the whole body behind a settings flag (e.g.
settings_.enable_reduced_cost_fixings) and replace printf calls with
settings_.log.debug, or if the intent is to tighten the search, apply the
tightened bounds (new_lp.lower/new_lp.upper) to the node/global state
(original_lp_ or the node data structure) after successful bounds_strengthening,
avoid unnecessary O(n) copies when the flag is disabled by returning early
before allocations, and preserve mutex semantics around any mutation of
original_lp_ (use mutex_original_lp_ only when writing back).
- Around line 302-378: find_reduced_cost_fixings() acquires mutex_original_lp_
but is sometimes called while mutex_upper_ is held (from
add_feasible_solution()/repair_heuristic_solutions()), creating a lock-order
inversion with set_new_solution() which locks original then upper; to fix,
ensure callers do not hold mutex_upper_ when invoking
find_reduced_cost_fixings(): release mutex_upper_ (or restructure code so
mutex_original_lp_ is always locked before mutex_upper_) before calling
find_reduced_cost_fixings(), and update the other call sites mentioned (around
the ranges referenced) to follow the same rule so the global lock order is
consistent (prefer locking mutex_original_lp_ before mutex_upper_ or avoid
nested locks by releasing upper before calling).
In `@cpp/src/dual_simplex/cuts.cpp`:
- Around line 2030-2226: The add_cuts function uses exit(1) in several
runtime-error paths and fails to resize edge_norms after adding slack columns;
change all non-#ifdef exit(1) calls in add_cuts (e.g., the append_rows failure,
the basic_list out-of-bounds check, and the C_B_nz mismatch) to return a
non-zero error code (e.g., return -1) after logging the error so the caller can
handle it; after lp.num_cols is increased (after lp.num_cols += p) resize and
initialize edge_norms to the new length (edge_norms.resize(lp.num_cols) and set
the new entries to a safe default or recompute their values consistent with how
edge_norms is used downstream); keep the CHECK_BASIS debug exits conditional,
and finally ensure the call basis_update.append_cuts(C_B) leaves factorization
state consistent—if the basis_update API requires an explicit
invalidate/recompute step, call that (or set the appropriate flag) immediately
after append_cuts so subsequent LU operations use a correct factorization.
♻️ Duplicate comments (11)
cpp/src/dual_simplex/branch_and_bound.cpp (6)
1403-1507: Fixbasic_list/nonbasic_listconstruction: mustclear()beforepush_back(); neverexit(1)on mismatch.
Currently the vectors can already be sized (caller initializesbasic_list(m)), sopush_backproduces wrong sizes and triggers the hard exits.Proposed fix
@@ // Get the basic list and nonbasic list from the vstatus + basic_list.clear(); + nonbasic_list.clear(); + basic_list.reserve(original_lp_.num_rows); + nonbasic_list.reserve(original_lp_.num_cols - original_lp_.num_rows); for (i_t j = 0; j < original_lp_.num_cols; j++) { @@ } - if (basic_list.size() != original_lp_.num_rows) { - printf("basic_list size %d != m %d\n", basic_list.size(), original_lp_.num_rows); - exit(1); - } - if (nonbasic_list.size() != original_lp_.num_cols - original_lp_.num_rows) { - printf("nonbasic_list size %d != n - m %d\n", nonbasic_list.size(), original_lp_.num_cols - original_lp_.num_rows); - exit(1); - } + assert(basic_list.size() == static_cast<size_t>(original_lp_.num_rows)); + assert(nonbasic_list.size() == + static_cast<size_t>(original_lp_.num_cols - original_lp_.num_rows));
238-263: Remove/gate constructorprintf(...)+exit(1)slack validation (library must not terminate process).
This is on a hot/production path and will kill host processes on benign inputs. Please convert to debug-only assertions/logging and propagate failure via status (or move validation to a non-ctor init that can return an error).
604-630: Remove or macro-gatesolution.datwrite; fix FILE RAII +size_tprintf format.
This introduces filesystem coupling in solver runs and uses%20xforsize_t(UB on many platforms). Also,#if 1forces it on.
1556-1572: Initialize rootbasic_list/nonbasic_listconsistently with the expected fill pattern.
Given the crossover path usespush_back, the caller should not pre-sizebasic_list(original_lp_.num_rows). Preferreserve+ empty vectors.Proposed fix
- std::vector<i_t> basic_list(original_lp_.num_rows); - std::vector<i_t> nonbasic_list; + std::vector<i_t> basic_list; + basic_list.reserve(original_lp_.num_rows); + std::vector<i_t> nonbasic_list; + nonbasic_list.reserve(original_lp_.num_cols - original_lp_.num_rows);Also applies to: 1573-1580
1647-1976: Cut loop still contains multiple unconditionalexit(1)+ forced solution.dat read path (#if 1).
These should be debug-only and/or return a solver status instead (NUMERICAL/ERROR), especially around: bad cuts matrix, saved-solution cut violation, add_cuts failure, bound strengthening failure, non-optimal cut LP status.
747-756: Gate or remove debugprintfinsolve_node(); avoid exact float equality checks for “fixed to bounds”.
The prints will spam in multi-threaded search. Alsoleaf_problem.lower[j] == leaf_problem.upper[j]is brittle for floating bounds; considerabs(ub-lb) <= eps.Also applies to: 869-884
cpp/src/dual_simplex/cuts.cpp (5)
58-74: Guard divide-by-zero incut_distance()/cut_orthogonality()(NaNs will poison scoring).
cut_norm(ornorm_i*norm_j) can be 0 for degenerate/empty cuts after squeezing/substitution.Proposed fix
@@ cut_violation = rhs_storage_[row] - cut_x; cut_norm = std::sqrt(dot); - const f_t distance = cut_violation / cut_norm; + constexpr f_t kMinNorm = 1e-12; + if (!(cut_norm > kMinNorm)) { + cut_norm = 0.0; + cut_violation = 0.0; + return 0.0; + } + const f_t distance = cut_violation / cut_norm; return distance; @@ f_t norm_i = cut_norms_[i]; f_t norm_j = cut_norms_[j]; - return 1.0 - std::abs(dot) / (norm_i * norm_j); + const f_t denom = norm_i * norm_j; + constexpr f_t kMinDenom = 1e-12; + if (!(denom > kMinDenom)) { return 1.0; } + return 1.0 - std::abs(dot) / denom;Also applies to: 87-102
175-196:get_best_cuts()must clearbest_rhs/best_cut_types(stale accumulation across calls).Proposed fix
i_t cut_pool_t<i_t, f_t>::get_best_cuts(csr_matrix_t<i_t, f_t>& best_cuts, std::vector<f_t>& best_rhs, std::vector<cut_type_t>& best_cut_types) { + best_rhs.clear(); + best_cut_types.clear(); best_cuts.m = 0;
613-660: Fixslack_mapdefault init: rows without slacks silently use column 0 as “slack”.
std::vector<i_t> slack_map(lp.num_rows);leaves unmapped rows at 0; thenxstar[slack]readsxstar[0]and corrupts scoring/cut building.Proposed fix
- std::vector<i_t> slack_map(lp.num_rows); + std::vector<i_t> slack_map(lp.num_rows, static_cast<i_t>(-1)); @@ const i_t i = lp.A.i[col_start]; slack_map[i] = slack; } @@ - const i_t slack = slack_map[i]; + const i_t slack = slack_map[i]; + if (slack == static_cast<i_t>(-1)) { + score[i] = 0.0; + continue; + } const f_t slack_value = xstar[slack];
1543-1552: Bug: repeated-index “check” is a no-op (==instead of=).
Duplicates will not be detected.Proposed fix
- check[cut.i[p]] == 1; + check[cut.i[p]] = 1;Also applies to: 1751-1761
618-621: Replace remainingexit(1)with error propagation (and gate validation/prints).
This is library code; it must not terminate the process. Prefer returning non-zero error codes (you already usei_tstatuses) and let the caller map toNUMERICAL/failure.Also applies to: 913-915, 1151-1160, 1206-1216, 1291-1302, 1368-1369, 1523-1534, 1572-1585, 1732-1744, 1807-1815, 2073-2077, 2111-2116, 2213-2217, 2255-2259
🧹 Nitpick comments (1)
cpp/src/dual_simplex/cuts.cpp (1)
409-448: Knapsack routines: guard division-by-zero and DP memory blow-ups.
ratios[i] = values[i] / weights[i]can divide by 0 if a row has 0 coeffs.- DP alloc is
(n+1)*(sum_value+1); even withsum_value<=10000, largencan explode memory.Also applies to: 451-552
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cpp
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{cu,cuh,cpp,hpp,h}
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
**/*.{cu,cuh,cpp,hpp,h}: Track GPU device memory allocations and deallocations to prevent memory leaks; ensure cudaMalloc/cudaFree balance and cleanup of streams/events
Validate algorithm correctness in optimization logic: simplex pivots, branch-and-bound decisions, routing heuristics, and constraint/objective handling must produce correct results
Check numerical stability: prevent overflow/underflow, precision loss, division by zero/near-zero, and use epsilon comparisons for floating-point equality checks
Validate correct initialization of variable bounds, constraint coefficients, and algorithm state before solving; ensure reset when transitioning between algorithm phases (presolve, simplex, diving, crossover)
Ensure variables and constraints are accessed from the correct problem context (original vs presolve vs folded vs postsolve); verify index mapping consistency across problem transformations
For concurrent CUDA operations (barriers, async operations), explicitly create and manage dedicated streams instead of reusing the default stream; document stream lifecycle
Eliminate unnecessary host-device synchronization (cudaDeviceSynchronize) in hot paths that blocks GPU pipeline; use streams and events for async execution
Assess algorithmic complexity for large-scale problems (millions of variables/constraints); ensure O(n log n) or better complexity, not O(n²) or worse
Verify correct problem size checks before expensive GPU/CPU operations; prevent resource exhaustion on oversized problems
Identify assertions with overly strict numerical tolerances that fail on legitimate degenerate/edge cases (near-zero pivots, singular matrices, empty problems)
Ensure race conditions are absent in multi-GPU code and multi-threaded server implementations; verify proper synchronization of shared state
Refactor code duplication in solver components (3+ occurrences) into shared utilities; for GPU kernels, use templated device functions to avoid duplication
Check that hard-coded GPU de...
Files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cpp
**/*.{cpp,hpp,h}
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
**/*.{cpp,hpp,h}: Check for unclosed file handles when reading MPS/QPS problem files; ensure RAII patterns or proper cleanup in exception paths
Validate input sanitization to prevent buffer overflows and resource exhaustion attacks; avoid unsafe deserialization of problem files
Prevent thread-unsafe use of global and static variables; use proper mutex/synchronization in server code accessing shared solver state
Files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cpp
**/*.{cu,cpp,hpp,h}
📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)
Avoid inappropriate use of exceptions in performance-critical GPU operation paths; prefer error codes or CUDA error checking for latency-sensitive code
Files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cpp
🧠 Learnings (22)
📓 Common learnings
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Validate algorithm correctness in optimization logic: simplex pivots, branch-and-bound decisions, routing heuristics, and constraint/objective handling must produce correct results
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Reduce tight coupling between solver components (presolve, simplex, basis, barrier); increase modularity and reusability of optimization algorithms
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*test*.{cpp,cu,py} : Add tests for algorithm phase transitions: verify correct initialization of bounds and state when transitioning from presolve to simplex to diving to crossover
Learnt from: chris-maes
Repo: NVIDIA/cuopt PR: 500
File: cpp/src/dual_simplex/scaling.cpp:68-76
Timestamp: 2025-12-04T04:11:12.640Z
Learning: In the cuOPT dual simplex solver, CSR/CSC matrices (including the quadratic objective matrix Q) are required to have valid dimensions and indices by construction. Runtime bounds checking in performance-critical paths like matrix scaling is avoided to prevent slowdowns. Validation is performed via debug-only check_matrix() calls wrapped in `#ifdef` CHECK_MATRIX.
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Validate correct initialization of variable bounds, constraint coefficients, and algorithm state before solving; ensure reset when transitioning between algorithm phases (presolve, simplex, diving, crossover)
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Validate algorithm correctness in optimization logic: simplex pivots, branch-and-bound decisions, routing heuristics, and constraint/objective handling must produce correct results
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Reduce tight coupling between solver components (presolve, simplex, basis, barrier); increase modularity and reusability of optimization algorithms
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Validate correct initialization of variable bounds, constraint coefficients, and algorithm state before solving; ensure reset when transitioning between algorithm phases (presolve, simplex, diving, crossover)
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*test*.{cpp,cu,py} : Add tests for algorithm phase transitions: verify correct initialization of bounds and state when transitioning from presolve to simplex to diving to crossover
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Ensure variables and constraints are accessed from the correct problem context (original vs presolve vs folded vs postsolve); verify index mapping consistency across problem transformations
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Assess algorithmic complexity for large-scale problems (millions of variables/constraints); ensure O(n log n) or better complexity, not O(n²) or worse
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Refactor code duplication in solver components (3+ occurrences) into shared utilities; for GPU kernels, use templated device functions to avoid duplication
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Identify assertions with overly strict numerical tolerances that fail on legitimate degenerate/edge cases (near-zero pivots, singular matrices, empty problems)
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-12-04T20:09:09.264Z
Learnt from: chris-maes
Repo: NVIDIA/cuopt PR: 602
File: cpp/src/linear_programming/solve.cu:732-742
Timestamp: 2025-12-04T20:09:09.264Z
Learning: In cpp/src/linear_programming/solve.cu, the barrier solver does not currently return INFEASIBLE or UNBOUNDED status. It only returns OPTIMAL, TIME_LIMIT, NUMERICAL_ISSUES, or CONCURRENT_LIMIT.
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*test*.{cpp,cu,py} : Test with free variables, singleton problems, and extreme problem dimensions near resource limits to validate edge case handling
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cpp
📚 Learning: 2025-10-22T14:25:22.899Z
Learnt from: aliceb-nv
Repo: NVIDIA/cuopt PR: 527
File: cpp/src/mip/diversity/lns/rins.cu:167-175
Timestamp: 2025-10-22T14:25:22.899Z
Learning: In MIP (Mixed Integer Programming) problems in the cuOPT codebase, `n_integer_vars == 0` is impossible by definition—MIP problems must have at least one integer variable. If there are no integer variables, it would be a pure Linear Programming (LP) problem, not a MIP problem.
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cpp,hpp,h} : Check for unclosed file handles when reading MPS/QPS problem files; ensure RAII patterns or proper cleanup in exception paths
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Verify correct problem size checks before expensive GPU/CPU operations; prevent resource exhaustion on oversized problems
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*test*.{cpp,cu,py} : Write tests validating numerical correctness of optimization results (not just 'runs without error'); test degenerate cases (infeasible, unbounded, empty, singleton problems)
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-12-03T23:29:26.391Z
Learnt from: chris-maes
Repo: NVIDIA/cuopt PR: 500
File: cpp/src/dual_simplex/sparse_matrix.cpp:519-524
Timestamp: 2025-12-03T23:29:26.391Z
Learning: In cpp/src/dual_simplex/sparse_matrix.cpp, the check_matrix() function is debug/diagnostic code (wrapped in `#ifdef` CHECK_MATRIX) that intentionally prints errors without necessarily returning early. The return codes from this debug code are not actively checked; the purpose is to print all validation errors in one pass for better diagnostics.
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Check numerical stability: prevent overflow/underflow, precision loss, division by zero/near-zero, and use epsilon comparisons for floating-point equality checks
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cpp,hpp,h} : Avoid inappropriate use of exceptions in performance-critical GPU operation paths; prefer error codes or CUDA error checking for latency-sensitive code
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-12-04T04:11:12.640Z
Learnt from: chris-maes
Repo: NVIDIA/cuopt PR: 500
File: cpp/src/dual_simplex/scaling.cpp:68-76
Timestamp: 2025-12-04T04:11:12.640Z
Learning: In the cuOPT dual simplex solver, CSR/CSC matrices (including the quadratic objective matrix Q) are required to have valid dimensions and indices by construction. Runtime bounds checking in performance-critical paths like matrix scaling is avoided to prevent slowdowns. Validation is performed via debug-only check_matrix() calls wrapped in `#ifdef` CHECK_MATRIX.
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Verify error propagation from CUDA to user-facing APIs is complete; ensure CUDA errors are caught and mapped to meaningful user error codes
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cppcpp/src/dual_simplex/cuts.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cpp,hpp,h} : Prevent thread-unsafe use of global and static variables; use proper mutex/synchronization in server code accessing shared solver state
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cpp,hpp,h} : Validate input sanitization to prevent buffer overflows and resource exhaustion attacks; avoid unsafe deserialization of problem files
Applied to files:
cpp/src/dual_simplex/branch_and_bound.cpp
🧬 Code graph analysis (2)
cpp/src/dual_simplex/branch_and_bound.cpp (8)
cpp/src/dual_simplex/presolve.hpp (1)
convert_user_problem(112-116)cpp/src/dual_simplex/bounds_strengthening.hpp (1)
lower_bounds(23-25)cpp/src/dual_simplex/solve.hpp (4)
compute_objective(34-34)solve_linear_program_with_advanced_basis(53-62)compute_user_objective(37-37)compute_user_objective(40-40)cpp/src/dual_simplex/solve.cpp (13)
compute_objective(77-86)compute_objective(77-77)compute_objective(659-660)basic_list(114-114)solve_linear_program_with_advanced_basis(129-295)solve_linear_program_with_advanced_basis(129-138)solve_linear_program_with_advanced_basis(675-684)compute_user_objective(89-94)compute_user_objective(89-89)compute_user_objective(97-101)compute_user_objective(97-97)compute_user_objective(662-663)compute_user_objective(665-665)cpp/src/dual_simplex/phase2.cpp (7)
residual(1170-1170)residual(1476-1476)residual(2936-2936)basic_list(2268-2268)dual_phase2_with_advanced_basis(2289-3183)dual_phase2_with_advanced_basis(2289-2301)dual_phase2_with_advanced_basis(3198-3211)cpp/src/dual_simplex/vector_math.hpp (2)
vector_norm_inf(17-26)vector_norm_inf(17-17)cpp/src/dual_simplex/cuts.cpp (6)
add_cuts(2030-2226)add_cuts(2030-2040)add_cuts(2381-2391)remove_cuts(2229-2371)remove_cuts(2229-2241)remove_cuts(2394-2406)cpp/src/dual_simplex/phase2.hpp (1)
dual_phase2_with_advanced_basis(61-73)
cpp/src/dual_simplex/cuts.cpp (12)
cpp/src/dual_simplex/cuts.hpp (9)
cut_type(83-83)cut(330-332)cut_storage_(94-94)row(97-97)row(98-98)i(99-99)x_relax(85-85)a(324-328)a(334-340)cpp/src/dual_simplex/crossover.cpp (5)
p(500-500)p(779-779)p(1133-1133)basic_list(1120-1120)r(650-650)cpp/src/dual_simplex/sparse_vector.hpp (5)
p(40-40)p(42-42)index(52-52)x(44-44)y(54-54)cpp/src/dual_simplex/sparse_matrix.hpp (9)
j(65-65)j(115-126)row(89-89)x(72-72)x(76-76)i(163-174)sparse_dot(233-236)nz(80-80)add(226-230)cpp/src/dual_simplex/sparse_vector.cpp (6)
dot(125-133)dot(125-125)sparse_dot(136-157)sparse_dot(136-136)sort(160-204)sort(160-160)cpp/tests/dual_simplex/unit_tests/solve.cpp (1)
solution(156-156)cpp/src/dual_simplex/presolve.cpp (2)
objective(83-83)objective(239-239)cpp/src/dual_simplex/bound_flipping_ratio_test.cpp (1)
ratios(121-121)cpp/src/dual_simplex/branch_and_bound.cpp (3)
basic_list(1033-1033)basic_list(1243-1243)basic_list(1312-1312)cpp/src/dual_simplex/solve.cpp (1)
basic_list(114-114)cpp/src/dual_simplex/basis_solves.cpp (1)
slack_map(630-630)cpp/src/dual_simplex/pseudo_costs.cpp (2)
score(299-299)score(369-369)
🪛 Clang (14.0.6)
cpp/src/dual_simplex/cuts.cpp
[error] 8-8: 'dual_simplex/cuts.hpp' file not found
(clang-diagnostic-error)
🪛 Cppcheck (2.19.0)
cpp/src/dual_simplex/branch_and_bound.cpp
[error] 617-617: Resource leak
(resourceLeak)
🔇 Additional comments (1)
cpp/src/dual_simplex/cuts.cpp (1)
8-10: The header filedual_simplex/cuts.hppexists and the include path is properly configured in the CMakeLists.txt (cpp/srcis added as a PRIVATE include directory for the cuopt target at line 252-264 of cpp/CMakeLists.txt). The include statement#include <dual_simplex/cuts.hpp>is correct and should resolve without errors.Likely an incorrect or invalid review comment.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| template <typename i_t, typename f_t> | ||
| void branch_and_bound_t<i_t, f_t>::find_reduced_cost_fixings(f_t upper_bound) | ||
| { | ||
| printf("Finding reduced cost fixings\n"); | ||
| mutex_original_lp_.lock(); | ||
| std::vector<f_t> reduced_costs = root_relax_soln_.z; | ||
| std::vector<f_t> lower_bounds = original_lp_.lower; | ||
| std::vector<f_t> upper_bounds = original_lp_.upper; | ||
| std::vector<bool> bounds_changed(original_lp_.num_cols, false); | ||
| const f_t root_obj = compute_objective(original_lp_, root_relax_soln_.x); | ||
| const f_t threshold = 1e-3; | ||
| const f_t weaken = 1e-5; | ||
| i_t num_improved = 0; | ||
| i_t num_fixed = 0; | ||
| for (i_t j = 0; j < original_lp_.num_cols; j++) { | ||
| //printf("Variable %d type %d reduced cost %e\n", j, var_types_[j], reduced_costs[j]); | ||
| if (std::abs(reduced_costs[j]) > threshold) { | ||
| const f_t lower_j = original_lp_.lower[j]; | ||
| const f_t upper_j = original_lp_.upper[j]; | ||
| const f_t abs_gap = upper_bound - root_obj; | ||
| f_t reduced_cost_upper_bound = upper_j; | ||
| f_t reduced_cost_lower_bound = lower_j; | ||
| if (lower_j > -inf && reduced_costs[j] > 0) | ||
| { | ||
| const f_t new_upper_bound = lower_j + abs_gap/reduced_costs[j]; | ||
| reduced_cost_upper_bound = var_types_[j] == variable_type_t::INTEGER | ||
| ? std::floor(new_upper_bound + weaken) | ||
| : new_upper_bound; | ||
| if (reduced_cost_upper_bound < upper_j) { | ||
| //printf("Improved upper bound for variable %d from %e to %e (%e)\n", j, upper_j, reduced_cost_upper_bound, new_upper_bound); | ||
| num_improved++; | ||
| upper_bounds[j] = reduced_cost_upper_bound; | ||
| bounds_changed[j] = true; | ||
| } | ||
| } | ||
| if (upper_j < inf && reduced_costs[j] < 0) | ||
| { | ||
| const f_t new_lower_bound = upper_j + abs_gap/reduced_costs[j]; | ||
| reduced_cost_lower_bound = var_types_[j] == variable_type_t::INTEGER | ||
| ? std::ceil(new_lower_bound - weaken) | ||
| : new_lower_bound; | ||
| if (reduced_cost_lower_bound > lower_j) { | ||
| //printf("Improved lower bound for variable %d from %e to %e (%e)\n", j, lower_j, reduced_cost_lower_bound, new_lower_bound); | ||
| num_improved++; | ||
| lower_bounds[j] = reduced_cost_lower_bound; | ||
| bounds_changed[j] = true; | ||
| } | ||
| } | ||
| if (var_types_[j] == variable_type_t::INTEGER && reduced_cost_upper_bound <= reduced_cost_lower_bound) | ||
| { | ||
| num_fixed++; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| printf("Reduced costs: Found %d improved bounds and %d fixed variables (%.1f%%)\n", num_improved, num_fixed, 100.0*static_cast<f_t>(num_fixed)/static_cast<f_t>(num_integer_variables_)); | ||
|
|
||
| if (num_improved > 0) { | ||
| lp_problem_t<i_t, f_t> new_lp = original_lp_; | ||
| new_lp.lower = lower_bounds; | ||
| new_lp.upper = upper_bounds; | ||
| std::vector<char> row_sense; | ||
| csr_matrix_t<i_t, f_t> Arow(1, 1, 1); | ||
| original_lp_.A.to_compressed_row(Arow); | ||
| bounds_strengthening_t<i_t, f_t> node_presolve(new_lp, Arow, row_sense, var_types_); | ||
| bool feasible = node_presolve.bounds_strengthening(new_lp.lower, new_lp.upper, settings_); | ||
|
|
||
| num_improved = 0; | ||
| for (i_t j = 0; j < original_lp_.num_cols; j++) { | ||
| if (new_lp.lower[j] > original_lp_.lower[j]) { num_improved++; } | ||
| if (new_lp.upper[j] < original_lp_.upper[j]) { num_improved++; } | ||
| } | ||
| printf("Bound strengthening: Found %d improved bounds\n", num_improved); | ||
| } | ||
|
|
||
| mutex_original_lp_.unlock(); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
find_reduced_cost_fixings() looks diagnostic-only and is very expensive as called (copies O(n) vectors + bound strengthening).
If this is meant to actually tighten the search, the computed new_lp.lower/upper never feed back into the node/global bounds. If it’s debug/experimental, please gate behind a settings flag and replace printf with settings_.log.debug.
🤖 Prompt for AI Agents
In `@cpp/src/dual_simplex/branch_and_bound.cpp` around lines 302 - 378,
find_reduced_cost_fixings is currently diagnostic-only and expensive
(allocates/copies vectors, runs bounds_strengthening) while printing via printf
and never applying new_lp bounds back to the node/global LP; either gate the
whole body behind a settings flag (e.g. settings_.enable_reduced_cost_fixings)
and replace printf calls with settings_.log.debug, or if the intent is to
tighten the search, apply the tightened bounds (new_lp.lower/new_lp.upper) to
the node/global state (original_lp_ or the node data structure) after successful
bounds_strengthening, avoid unnecessary O(n) copies when the flag is disabled by
returning early before allocations, and preserve mutex semantics around any
mutation of original_lp_ (use mutex_original_lp_ only when writing back).
Fix lock-order inversion: potential deadlock between mutex_upper_ and mutex_original_lp_.
add_feasible_solution() / repair_heuristic_solutions() call find_reduced_cost_fixings() while holding mutex_upper_, and find_reduced_cost_fixings() locks mutex_original_lp_ (upper→original). Meanwhile set_new_solution() locks original then later locks upper (original→upper). This is a classic deadlock cycle.
Proposed fix (avoid calling while holding upper lock)
@@
mutex_upper_.lock();
if (leaf_objective < upper_bound_) {
@@
- find_reduced_cost_fixings(upper_bound_);
+ const f_t ub_snapshot = upper_bound_;
send_solution = true;
}
@@
mutex_upper_.unlock();
+
+ if (send_solution) {
+ find_reduced_cost_fixings(ub_snapshot);
+ }@@
if (repaired_obj < upper_bound_) {
@@
- find_reduced_cost_fixings(obj);
+ const f_t ub_snapshot = upper_bound_;
if (settings_.solution_callback != nullptr) {
@@
}
+ mutex_upper_.unlock();
+ find_reduced_cost_fixings(ub_snapshot);
+ continue;
}
@@
- mutex_upper_.unlock();
+ mutex_upper_.unlock();Also applies to: 536-563, 678-708
🤖 Prompt for AI Agents
In `@cpp/src/dual_simplex/branch_and_bound.cpp` around lines 302 - 378,
find_reduced_cost_fixings() acquires mutex_original_lp_ but is sometimes called
while mutex_upper_ is held (from
add_feasible_solution()/repair_heuristic_solutions()), creating a lock-order
inversion with set_new_solution() which locks original then upper; to fix,
ensure callers do not hold mutex_upper_ when invoking
find_reduced_cost_fixings(): release mutex_upper_ (or restructure code so
mutex_original_lp_ is always locked before mutex_upper_) before calling
find_reduced_cost_fixings(), and update the other call sites mentioned (around
the ranges referenced) to follow the same rule so the global lock order is
consistent (prefer locking mutex_original_lp_ before mutex_upper_ or avoid
nested locks by releasing upper before calling).
| i_t add_cuts(const simplex_solver_settings_t<i_t, f_t>& settings, | ||
| const csr_matrix_t<i_t, f_t>& cuts, | ||
| const std::vector<f_t>& cut_rhs, | ||
| lp_problem_t<i_t, f_t>& lp, | ||
| std::vector<i_t>& new_slacks, | ||
| lp_solution_t<i_t, f_t>& solution, | ||
| basis_update_mpf_t<i_t, f_t>& basis_update, | ||
| std::vector<i_t>& basic_list, | ||
| std::vector<i_t>& nonbasic_list, | ||
| std::vector<variable_status_t>& vstatus, | ||
| std::vector<f_t>& edge_norms) | ||
|
|
||
| { | ||
| // Given a set of cuts: C*x <= d that are currently violated | ||
| // by the current solution x* (i.e. C*x* > d), this function | ||
| // adds the cuts into the LP and solves again. | ||
|
|
||
| #ifdef CHECK_BASIS | ||
| { | ||
| csc_matrix_t<i_t, f_t> Btest(lp.num_rows, lp.num_rows, 1); | ||
| basis_update.multiply_lu(Btest); | ||
| csc_matrix_t<i_t, f_t> B(lp.num_rows, lp.num_rows, 1); | ||
| form_b(lp.A, basic_list, B); | ||
| csc_matrix_t<i_t, f_t> Diff(lp.num_rows, lp.num_rows, 1); | ||
| add(Btest, B, 1.0, -1.0, Diff); | ||
| const f_t err = Diff.norm1(); | ||
| settings.log.printf("Before || B - L*U || %e\n", err); | ||
| if (err > 1e-6) { exit(1); } | ||
| } | ||
| #endif | ||
|
|
||
| const i_t p = cuts.m; | ||
| if (cut_rhs.size() != static_cast<size_t>(p)) { | ||
| settings.log.printf("cut_rhs must have the same number of rows as cuts\n"); | ||
| return -1; | ||
| } | ||
| settings.log.debug("Number of cuts %d\n", p); | ||
| settings.log.debug("Original lp rows %d\n", lp.num_rows); | ||
| settings.log.debug("Original lp cols %d\n", lp.num_cols); | ||
|
|
||
| csr_matrix_t<i_t, f_t> new_A_row(lp.num_rows, lp.num_cols, 1); | ||
| lp.A.to_compressed_row(new_A_row); | ||
|
|
||
| i_t append_status = new_A_row.append_rows(cuts); | ||
| if (append_status != 0) { | ||
| settings.log.printf("append_rows error: %d\n", append_status); | ||
| exit(1); | ||
| } | ||
|
|
||
| csc_matrix_t<i_t, f_t> new_A_col(lp.num_rows + p, lp.num_cols, 1); | ||
| new_A_row.to_compressed_col(new_A_col); | ||
|
|
||
| // Add in slacks variables for the new rows | ||
| lp.lower.resize(lp.num_cols + p); | ||
| lp.upper.resize(lp.num_cols + p); | ||
| lp.objective.resize(lp.num_cols + p); | ||
| i_t nz = new_A_col.col_start[lp.num_cols]; | ||
| new_A_col.col_start.resize(lp.num_cols + p + 1); | ||
| new_A_col.i.resize(nz + p); | ||
| new_A_col.x.resize(nz + p); | ||
| i_t k = lp.num_rows; | ||
| for (i_t j = lp.num_cols; j < lp.num_cols + p; j++) { | ||
| new_A_col.col_start[j] = nz; | ||
| new_A_col.i[nz] = k++; | ||
| new_A_col.x[nz] = 1.0; | ||
| nz++; | ||
| lp.lower[j] = 0.0; | ||
| lp.upper[j] = inf; | ||
| lp.objective[j] = 0.0; | ||
| new_slacks.push_back(j); | ||
| } | ||
| settings.log.debug("Done adding slacks\n"); | ||
| new_A_col.col_start[lp.num_cols + p] = nz; | ||
| new_A_col.n = lp.num_cols + p; | ||
|
|
||
| lp.A = new_A_col; | ||
|
|
||
| // Check that all slack columns have length 1 | ||
| for (i_t slack: new_slacks) { | ||
| const i_t col_start = lp.A.col_start[slack]; | ||
| const i_t col_end = lp.A.col_start[slack + 1]; | ||
| const i_t col_len = col_end - col_start; | ||
| if (col_len != 1) { | ||
| printf("Add cuts: Slack %d has %d nzs in column\n", slack, col_len); | ||
| exit(1); | ||
| } | ||
| } | ||
|
|
||
|
|
||
| i_t old_rows = lp.num_rows; | ||
| lp.num_rows += p; | ||
| i_t old_cols = lp.num_cols; | ||
| lp.num_cols += p; | ||
|
|
||
| lp.rhs.resize(lp.num_rows); | ||
| for (i_t k = old_rows; k < old_rows + p; k++) { | ||
| const i_t h = k - old_rows; | ||
| lp.rhs[k] = cut_rhs[h]; | ||
| } | ||
| settings.log.debug("Done adding rhs\n"); | ||
|
|
||
| // Construct C_B = C(:, basic_list) | ||
| std::vector<i_t> C_col_degree(lp.num_cols, 0); | ||
| i_t cuts_nz = cuts.row_start[p]; | ||
| for (i_t q = 0; q < cuts_nz; q++) { | ||
| const i_t j = cuts.j[q]; | ||
| if (j >= lp.num_cols) { | ||
| settings.log.printf("j %d is greater than p %d\n", j, p); | ||
| return -1; | ||
| } | ||
| C_col_degree[j]++; | ||
| } | ||
| settings.log.debug("Done computing C_col_degree\n"); | ||
|
|
||
| std::vector<i_t> in_basis(old_cols, -1); | ||
| const i_t num_basic = static_cast<i_t>(basic_list.size()); | ||
| i_t C_B_nz = 0; | ||
| for (i_t k = 0; k < num_basic; k++) { | ||
| const i_t j = basic_list[k]; | ||
| if (j < 0 || j >= old_cols) { | ||
| settings.log.printf( | ||
| "basic_list[%d] = %d is out of bounds %d old_cols %d\n", k, j, j, old_cols); | ||
| return -1; | ||
| } | ||
| in_basis[j] = k; | ||
| // The cuts are on the original variables. So it is possible that | ||
| // a slack will be basic and thus not part of the cuts matrix | ||
| if (j < cuts.n) { C_B_nz += C_col_degree[j]; } | ||
| } | ||
| settings.log.debug("Done estimating C_B_nz\n"); | ||
|
|
||
| csr_matrix_t<i_t, f_t> C_B(p, num_basic, C_B_nz); | ||
| nz = 0; | ||
| for (i_t i = 0; i < p; i++) { | ||
| C_B.row_start[i] = nz; | ||
| const i_t row_start = cuts.row_start[i]; | ||
| const i_t row_end = cuts.row_start[i + 1]; | ||
| for (i_t q = row_start; q < row_end; q++) { | ||
| const i_t j = cuts.j[q]; | ||
| const i_t j_basis = in_basis[j]; | ||
| if (j_basis == -1) { continue; } | ||
| C_B.j[nz] = j_basis; | ||
| C_B.x[nz] = cuts.x[q]; | ||
| nz++; | ||
| } | ||
| } | ||
| C_B.row_start[p] = nz; | ||
|
|
||
| if (nz != C_B_nz) { | ||
| settings.log.printf("Add cuts: predicted nz %d actual nz %d\n", C_B_nz, nz); | ||
| for (i_t i = 0; i < p; i++) { | ||
| const i_t row_start = cuts.row_start[i]; | ||
| const i_t row_end = cuts.row_start[i + 1]; | ||
| for (i_t q = row_start; q < row_end; q++) { | ||
| const i_t j = cuts.j[q]; | ||
| printf("C(%d, %d) = %e\n", i, j, C_B.x[q]); | ||
| } | ||
| } | ||
| return -1; | ||
| } | ||
| settings.log.debug("C_B rows %d cols %d nz %d\n", C_B.m, C_B.n, nz); | ||
|
|
||
| // Adjust the basis update to include the new cuts | ||
| basis_update.append_cuts(C_B); | ||
|
|
||
| basic_list.resize(lp.num_rows, 0); | ||
| i_t h = old_cols; | ||
| for (i_t j = old_rows; j < lp.num_rows; j++) { | ||
| basic_list[j] = h++; | ||
| } | ||
|
|
||
| #ifdef CHECK_BASIS | ||
| // Check the basis update | ||
| csc_matrix_t<i_t, f_t> Btest(lp.num_rows, lp.num_rows, 1); | ||
| basis_update.multiply_lu(Btest); | ||
|
|
||
| csc_matrix_t<i_t, f_t> B(lp.num_rows, lp.num_rows, 1); | ||
| form_b(lp.A, basic_list, B); | ||
|
|
||
| csc_matrix_t<i_t, f_t> Diff(lp.num_rows, lp.num_rows, 1); | ||
| add(Btest, B, 1.0, -1.0, Diff); | ||
| const f_t err = Diff.norm1(); | ||
| settings.log.printf("After || B - L*U || %e\n", err); | ||
| if (err > 1e-6) { | ||
| settings.log.printf("Diff matrix\n"); | ||
| // Diff.print_matrix(); | ||
| exit(1); | ||
| } | ||
| #endif | ||
| // Adjust the vstatus | ||
| vstatus.resize(lp.num_cols); | ||
| for (i_t j = old_cols; j < lp.num_cols; j++) { | ||
| vstatus[j] = variable_status_t::BASIC; | ||
| } | ||
|
|
||
| return 0; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add_cuts(...): Fix inconsistent error handling; verify edge_norms is properly maintained after resizing columns.
-
Inconsistent error handling: The function uses
exit(1)on lines ~2066, 2074, and 2095 for genuine error conditions (out-of-bounds indices, mismatch detection), yet returns-1for other errors. This is unsafe in production/library code—replaceexit(1)with proper error returns or exceptions. -
Edge_norms not resized: The function increases
lp.num_colsbyp(new slack columns) but does not resize or reinitializeedge_norms. The caller incutting_plane_search.cpppassesedge_normstodual_simplex_mainwithout explicit reinitialization. If downstream code accessesedge_norms[j]for the new columns, this will cause out-of-bounds access. Confirm whetherdual_simplex_mainor callers handle this, or add explicit edge_norms reinitialization. -
vstatus invariant: Properly resized and initialized for new columns (lines 2174–2177).
-
basis_update.append_cuts() state: The call at line 2159 updates the internal matrix
C, which is used for subsequent factorization updates. Verify that all factorization state (LU factors, permutations) is correctly maintained when new rows/cols are added.
🤖 Prompt for AI Agents
In `@cpp/src/dual_simplex/cuts.cpp` around lines 2030 - 2226, The add_cuts
function uses exit(1) in several runtime-error paths and fails to resize
edge_norms after adding slack columns; change all non-#ifdef exit(1) calls in
add_cuts (e.g., the append_rows failure, the basic_list out-of-bounds check, and
the C_B_nz mismatch) to return a non-zero error code (e.g., return -1) after
logging the error so the caller can handle it; after lp.num_cols is increased
(after lp.num_cols += p) resize and initialize edge_norms to the new length
(edge_norms.resize(lp.num_cols) and set the new entries to a safe default or
recompute their values consistent with how edge_norms is used downstream); keep
the CHECK_BASIS debug exits conditional, and finally ensure the call
basis_update.append_cuts(C_B) leaves factorization state consistent—if the
basis_update API requires an explicit invalidate/recompute step, call that (or
set the appropriate flag) immediately after append_cuts so subsequent LU
operations use a correct factorization.
This PR adds cuts to the MIP solver. This includes the following:
Summary by CodeRabbit
New Features
Improvements
Bug Fixes
✏️ Tip: You can customize this high-level summary in your review settings.