Skip to content

Conversation

@chris-maes
Copy link
Contributor

@chris-maes chris-maes commented Nov 17, 2025

This PR adds cuts to the MIP solver. This includes the following:

  1. Add constraints in the form C*x <= d to an LP that has already been solved to optimality (and has basis information).
    • The constraints must be violated at the current relaxation solution x^star. That is, C*x^star > d.
    • The constraint matrix, rhs, basis, and basis factorization, are all updated to include the additional constraints.
    • Dual simplex is started in phase 2 from a dual feasible solution.
  2. Remove constraints from an LP that has already been solved to optimality.
    • The constraints must have slacks in the basis
    • The basis is refactored from scratch
  3. Add cut pass loop after solving the root relaxation
  4. Add a cut pool to store cuts and select cuts
    • We currently score cuts based on distance and orthogonality.
  5. Add Mixed Integer Gomory Cuts
    • These are computed via a MIR cut on a row of the simplex tableau
  6. Add Mixed Integer Rounding (MIR) Cuts
    • These are currently constructed from a single rows of the constraint matrix. No aggregation.
  7. Fixes to Handling of Steepest Edge Norms in Dual Simplex
    • Ensure that all basic variables have a positive steepest edge norms
  8. Experimental Reliability Branching
  9. Experimental Reduced Costs Fixing

Summary by CodeRabbit

  • New Features

    • Cutting-plane framework added (Gomory, MIR, knapsack) with cut pool, generation, scoring, and dynamic add/remove.
    • New solver options: max_cut_passes, node_limit, reliability_branching.
  • Improvements

    • Enhanced branching with reliable pseudo-cost trials and expanded root/node solving integration with cuts and bound-strengthening.
    • CSR/CSC utilities and sparse/dense helpers for row-based operations and dot/squeeze utilities.
  • Bug Fixes

    • Bounds-aware basis repair/refactor for more robust handling of variable bounds.

✏️ Tip: You can customize this high-level summary in your review settings.

@chris-maes chris-maes requested a review from a team as a code owner November 17, 2025 22:21
@copy-pr-bot
Copy link

copy-pr-bot bot commented Nov 17, 2025

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@coderabbitai
Copy link

coderabbitai bot commented Nov 17, 2025

📝 Walkthrough

Walkthrough

Adds 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

Cohort / File(s) Summary
Cut framework & integration
cpp/src/dual_simplex/cuts.cpp, cpp/src/dual_simplex/cuts.hpp
New templated cut subsystem (cut pool, scoring/aging), Gomory/MIR/Knapsack generation, knapsack solvers, cut orchestration, add_cuts/remove_cuts, CSR materialization, and explicit template instantiations. Review: algorithm correctness, memory ownership, and numeric/CSR conversions.
Basis updates & append cuts
cpp/src/dual_simplex/basis_updates.cpp, cpp/src/dual_simplex/basis_updates.hpp
Added basis_update_mpf_t::append_cuts(const csr_matrix_t&); in-place L/U updates, permutation/workspace expansion, recompute transposes; updated refactor_basis signature to accept lower/upper. Review: permutation consistency, numeric stability, CHECK_W paths.
Basis repair / solves API
cpp/src/dual_simplex/basis_solves.cpp, cpp/src/dual_simplex/basis_solves.hpp
Extended basis_repair declaration/implementation/instantiation to accept lower and upper bounds and use them to set nonbasic status. Review: all call sites updated, explicit instantiations adjusted.
CSR & sparse-vector utilities
cpp/src/dual_simplex/sparse_matrix.cpp, cpp/src/dual_simplex/sparse_matrix.hpp, cpp/src/dual_simplex/sparse_vector.cpp, cpp/src/dual_simplex/sparse_vector.hpp
Added csr_matrix_t::append_rows/append_row, strengthened index checks (check_matrix now returns i_t), and sparse_vector_t ctor-from-CSR-row plus dot and squeeze. Review: append correctness, callers handling new check return.
Branch-and-bound & root integration
cpp/src/dual_simplex/branch_and_bound.cpp, cpp/src/dual_simplex/branch_and_bound.hpp, cpp/src/dual_simplex/mip_node.hpp
solve_root_relaxation signature expanded to return root state and basis, added find_reduced_cost_fixings, added num_integer_variables_, mutex_original_lp_, and mip_node_t::integer_infeasible. Review: ABI/signature impacts, concurrency, node propagation.
Phase2, crossover & primal flows
cpp/src/dual_simplex/phase2.cpp, cpp/src/dual_simplex/crossover.cpp, cpp/src/dual_simplex/primal.cpp
Added primal infeasibility breakdown, modified prepare_optimality signature, guarded dual_phase2 by dual feasibility, and updated repair/refactor calls to pass lp.lower/lp.upper. Review: call-site updates and feasibility guards.
Variable selection & trial branching
cpp/src/dual_simplex/pseudo_costs.cpp, cpp/src/dual_simplex/pseudo_costs.hpp
Added trial_branching helper and pseudo_costs_t::reliable_variable_selection performing trial solves to build reliable pseudo-costs and select branching vars. Review: correctness, thread-safety, and perf impact.
Solver settings & constants
cpp/src/dual_simplex/simplex_solver_settings.hpp, cpp/include/cuopt/linear_programming/mip/solver_settings.hpp, cpp/include/cuopt/linear_programming/constants.h, cpp/src/math_optimization/solver_settings.cu
Added max_cut_passes, node_limit, and reliability_branching settings/parameters and plumbing into solver initialization. Review: defaults, bounds, propagation.
Build, infra & small APIs
cpp/src/dual_simplex/solve.cpp, cpp/src/dual_simplex/CMakeLists.txt, cpp/src/dual_simplex/dense_matrix.hpp
Added include for basis_solves.hpp, enabled compiling cuts.cpp and debug -g1, and added dense_matrix_t(rows,cols,value) constructor. Review: build implications and constructor semantics.
MIP flow gating & diversity tweaks
cpp/src/mip/solver.cu, cpp/src/mip/diversity/diversity_manager.cu, cpp/src/mip/diversity/lns/rins.cu, cpp/src/mip/diversity/recombiners/sub_mip.cuh
Disabled some presolve/concurrent-LP paths, commented out rins.enable(), set max_cut_passes in RINS, and disabled sub-MIP gating. Review: behavioral/regression impacts.
Quadratic objective construction
cpp/src/linear_programming/optimization_problem.cu
Replaced map-based Q+Q^T accumulation with triplet→CSR construction and in-row deduplication. Review: aggregation correctness, symmetry, and performance.
Tests
cpp/tests/dual_simplex/unit_tests/solve.cpp
Added simple_cuts test block but left wrapped in #if 0 and duplicated (disabled). Review: remove duplicate or enable test.
Misc & logging
cpp/src/dual_simplex/solution.hpp, various files
Many diagnostic/logging additions across B&B and phase flows; minor comment edits. Review: verbosity control and conditional logging.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.91% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add cuts to MIP solver' directly and concisely describes the main feature being implemented across the changeset—the addition of cut management and generation capabilities to the MIP solver.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@chris-maes chris-maes added feature request New feature or request non-breaking Introduces a non-breaking change improvement Improves an existing functionality labels Nov 17, 2025
@chris-maes chris-maes self-assigned this Nov 17, 2025
@chris-maes chris-maes removed the improvement Improves an existing functionality label Nov 17, 2025
@chris-maes chris-maes changed the title Add constraints and adjust dual simplex to warm start from current basis Add constraints and warm start dual simplex from current basis Nov 17, 2025
Copy link

@coderabbitai coderabbitai bot left a 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 consistent

The new append_cuts declaration cleanly extends the basis update interface and matches the described usage from solve_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 other i_t-returning methods.

cpp/src/dual_simplex/sparse_matrix.hpp (1)

139-140: CSR append_rows API is reasonable

Adding csr_matrix_t::append_rows here 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 when C.n matches this->n (or clearly document if smaller C.n is intentionally supported).

cpp/src/dual_simplex/sparse_vector.cpp (1)

31-44: CSR-row constructor is correct; consider adding a bounds assert

The implementation correctly builds a length‑A.n sparse vector whose indices are the column indices from row row of the CSR matrix. To match existing defensive style (e.g., in other methods using assert for 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 checks

The append_rows logic (row_start updates, contiguous copy of C.j/C.x, and m/nz_max updates) 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 0 on success and -1 on dimension mismatch, so callers know they must check the return value.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f23d0ff and 099a1df.

📒 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 consistent

The 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.

@rgsl888prabhu rgsl888prabhu changed the base branch from main to release/25.12 November 17, 2025 23:00
@rgsl888prabhu rgsl888prabhu requested review from a team as code owners November 17, 2025 23:00
@anandhkb
Copy link
Contributor

@chris-maes Aspirational for 25.12?

@anandhkb anandhkb added this to the 25.12 milestone Nov 24, 2025
@chris-maes chris-maes modified the milestones: 25.12, 26.02 Nov 25, 2025
@chris-maes
Copy link
Contributor Author

This does not need to be part of 25.12.

@chris-maes chris-maes changed the base branch from release/25.12 to main November 25, 2025 01:02
…eursitics and MIP presolve for experimentation
Copy link

@coderabbitai coderabbitai bot left a 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 to sparse_vector_t construction.

As previously noted, sparse_vector_t is 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 in append_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 assert would 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 with exit(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:

  1. Converting to CUOPT_ASSERT for debug builds only
  2. Returning an error status instead of terminating
  3. Wrapping with a constexpr bool debug_verify = false; guard similar to write_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

📥 Commits

Reviewing files that changed from the base of the PR and between 099a1df and 1882892.

📒 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.cu
  • cpp/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_rows and append_row follow 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 of append_cuts looks structurally sound.

The method correctly:

  1. Solves U^T W^T = C_B^T row-by-row
  2. Computes V from W using the inverse transforms
  3. Extends L and U with the appropriate block structure
  4. 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_rows implementation correctly:

  1. Validates that the input matrix has compatible column dimensions
  2. Resizes storage appropriately
  3. Updates row pointers and copies nonzero data
cpp/src/dual_simplex/branch_and_bound.cpp (1)

1070-1075: Setting scale_columns = false may 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.hpp is required to support the append_cuts method 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_i gets a slack variable s_i >= 0 such that C_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.

Comment on lines 1182 to 1185
settings_.log.printf(
"Variable %d has negative lower bound %e\n", j, original_lp_.lower[j]);
exit(1);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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.

Suggested change
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.

Comment on lines 1467 to 1471
if (Cx[k] <= cut_rhs[k]) {
lp_settings.log.printf("C*x <= d for cut %d\n", k);
exit(1);
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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.

Suggested change
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.

Comment on lines 1490 to 1493
if (cut_status != lp_status_t::OPTIMAL) {
lp_settings.log.printf("Cut status %d\n", cut_status);
exit(1);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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.

Suggested change
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.

Comment on lines 479 to 487
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;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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.

Comment on lines +112 to +113
//bool presolve_success = dm.run_presolve(timer_.remaining_time());
bool presolve_success = true;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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.

Suggested change
//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) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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.

Suggested change
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.

Comment on lines 228 to 229
auto bb_status = branch_and_bound_status_future.get();
exit(1);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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.

Suggested change
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.

@github-actions
Copy link

🔔 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.
If it is a PR and not ready for review, then please convert this to draft.
If you just want to switch off this notification, then use the "skip inactivity reminder" label.

Copy link

@coderabbitai coderabbitai bot left a 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_success is hardcoded to true. 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 to false, 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: Replace exit(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: Replace exit(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_cuts and the subsequent status check. If add_cuts is a lengthy operation, this could create contention with set_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 printf call bypasses the solver's logging infrastructure. Use settings.log.printf or similar for consistency with the rest of the codebase.

However, since minimum_violation doesn't take settings as a parameter, consider either:

  1. Adding a settings parameter, or
  2. 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_simplex
cpp/src/dual_simplex/cuts.cpp (7)

15-35: Consider returning an error code from add_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_t status) 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_B matrix by iterating through all cuts and checking basic variable membership. The nested loops (cuts × cut_coefficients × basic_lookup) have O(p × nnz × 1) complexity where p is the number of cuts. For large-scale problems with thousands of cuts, consider whether the in_basis lookup (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

📥 Commits

Reviewing files that changed from the base of the PR and between 9dea7ce and 3c36836.

📒 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.hpp
  • cpp/src/mip/diversity/diversity_manager.cu
  • cpp/src/mip/solver.cu
  • cpp/src/dual_simplex/solve.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/src/mip/diversity/lns/rins.cu
  • cpp/src/dual_simplex/cuts.hpp
  • cpp/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.hpp
  • cpp/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.hpp
  • cpp/src/dual_simplex/solve.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/src/dual_simplex/cuts.hpp
  • 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/branch_and_bound.hpp
  • cpp/src/mip/diversity/diversity_manager.cu
  • cpp/src/mip/solver.cu
  • cpp/src/dual_simplex/solve.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/src/mip/diversity/lns/rins.cu
  • cpp/src/dual_simplex/cuts.hpp
  • cpp/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.cu
  • cpp/src/mip/solver.cu
  • 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/diversity_manager.cu
  • cpp/src/mip/solver.cu
  • cpp/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.hpp
  • cpp/src/mip/solver.cu
  • cpp/src/dual_simplex/solve.cpp
  • cpp/src/dual_simplex/CMakeLists.txt
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/src/mip/diversity/lns/rins.cu
  • cpp/src/dual_simplex/cuts.hpp
  • 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/branch_and_bound.hpp
  • cpp/src/mip/diversity/diversity_manager.cu
  • cpp/src/mip/solver.cu
  • cpp/src/dual_simplex/solve.cpp
  • cpp/src/dual_simplex/CMakeLists.txt
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/src/mip/diversity/lns/rins.cu
  • cpp/src/dual_simplex/cuts.hpp
  • 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/branch_and_bound.hpp
  • cpp/src/mip/solver.cu
  • cpp/src/dual_simplex/solve.cpp
  • cpp/src/dual_simplex/CMakeLists.txt
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/src/mip/diversity/lns/rins.cu
  • cpp/src/dual_simplex/cuts.hpp
  • 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/mip/diversity/diversity_manager.cu
  • cpp/src/mip/solver.cu
  • cpp/src/dual_simplex/solve.cpp
  • cpp/src/dual_simplex/CMakeLists.txt
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/src/dual_simplex/cuts.hpp
  • 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} : 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.cu
  • cpp/src/dual_simplex/solve.cpp
  • cpp/src/dual_simplex/CMakeLists.txt
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/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.cu
  • cpp/src/dual_simplex/solve.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/src/mip/diversity/lns/rins.cu
  • cpp/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.cu
  • cpp/src/dual_simplex/solve.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/src/dual_simplex/cuts.hpp
  • 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} : 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.cu
  • cpp/src/dual_simplex/solve.cpp
  • 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 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.cu
  • cpp/src/dual_simplex/solve.cpp
  • 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/mip/solver.cu
  • cpp/src/dual_simplex/solve.cpp
  • 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/mip/solver.cu
  • 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/mip/solver.cu
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/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.cu
  • cpp/src/dual_simplex/solve.cpp
  • cpp/src/dual_simplex/CMakeLists.txt
  • 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/solve.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • 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} : 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.cpp
  • 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/solve.cpp
  • 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 **/*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.cpp to the build is appropriate for the new cutting plane support.


37-37: Debug compile flag enabled - verify this is intentional for release.

The -g1 compile 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_passes configuration 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 transformations
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)
cpp/src/dual_simplex/branch_and_bound.cpp (5)

9-10: LGTM: New includes for cut generation infrastructure.

The includes for basis_solves.hpp and cuts.hpp are 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_basis enables 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_pool is initialized with original_lp_.num_cols at this point. After cuts are added, original_lp_.num_cols increases (slack variables are added). Verify that the cut pool's original_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_fractional for tracking integer infeasibility at each node.

cpp/src/dual_simplex/cuts.hpp (3)

44-52: Verify lifetime of settings_ reference.

The cut_pool_t stores settings_ as a const reference. Ensure the simplex_solver_settings_t object outlives the cut_pool_t instance to avoid dangling reference issues.


41-88: LGTM: Cut pool class design.

The cut_pool_t class 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.

Copy link

@coderabbitai coderabbitai bot left a 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_violation by cut_norm without checking if cut_norm is 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 when mir_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: Replace exit(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: Replace exit(1) with error return.

Line 1232 calls exit(1) when add_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: Replace exit(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:

  1. 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.

  2. Unbounded cut pool memory: cut_pool accumulates cuts across iterations with no enforcement of memory bounds. While age_cuts() and drop_cuts() methods exist in cut_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.

  3. 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 be std::vector<i_t>.

Line 158: nonbasic_mark_ is declared as std::vector<f_t> but is used as a marker array with values 0 or 1 (see line 139 in the constructor). It should be std::vector<i_t> for consistency with x_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 if cut_norm is 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_rows implementation 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

📥 Commits

Reviewing files that changed from the base of the PR and between 3c36836 and 369e755.

📒 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.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/src/dual_simplex/sparse_matrix.cpp
  • cpp/src/dual_simplex/sparse_vector.hpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/src/dual_simplex/sparse_matrix.cpp
  • cpp/src/dual_simplex/sparse_vector.hpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/src/dual_simplex/sparse_matrix.cpp
  • cpp/src/dual_simplex/sparse_vector.hpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/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.hpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/src/dual_simplex/sparse_matrix.cpp
  • 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} : 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
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/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.cpp
  • 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/cuts.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/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.cpp
  • 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} : 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.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/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.cpp
  • 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 **/*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.cpp
  • 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/cuts.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/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.cpp
  • 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/cuts.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/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.cpp
  • 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} : 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_row implementation 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_start size 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.hpp and cuts.hpp are necessary for the extended cutting plane functionality.


1080-1088: LGTM - Enhanced root LP solve with advanced basis.

The change from solve_linear_program_advanced to solve_linear_program_with_advanced_basis with explicit basis tracking (basis_update, basic_list, nonbasic_list) enables the warm-start dual simplex flow described in the PR objectives.

@github-actions
Copy link

🔔 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.
If it is a PR and not ready for review, then please convert this to draft.
If you just want to switch off this notification, then use the "skip inactivity reminder" label.

1 similar comment
@github-actions
Copy link

github-actions bot commented Jan 4, 2026

🔔 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.
If it is a PR and not ready for review, then please convert this to draft.
If you just want to switch off this notification, then use the "skip inactivity reminder" label.

…b-ball and swath1. Add reliability branching as an option. Finally seeing good performance on swath1
Copy link

@coderabbitai coderabbitai bot left a 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_mip is false, branch_and_bound_solution remains 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 false to indicate no valid solution was found via sub-MIP, or adjust the logic to account for the offspring.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: Use settings.log.printf for consistency with the rest of the codebase.

This diagnostic output uses raw printf while the rest of the file uses settings.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_needed variable and its check are redundant since they occur within the same loop iteration. The commented-out printf statements should be removed or converted to proper debug macros (similar to #ifdef STEEPEST_EDGE_DEBUG used 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_mip is 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: Variable n shadows member variable, causing confusion.

Line 242 declares const i_t n = x.size() which shadows the member variable n (the vector dimension). While the code functions correctly because y.n is assigned from this->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 raw printf with structured logging.

This debug output should use settings.log.printf or settings.log.debug for 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.dat unconditionally 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.dat is debug/development code that should not run in production. It also contains exit(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 printf calls 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

📥 Commits

Reviewing files that changed from the base of the PR and between 369e755 and 78cb1dc.

📒 Files selected for processing (12)
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/src/dual_simplex/branch_and_bound.hpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/src/dual_simplex/cuts.hpp
  • cpp/src/dual_simplex/dense_matrix.hpp
  • cpp/src/dual_simplex/phase2.cpp
  • cpp/src/dual_simplex/presolve.cpp
  • cpp/src/dual_simplex/pseudo_costs.cpp
  • cpp/src/dual_simplex/pseudo_costs.hpp
  • cpp/src/dual_simplex/sparse_vector.cpp
  • cpp/src/dual_simplex/sparse_vector.hpp
  • cpp/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.cuh
  • cpp/src/dual_simplex/presolve.cpp
  • cpp/src/dual_simplex/dense_matrix.hpp
  • cpp/src/dual_simplex/pseudo_costs.hpp
  • cpp/src/dual_simplex/pseudo_costs.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/src/dual_simplex/phase2.cpp
  • cpp/src/dual_simplex/sparse_vector.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/dense_matrix.hpp
  • cpp/src/dual_simplex/pseudo_costs.hpp
  • cpp/src/dual_simplex/pseudo_costs.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/src/dual_simplex/phase2.cpp
  • cpp/src/dual_simplex/sparse_vector.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/dense_matrix.hpp
  • cpp/src/dual_simplex/pseudo_costs.hpp
  • cpp/src/dual_simplex/pseudo_costs.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/src/dual_simplex/phase2.cpp
  • cpp/src/dual_simplex/sparse_vector.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/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.hpp
  • cpp/src/dual_simplex/pseudo_costs.hpp
  • cpp/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.cuh
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/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.cuh
  • cpp/src/dual_simplex/presolve.cpp
  • cpp/src/dual_simplex/pseudo_costs.hpp
  • cpp/src/dual_simplex/pseudo_costs.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/src/dual_simplex/phase2.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/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.cuh
  • cpp/src/dual_simplex/presolve.cpp
  • cpp/src/dual_simplex/pseudo_costs.hpp
  • cpp/src/dual_simplex/pseudo_costs.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/src/dual_simplex/phase2.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/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.cuh
  • cpp/src/dual_simplex/presolve.cpp
  • cpp/src/dual_simplex/pseudo_costs.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/src/dual_simplex/phase2.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/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.cuh
  • cpp/src/dual_simplex/presolve.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/src/dual_simplex/phase2.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/src/dual_simplex/phase2.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • 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} : 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.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/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.cpp
  • 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} : 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.cpp
  • 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} : 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
  • cpp/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.cpp
  • 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,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.cpp
  • cpp/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 info parameter provides useful call-site identification for debugging primal infeasibility issues.


2473-2488: LGTM - Call site correctly updated.

The call to prepare_optimality is correctly updated with the new info parameter.


2640-2655: LGTM - Call site correctly updated.

The call to prepare_optimality is correctly updated with the new info parameter.


2678-2693: LGTM - Call site correctly updated.

The call to prepare_optimality is correctly updated with the new info parameter.

cpp/src/dual_simplex/pseudo_costs.hpp (1)

50-59: LGTM!

The new reliable_variable_selection method 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_t class 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: Replace exit(1) with error return in minimum_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 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 **/*.{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;
Copy link

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:

  1. Add a configurable setting (e.g., context.settings.enable_sub_mip_recombiner) to make this runtime-controllable
  2. Remove or comment out the dead code (lines 92-138) if sub-MIP is being permanently disabled
  3. 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.

Copy link

@coderabbitai coderabbitai bot left a 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 = 0 disables 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:

  1. Removing this debug code entirely, or
  2. 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

📥 Commits

Reviewing files that changed from the base of the PR and between 78cb1dc and 3744548.

📒 Files selected for processing (22)
  • cpp/include/cuopt/linear_programming/constants.h
  • cpp/include/cuopt/linear_programming/mip/solver_settings.hpp
  • cpp/src/dual_simplex/CMakeLists.txt
  • cpp/src/dual_simplex/basis_updates.cpp
  • cpp/src/dual_simplex/basis_updates.hpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/src/dual_simplex/branch_and_bound.hpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/src/dual_simplex/cuts.hpp
  • cpp/src/dual_simplex/mip_node.hpp
  • cpp/src/dual_simplex/phase2.cpp
  • cpp/src/dual_simplex/pseudo_costs.cpp
  • cpp/src/dual_simplex/simplex_solver_settings.hpp
  • cpp/src/dual_simplex/solution.hpp
  • cpp/src/dual_simplex/solve.cpp
  • cpp/src/dual_simplex/sparse_matrix.cpp
  • cpp/src/dual_simplex/sparse_matrix.hpp
  • cpp/src/math_optimization/solver_settings.cu
  • cpp/src/mip/diversity/diversity_manager.cu
  • cpp/src/mip/diversity/lns/rins.cu
  • cpp/src/mip/diversity/recombiners/sub_mip.cuh
  • cpp/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.cpp
  • cpp/src/dual_simplex/mip_node.hpp
  • cpp/src/dual_simplex/pseudo_costs.cpp
  • cpp/src/dual_simplex/sparse_matrix.cpp
  • cpp/src/dual_simplex/basis_updates.hpp
  • cpp/src/dual_simplex/solution.hpp
  • cpp/src/mip/diversity/lns/rins.cu
  • cpp/src/dual_simplex/branch_and_bound.hpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/mip_node.hpp
  • cpp/src/dual_simplex/pseudo_costs.cpp
  • cpp/src/dual_simplex/sparse_matrix.cpp
  • cpp/src/dual_simplex/basis_updates.hpp
  • cpp/src/dual_simplex/solution.hpp
  • cpp/src/dual_simplex/branch_and_bound.hpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/mip_node.hpp
  • cpp/src/dual_simplex/pseudo_costs.cpp
  • cpp/src/dual_simplex/sparse_matrix.cpp
  • cpp/src/dual_simplex/basis_updates.hpp
  • cpp/src/dual_simplex/solution.hpp
  • cpp/src/mip/diversity/lns/rins.cu
  • cpp/src/dual_simplex/branch_and_bound.hpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/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.hpp
  • cpp/src/dual_simplex/basis_updates.hpp
  • cpp/src/dual_simplex/solution.hpp
  • cpp/src/dual_simplex/branch_and_bound.hpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/mip_node.hpp
  • cpp/src/dual_simplex/pseudo_costs.cpp
  • cpp/src/mip/diversity/lns/rins.cu
  • cpp/src/dual_simplex/branch_and_bound.hpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/sparse_matrix.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • 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: 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.cpp
  • cpp/src/dual_simplex/basis_updates.hpp
  • cpp/src/dual_simplex/branch_and_bound.hpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/pseudo_costs.cpp
  • cpp/src/mip/diversity/lns/rins.cu
  • cpp/src/dual_simplex/branch_and_bound.hpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/pseudo_costs.cpp
  • cpp/src/dual_simplex/sparse_matrix.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/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.hpp
  • cpp/src/dual_simplex/branch_and_bound.hpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/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.cpp
  • cpp/src/mip/diversity/lns/rins.cu
  • cpp/src/dual_simplex/branch_and_bound.hpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/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.cpp
  • cpp/src/mip/diversity/lns/rins.cu
  • cpp/src/dual_simplex/branch_and_bound.hpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • 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} : 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
  • cpp/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.cpp
  • cpp/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.cu
  • cpp/src/dual_simplex/branch_and_bound.hpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/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.hpp
  • 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/cuts.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/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.cpp
  • 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} : Verify correct problem size checks before expensive GPU/CPU operations; prevent resource exhaustion on oversized problems

Applied to files:

  • cpp/src/dual_simplex/cuts.cpp
  • 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/cuts.cpp
  • 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 **/*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.cpp
  • 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 (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_fixings method 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 acquires mutex_original_lp_ and mutex_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_inf parameter is properly threaded through the constructor signature and initializer list.


250-250: LGTM: New field declaration.

The integer_infeasible field is appropriately declared alongside other node attributes.


299-343: LGTM: Branching updated to propagate integer infeasibility.

The branch method correctly threads the integer_infeasible parameter 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, and bounds_strengthening.hpp support the PR's cut generation and warm-start features.


302-376: Reduced-cost fixings implementation looks sound.

The find_reduced_cost_fixings method correctly acquires and releases mutex_original_lp_ (lines 306, 375) and implements the reduced-cost strengthening logic as described in the PR objectives.


379-449: Mutex usage appears correct within set_new_solution.

The function properly acquires and releases mutex_original_lp_ and mutex_upper_ in separate critical sections. Lock ordering is consistent: when both are needed, mutex_original_lp_ is released before acquiring mutex_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_relaxation implementation 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_t enum and print_cut_types helper function provide clear classification and logging for different cut types.


66-115: LGTM: Cut pool class design.

The cut_pool_t class 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_t class has a focused responsibility for generating knapsack-based cuts with a clear interface.


280-306: Public API for cut integration is comprehensive.

The add_cuts and remove_cuts template 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.

Comment on lines +238 to +262
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);
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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");
     }
+#endif

Or 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.

Comment on lines +1787 to +1791
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);
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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).

Comment on lines 932 to 935
if (lp.lower[j] < 0) {
settings_.log.printf("Variable %d has negative lower bound %e\n", j, lp.lower[j]);
exit(1);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Suggested change
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));
Copy link

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.

Comment on lines 435 to 452
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;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix mutex synchronization error and prevent out-of-bounds access.

Two critical issues in this block:

  1. 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).

  2. Line 447: If fractional is empty, select remains -1, causing score[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)

Copy link

@coderabbitai coderabbitai bot left a 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 unused validate_positive_semi_definite parameter.

The validate_positive_semi_definite parameter (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:

  1. Preferred: Implement the PSD validation when validate_positive_semi_definite is true, as this is important for ensuring correctness when the quadratic matrix is used by the barrier solver.
  2. 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_starts before modifying H_cumulative_counts during 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

📥 Commits

Reviewing files that changed from the base of the PR and between 3744548 and f8e6fbe.

📒 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.

Copy link

@coderabbitai coderabbitai bot left a 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: Use settings.log.printf for consistency with logging infrastructure.

Multiple printf calls 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 with settings.log.printf or settings_.log.printf as 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

📥 Commits

Reviewing files that changed from the base of the PR and between f8e6fbe and 6fc7e99.

📒 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

Comment on lines +926 to +929
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);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +1157 to +1165
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);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +1349 to +1354
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);
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +1483 to +1488
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);
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

@chris-maes chris-maes changed the title Add constraints and warm start dual simplex from current basis Add cuts to MIP solver Jan 8, 2026
Copy link

@coderabbitai coderabbitai bot left a 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 between fopen and fclose throws an exception, the file handle will leak. Use std::ofstream instead 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
     }
 #endif

Committable 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/fclose for reading and writing solution files. This is not exception-safe. If any operation throws between fopen and fclose, 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 file

Apply 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 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));
cpp/src/dual_simplex/cuts.cpp-1211-1219 (1)

1211-1219: Replace exit(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:

  1. Infinite loop risk: If the underlying issue persists, iterations may continue indefinitely without making progress
  2. Masked optimality: The solver may have reached optimality but fails to detect it due to corrupted steepest-edge data
  3. 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_infeasibility is computed correctly after clamping

Based on coding guidelines: Validate algorithm correctness in optimization logic.

cpp/src/dual_simplex/cuts.cpp-1537-1542 (1)

1537-1542: Replace exit(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_cuts function 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: Replace exit(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: Replace exit(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: Replace exit(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: Replace exit(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:

  1. Add logging at line 2346 to match line 2468's diagnostic printf, including iteration context and norm values
  2. Replace the overly strict assertion at line 1221 with proper error handling for singular/near-singular bases
  3. 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_n calls 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_definite is 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 printf directly. For consistency with the rest of the codebase and to respect log level settings, consider using settings_.log.printf() or settings_.log.debug().

Also applies to: 355-355, 372-372

cpp/src/dual_simplex/phase2.cpp (1)

2017-2017: Consider removing or documenting the info parameter.

The info parameter added to prepare_optimality is 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

📥 Commits

Reviewing files that changed from the base of the PR and between 6fc7e99 and 67b57c7.

📒 Files selected for processing (7)
  • cpp/src/dual_simplex/basis_updates.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/src/dual_simplex/cuts.hpp
  • cpp/src/dual_simplex/phase2.cpp
  • cpp/src/dual_simplex/pseudo_costs.cpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/basis_updates.cpp
  • cpp/src/dual_simplex/pseudo_costs.cpp
  • cpp/src/linear_programming/optimization_problem.cu
  • cpp/src/dual_simplex/phase2.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/basis_updates.cpp
  • cpp/src/dual_simplex/pseudo_costs.cpp
  • cpp/src/dual_simplex/phase2.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/basis_updates.cpp
  • cpp/src/dual_simplex/pseudo_costs.cpp
  • cpp/src/linear_programming/optimization_problem.cu
  • cpp/src/dual_simplex/phase2.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/basis_updates.cpp
  • cpp/src/dual_simplex/pseudo_costs.cpp
  • cpp/src/linear_programming/optimization_problem.cu
  • cpp/src/dual_simplex/phase2.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/basis_updates.cpp
  • cpp/src/dual_simplex/phase2.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/basis_updates.cpp
  • cpp/src/dual_simplex/pseudo_costs.cpp
  • cpp/src/linear_programming/optimization_problem.cu
  • cpp/src/dual_simplex/phase2.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/basis_updates.cpp
  • cpp/src/dual_simplex/phase2.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/pseudo_costs.cpp
  • cpp/src/linear_programming/optimization_problem.cu
  • cpp/src/dual_simplex/phase2.cpp
  • cpp/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
  • 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} : 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.cpp
  • cpp/src/linear_programming/optimization_problem.cu
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/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.cpp
  • 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} : 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.cpp
  • cpp/src/dual_simplex/basis_updates.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/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.cpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/basis_updates.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/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.cpp
  • cpp/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.cpp
  • 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/branch_and_bound.cpp
  • 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,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
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/basis_updates.cpp
  • cpp/src/linear_programming/optimization_problem.cu
  • 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} : 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.cpp
  • cpp/src/dual_simplex/basis_updates.cpp
  • 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} : 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: Replace exit(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.

Comment on lines +1275 to +1278
if (L_nz != predicted_nz) {
printf("L_nz %d predicted_nz %d\n", L_nz, predicted_nz);
exit(1);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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.

Comment on lines +248 to +262
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);
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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");
     }
+#endif

Or 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.

Comment on lines +1825 to +1828
if (add_cuts_status != 0) {
settings_.log.printf("Failed to add cuts\n");
exit(1);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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.

Comment on lines +1844 to +1847
if (!feasible) {
settings_.log.printf("Bound strengthening failed\n");
exit(1);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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.

Comment on lines +1880 to +1883
if (cut_status != dual::status_t::OPTIMAL) {
settings_.log.printf("Cut status %d\n", cut_status);
exit(1);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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.

Comment on lines +68 to +84
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);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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.

Suggested change
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.

Comment on lines +1365 to +1369
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);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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.

Suggested change
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.

Comment on lines 353 to 455
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;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix mutex synchronization and prevent out-of-bounds access.

Two critical issues in this method:

  1. 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.

  2. Array bounds: If fractional is empty or all scores remain at their initial value, select stays at -1, causing score[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).

Copy link

@coderabbitai coderabbitai bot left a 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_infeasibilities now outputs both (a) primal_inf_squared (return) and (b) primal_inf as 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 mix primal_infeasibility_squared and primal_infeasibility when calling the same update routine (Line 2990-3021). This will corrupt primal_infeasibility after 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 using old_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_DEBUG block has an extra ) in if (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() corrupts basic_list/nonbasic_list by push_back on 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() != m and will poison basis_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.dat unconditionally,
  • uses multiple exit(1) on validation failures,
  • emits extensive printf in multi-threaded code paths,
  • sets lp_settings.concurrent_halt = NULL (prefer nullptr),
  • 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 holding mutex_upper_ in add_feasible_solution(), but attempts to acquire mutex_original_lp_.

This creates a deadlock scenario with set_new_solution(), which acquires locks in the opposite order:

  • add_feasible_solution() (lines 678–707): Holds mutex_upper_, then calls find_reduced_cost_fixings() (line 697) which tries to acquire mutex_original_lp_.
  • set_new_solution() (lines 383–422): Acquires mutex_original_lp_ (line 383), then later acquires mutex_upper_ (line 395).

Fix: Refactor find_reduced_cost_fixings() to accept the needed data without holding mutex_upper_, or move the call outside the critical section. Use std::scoped_lock to enforce a consistent global lock order across all methods.

cpp/src/dual_simplex/basis_solves.cpp (1)

614-672: Handle fixed variables in basis_repair() (currently misclassifies lower==upper as LOWER/UPPER).

If bad_j is fixed (|upper-lower| <= settings.fixed_tol), it should become NONBASIC_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:

  1. Line 160: Commented-out printf debug statement should be removed.
  2. Missing time limit check: Unlike strong_branch_helper (lines 54-57), this function doesn't check or enforce the solver's time_limit. Trial branching could consume excessive time, especially when called multiple times from reliable_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_limit has been exceeded. Combined with the missing time limit in trial_branching itself (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_time parameter 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_time to trial_branching as 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_cut function documents cuts as cut'*x >= rhs (violated when < rhs), but get_best_cuts returns cuts in form best_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 match minimum_violation implementation expectations) and rename parameters like rhs to lhs_sense for 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/solution are sized as knapsack_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), h will mismatch and solution[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 uses floor(weights[j-1]) (Line 491-496) and later compares dp(n,v) <= rhs where rhs is f_t (Line 503-504). If weights/rhs are 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
Setting delta_y_steepest_edge[j] = 1e-4 for 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) via b_transpose_solve for 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_cuts should clear best_rhs / best_cut_types (currently appends across calls)
You clear best_cuts but 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_norms returns -1 for both “time limit” and “concurrent halt” (Line 1234-1236), but the caller maps that to dual::status_t::TIME_LIMIT unconditionally (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 unconditional printf in solver hot paths; use settings.log + verbosity gates
These prints will flood stdout and can materially slow phase2 on hard instances (Line 1234-1236, 2166-2175, 2418-2421). Prefer settings.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 before refactor_basis call

The basis invariant basic_list.size() == lp.num_rows is required by refactor_basis and factorization routines. After rebuilding new_basic_list from remaining columns (line 1621–1627), you do not validate this invariant before calling refactor_basis at line 1662. Other code paths (e.g., phase2.cpp, branch_and_bound.cpp) explicitly assert this invariant with assert(nonbasic_list.size() == n - m) or runtime checks before calling refactor_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: Remove printf debug dumps in solve_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 printf directly, while the rest of the codebase uses log.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 for max_cut_passes and reliability_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 of reliability_branching > 0 are 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 for node_limit, reliability_branching, and max_cut_passes (expected ranges, -1 meaning, etc.). Also confirm whether mip_solver_settings_t is 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_breakdown duplicates logic already present; risk of drift
This is very close to phase2::primal_infeasibility (same file) and also the primal_infeasibility(...) helper shown in cpp/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_cut explicitly skips dedup (Line 18) and drop_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 new lower/upper parameters (avoid silent OOB).

Now that basis_repair() takes lower/upper, please ensure the implementation asserts lower.size() == A.n and upper.size() == A.n (and vstatus.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-aware refactor_basis() forwarding is good; add size assertions for lower/upper.

Since refactor_basis() now forwards bounds into basis_repair(), please assert lower.size() == A.n and upper.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 printf and 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

📥 Commits

Reviewing files that changed from the base of the PR and between 67b57c7 and 49bdd7f.

📒 Files selected for processing (16)
  • cpp/include/cuopt/linear_programming/constants.h
  • cpp/include/cuopt/linear_programming/mip/solver_settings.hpp
  • cpp/src/dual_simplex/basis_solves.cpp
  • cpp/src/dual_simplex/basis_solves.hpp
  • cpp/src/dual_simplex/basis_updates.cpp
  • cpp/src/dual_simplex/basis_updates.hpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/src/dual_simplex/crossover.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/src/dual_simplex/cuts.hpp
  • cpp/src/dual_simplex/phase2.cpp
  • cpp/src/dual_simplex/primal.cpp
  • cpp/src/dual_simplex/pseudo_costs.cpp
  • cpp/src/dual_simplex/simplex_solver_settings.hpp
  • cpp/src/math_optimization/solver_settings.cu
  • cpp/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.h
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/src/dual_simplex/primal.cpp
  • cpp/src/dual_simplex/pseudo_costs.cpp
  • cpp/src/dual_simplex/simplex_solver_settings.hpp
  • cpp/src/dual_simplex/basis_solves.hpp
  • cpp/src/dual_simplex/basis_updates.cpp
  • cpp/include/cuopt/linear_programming/mip/solver_settings.hpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/src/dual_simplex/basis_solves.cpp
  • cpp/src/dual_simplex/crossover.cpp
  • cpp/src/dual_simplex/cuts.hpp
  • cpp/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.h
  • cpp/src/dual_simplex/simplex_solver_settings.hpp
  • cpp/src/dual_simplex/basis_solves.hpp
  • cpp/include/cuopt/linear_programming/mip/solver_settings.hpp
  • cpp/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.h
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/src/dual_simplex/primal.cpp
  • cpp/src/dual_simplex/pseudo_costs.cpp
  • cpp/src/dual_simplex/simplex_solver_settings.hpp
  • cpp/src/dual_simplex/basis_solves.hpp
  • cpp/src/dual_simplex/basis_updates.cpp
  • cpp/include/cuopt/linear_programming/mip/solver_settings.hpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/src/dual_simplex/basis_solves.cpp
  • cpp/src/dual_simplex/crossover.cpp
  • cpp/src/dual_simplex/cuts.hpp
  • cpp/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.h
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/src/dual_simplex/primal.cpp
  • cpp/src/dual_simplex/pseudo_costs.cpp
  • cpp/src/dual_simplex/simplex_solver_settings.hpp
  • cpp/src/dual_simplex/basis_solves.hpp
  • cpp/src/dual_simplex/basis_updates.cpp
  • cpp/include/cuopt/linear_programming/mip/solver_settings.hpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/src/dual_simplex/basis_solves.cpp
  • cpp/src/dual_simplex/crossover.cpp
  • cpp/src/dual_simplex/cuts.hpp
  • cpp/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.h
  • cpp/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.h
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/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.h
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/src/dual_simplex/primal.cpp
  • cpp/src/dual_simplex/pseudo_costs.cpp
  • cpp/src/dual_simplex/simplex_solver_settings.hpp
  • cpp/src/dual_simplex/basis_updates.cpp
  • cpp/include/cuopt/linear_programming/mip/solver_settings.hpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/src/dual_simplex/basis_solves.cpp
  • cpp/src/dual_simplex/crossover.cpp
  • cpp/src/dual_simplex/cuts.hpp
  • cpp/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.h
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/src/dual_simplex/pseudo_costs.cpp
  • cpp/src/dual_simplex/basis_solves.cpp
  • cpp/src/dual_simplex/cuts.hpp
  • cpp/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.h
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/src/dual_simplex/primal.cpp
  • cpp/src/dual_simplex/pseudo_costs.cpp
  • cpp/src/dual_simplex/simplex_solver_settings.hpp
  • cpp/src/dual_simplex/basis_solves.hpp
  • cpp/src/dual_simplex/basis_updates.cpp
  • cpp/include/cuopt/linear_programming/mip/solver_settings.hpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/src/dual_simplex/basis_solves.cpp
  • cpp/src/dual_simplex/crossover.cpp
  • cpp/src/dual_simplex/cuts.hpp
  • cpp/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.h
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/src/dual_simplex/pseudo_costs.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/primal.cpp
  • cpp/src/dual_simplex/basis_solves.hpp
  • cpp/src/dual_simplex/basis_updates.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/src/dual_simplex/basis_solves.cpp
  • cpp/src/dual_simplex/crossover.cpp
  • cpp/src/dual_simplex/cuts.hpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/pseudo_costs.cpp
  • cpp/src/dual_simplex/simplex_solver_settings.hpp
  • cpp/src/dual_simplex/basis_updates.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/src/dual_simplex/basis_solves.cpp
  • cpp/src/dual_simplex/crossover.cpp
  • cpp/src/dual_simplex/cuts.hpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/basis_updates.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/src/dual_simplex/basis_solves.cpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/src/dual_simplex/crossover.cpp
  • cpp/src/dual_simplex/cuts.hpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/pseudo_costs.cpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/basis_updates.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/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.cpp
  • 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} : 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.cpp
  • 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/branch_and_bound.cpp
  • 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,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
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/basis_updates.cpp
  • 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} : 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.cpp
  • cpp/src/dual_simplex/basis_updates.cpp
  • 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} : 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_t and exit but doesn’t include <cstdint> / <cstdlib> (it may compile only via transitive includes). Also, static analysis reports dual_simplex/basis_updates.hpp not found—please confirm include directories for cpp/src/dual_simplex headers are correctly configured for all targets that include cuts.hpp.

cpp/src/dual_simplex/primal.cpp (1)

299-310: Good: basis_repair now receives bounds + vstatus.

This aligns the repair step with bound-aware status assignment (NONBASIC_LOWER/UPPER/FREE). Please just double-check lp.lower/lp.upper are 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-aware refactor_basis call sites look consistent
Passing lp.lower/lp.upper into ft.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: remove exit(1) and most printf from non-debug code paths
This file contains multiple unconditional exit(1) and printf statements 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 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)

8-10: The header file cuts.hpp exists at cpp/src/dual_simplex/cuts.hpp and 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.

  1. Solution state updates are handled correctly. Immediately after add_cuts returns (lines 1885–1887 in branch_and_bound.cpp), the code explicitly resizes solution.x, solution.y, and solution.z to match the new lp.num_cols and lp.num_rows. Line 1890 clears edge_norms_, which is then passed to the next solve with the updated LP dimensions. The solution vectors remain consistent with the LP state.

  2. Dimension mismatch is not a risk. The cuts matrix has n = original_vars_ (set once at initialization and never modified), which represents only the original problem variables, not slack columns. At each add_cuts call, lp.num_cols has been reset to original_vars_ by the preceding remove_cuts call. The append_rows validation (sparse_matrix.cpp line 373–375) checks cuts.n <= lp.num_cols, which always holds because both equal original_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 vstatus classifications for fixed vars (lower==upper) are preserved (see basis_repair implementation) 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

Comment on lines +1111 to +1315
// 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;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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 to lp_status_t::NUMERICAL_ISSUES or similar),
  • route diagnostics via settings.log (or gate printf behind a dedicated debug macro),
  • add fast precondition checks (debug asserts are fine) like cuts_basic.n == L0_.m and 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.

Comment on lines +231 to +263
#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);
}
}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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.

Comment on lines +606 to +630
#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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Debug file write (solution.dat) must not be enabled by default; also fix FILE RAII and format specifiers.

  • #if 1 forces file IO during normal solves.
  • Use RAII to guarantee close (and avoid static analysis “resourceLeak” reports).
  • printf("Solution hash: %20x\n", seed); is UB for size_t on many platforms (use %zx or cast).
🧰 Tools
🪛 Cppcheck (2.19.0)

[error] 617-617: Resource leak

(resourceLeak)

Comment on lines +1381 to 1396
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;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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 iter into Phase 2 (if Phase 1 iterated), and
  • makes slack_basis selection via iter == 0 ? 1 : 0 unreliable,
  • 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.

Comment on lines +39 to +56
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;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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).

Comment on lines 570 to 643
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);
}
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 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 -100

Repository: 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.cpp

Repository: 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 -80

Repository: 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 -50

Repository: 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.cpp

Repository: 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 -30

Repository: 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 -200

Repository: 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.cpp

Repository: 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 -30

Repository: 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 -10

Repository: 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 -200

Repository: 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 -30

Repository: 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 -80

Repository: 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 -150

Repository: 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 -50

Repository: 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 -40

Repository: 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 -l

Repository: 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 -300

Repository: 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 -150

Repository: 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.cpp

Repository: 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 -100

Repository: 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 -20

Repository: 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 -A2

Repository: 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.cpp

Repository: 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 -150

Repository: 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 -100

Repository: 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 -200

Repository: 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 -50

Repository: 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 -100

Repository: 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.

Comment on lines +21 to +42
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);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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.

Suggested change
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.

Comment on lines +407 to +416
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];
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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.

Suggested change
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();
}

Comment on lines +448 to +460
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);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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: select remains -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).

Copy link

@coderabbitai coderabbitai bot left a 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 is j >= col_start.size(). This can still OOB when j == size-1. Also, col_start.size() is size_t but 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() returns i_t and 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 -1 at 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

📥 Commits

Reviewing files that changed from the base of the PR and between 49bdd7f and f11838d.

📒 Files selected for processing (5)
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/src/dual_simplex/cuts.hpp
  • cpp/src/dual_simplex/sparse_matrix.cpp
  • cpp/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.hpp
  • cpp/src/dual_simplex/sparse_matrix.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/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.hpp
  • cpp/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.hpp
  • cpp/src/dual_simplex/sparse_matrix.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/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.hpp
  • cpp/src/dual_simplex/sparse_matrix.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/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.hpp
  • cpp/src/dual_simplex/sparse_matrix.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • 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/sparse_matrix.hpp
  • cpp/src/dual_simplex/sparse_matrix.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • 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/sparse_matrix.cpp
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/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.cpp
  • 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} : 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
  • cpp/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.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/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.cpp
  • 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/branch_and_bound.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/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.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/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.cpp
  • 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} : 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.cpp
  • cpp/src/dual_simplex/cuts.cpp
  • cpp/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 returns i_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 whether nz_max is “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 exporting cpp/src/dual_simplex headers 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() uses abs_gap = upper_bound - root_obj and applies bound tightening based on reduced costs. This typically depends on minimization/maximization conventions and requires finite upper_bound. Please:

  • Skip if upper_bound is inf (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() calls sparse_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 using i_t return codes in many APIs) and route diagnostics via settings.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 codes
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 **/*.{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
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
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
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
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)
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 verification
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

Comment on lines +238 to +263
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);
}
}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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.

Comment on lines +1478 to +1507
// 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");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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.

Comment on lines +58 to +74
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;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +175 to +196
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());
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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).

Comment on lines +598 to +609
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;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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

Comment on lines +1528 to +1537
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;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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.

Comment on lines +45 to +64
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;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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.

Copy link

@coderabbitai coderabbitai bot left a 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: Fix basic_list/nonbasic_list construction: must clear() before push_back(); never exit(1) on mismatch.
Currently the vectors can already be sized (caller initializes basic_list(m)), so push_back produces 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 constructor printf(...) + 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-gate solution.dat write; fix FILE RAII + size_t printf format.
This introduces filesystem coupling in solver runs and uses %20x for size_t (UB on many platforms). Also, #if 1 forces it on.


1556-1572: Initialize root basic_list/nonbasic_list consistently with the expected fill pattern.
Given the crossover path uses push_back, the caller should not pre-size basic_list(original_lp_.num_rows). Prefer reserve + 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 unconditional exit(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 debug printf in solve_node(); avoid exact float equality checks for “fixed to bounds”.
The prints will spam in multi-threaded search. Also leaf_problem.lower[j] == leaf_problem.upper[j] is brittle for floating bounds; consider abs(ub-lb) <= eps.

Also applies to: 869-884

cpp/src/dual_simplex/cuts.cpp (5)

58-74: Guard divide-by-zero in cut_distance() / cut_orthogonality() (NaNs will poison scoring).
cut_norm (or norm_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 clear best_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: Fix slack_map default init: rows without slacks silently use column 0 as “slack”.
std::vector<i_t> slack_map(lp.num_rows); leaves unmapped rows at 0; then xstar[slack] reads xstar[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 remaining exit(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 use i_t statuses) and let the caller map to NUMERICAL/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 with sum_value<=10000, large n can explode memory.

Also applies to: 451-552

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f11838d and fb85947.

📒 Files selected for processing (2)
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • 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/branch_and_bound.cpp
  • 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/branch_and_bound.cpp
  • 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/branch_and_bound.cpp
  • cpp/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.cpp
  • 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/branch_and_bound.cpp
  • 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/branch_and_bound.cpp
  • 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/branch_and_bound.cpp
  • 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} : 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.cpp
  • 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} : 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.cpp
  • 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/branch_and_bound.cpp
  • 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/branch_and_bound.cpp
  • 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} : 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.cpp
  • 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} : 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.cpp
  • 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} : 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
  • cpp/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
  • 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/branch_and_bound.cpp
  • 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,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
  • 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/branch_and_bound.cpp
  • 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} : 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.cpp
  • 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} : 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 file dual_simplex/cuts.hpp exists and the include path is properly configured in the CMakeLists.txt (cpp/src is 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.

Comment on lines +302 to +378
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();
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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).

⚠️ Potential issue | 🔴 Critical

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).

Comment on lines +2030 to +2226
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;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

add_cuts(...): Fix inconsistent error handling; verify edge_norms is properly maintained after resizing columns.

  1. 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 -1 for other errors. This is unsafe in production/library code—replace exit(1) with proper error returns or exceptions.

  2. Edge_norms not resized: The function increases lp.num_cols by p (new slack columns) but does not resize or reinitialize edge_norms. The caller in cutting_plane_search.cpp passes edge_norms to dual_simplex_main without explicit reinitialization. If downstream code accesses edge_norms[j] for the new columns, this will cause out-of-bounds access. Confirm whether dual_simplex_main or callers handle this, or add explicit edge_norms reinitialization.

  3. vstatus invariant: Properly resized and initialized for new columns (lines 2174–2177).

  4. 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature request New feature or request non-breaking Introduces a non-breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants