Skip to content

Conversation

@nguidotti
Copy link
Contributor

@nguidotti nguidotti commented Dec 12, 2025

The goal of this PR is to improve the diving framework in the MIP solver based on [1, Section 9.2]. It introduces the following changes:

MIPLIB results (GH200):

main (#718): 226 feasible solutions with 12.8% average primal gap

All No Coefficient Diving No Pseudocost Diving No Guided Diving No Line Search Diving
Feasible Solutions 225 224 225 224 225
Optimal 44 43 45 43 44
Average Primal Gap 12.083 12.486 12.658 12.431 13.151
Average Primal Integral 8233 8508 8524 8406 8789

Reference:

[1] T. Achterberg, “Constraint Integer Programming,” PhD, Technischen Universität Berlin,
Berlin, 2007. doi: 10.14279/depositonce-1634.

Checklist

  • I am familiar with the Contributing Guidelines.
  • Testing
    • New or existing tests cover these changes
    • Added tests
    • Created an issue to follow-up
    • NA
  • Documentation
    • The documentation is up to date with these changes
    • Added new documentation
    • NA

Summary by CodeRabbit

  • New Features

    • Multiple diving heuristics (line-search, pseudocost, guided, coefficient) with selectable strategies.
    • Dual-queue node scheduler separating best-first and diving work for improved dispatch.
  • Improvements

    • Basis repair/refactor now uses variable bounds for more robust solves.
    • New diving configuration and per-worker types for finer tuning and parallelism.
    • Variable-selection gains objective-estimate support for smarter branching.
    • Logging refined and safer file logging API.

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

@nguidotti nguidotti added this to the 26.02 milestone Dec 12, 2025
@nguidotti nguidotti self-assigned this Dec 12, 2025
@nguidotti nguidotti requested review from a team as code owners December 12, 2025 15:56
@nguidotti nguidotti added non-breaking Introduces a non-breaking change improvement Improves an existing functionality labels Dec 12, 2025
@nguidotti nguidotti added the mip label Dec 12, 2025
@coderabbitai
Copy link

coderabbitai bot commented Dec 12, 2025

📝 Walkthrough

Walkthrough

Adds diving heuristics and a node_queue scheduler, replaces the legacy diving_queue, refactors B&B worker types to bnb_worker_type_t with multiple diving strategies, extends basis repair/refactor APIs to accept variable lower/upper bounds, and propagates related signature, settings, logging, and pseudo-costs changes across dual_simplex.

Changes

Cohort / File(s) Summary
Diving heuristics module
\cpp/src/dual_simplex/diving_heuristics.cpp`, `cpp/src/dual_simplex/diving_heuristics.hpp`, `cpp/src/dual_simplex/CMakeLists.txt``
New diving heuristics (line_search, pseudocost, guided, coefficient). Adds branch_variable_t and heuristic functions; source added to build.
Basis repair & refactor APIs
\cpp/src/dual_simplex/basis_solves.hpp`, `cpp/src/dual_simplex/basis_solves.cpp`, `cpp/src/dual_simplex/basis_updates.hpp`, `cpp/src/dual_simplex/basis_updates.cpp``
basis_repair and refactor_basis signatures extended to accept const std::vector<f_t>& lower, const std::vector<f_t>& upper; repair logic uses bounds to choose nonbasic status; call sites and template instantiations updated.
Node scheduling / queue
\cpp/src/dual_simplex/node_queue.hpp`, `cpp/src/dual_simplex/diving_queue.hpp` (deleted)`
Adds heap_t and node_queue_t (best-first by lower_bound, diving by objective_estimate) with synchronization; removes legacy min-heap diving_queue_t.
Branch-and-bound core
\cpp/src/dual_simplex/branch_and_bound.hpp`, `cpp/src/dual_simplex/branch_and_bound.cpp``
Major refactor: thread_type_tbnb_worker_type_t (adds PSEUDOCOST/LINE_SEARCH/GUIDED/COEFFICIENT diving types); integrates node_queue_t, Arow_, bnb_stats_t; signature and flow changes, new reporting helpers and per-worker behavior.
Repair call sites & phase2 integration
\cpp/src/dual_simplex/crossover.cpp`, `cpp/src/dual_simplex/primal.cpp`, `cpp/src/dual_simplex/phase2.cpp`, `cpp/src/dual_simplex/basis_updates.cpp`**`
Callers updated to pass lp.lower/lp.upper into basis_repair/refactor_basis. compute_initial_primal_infeasibilities gains an out param primal_inf and now returns squared infeasibility; call sites adjusted.
Pseudo-costs & helpers
\cpp/src/dual_simplex/pseudo_costs.hpp`, `cpp/src/dual_simplex/pseudo_costs.cpp``
Replaced explicit mutex lock/unlock with std::lock_guard<omp_mutex_t>; added pseudo_costs_t::obj_estimate(...); logging moved to log.debug; minor fractional calculation tweak.
Settings / callers & examples
\cpp/src/dual_simplex/simplex_solver_settings.hpp`, `cpp/src/mip/solver.cu`, `cpp/src/mip/diversity/lns/rins.cu`, `cpp/src/mip/diversity/recombiners/sub_mip.cuh``
Adds diving_heuristics_settings_t and diving_settings member; renames num_bfs_threadsnum_bfs_workers; callers updated to populate num_bfs_workers and diving_settings.num_diving_workers.
Logging, node, and minor edits
\cpp/src/dual_simplex/logger.hpp`, `cpp/src/dual_simplex/bounds_strengthening.cpp`, `cpp/src/dual_simplex/mip_node.hpp`, `cpp/src/dual_simplex/phase2.cpp`, `cpp/src/dual_simplex/primal.cpp`, `cpp/src/dual_simplex/crossover.cpp``
Logger API enable_log_to_file/set_log_file mode signature changed; printf → log.debug replacements; mip_node_t gains objective_estimate; various call sites updated to new signatures and parameters.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Diving Improvements' is generic and vague, using a broad term that doesn't clearly convey the specific technical improvements made in this comprehensive refactoring. Consider a more specific title such as 'Implement multiple diving heuristics (line search, pseudocost, guided, coefficient)' or 'Refactor diving framework with unified node queue and heuristic strategies' to better describe the main technical changes.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ 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.

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

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/phase2.cpp (1)

2837-2858: Pass primal_infeasibility_squared instead of primal_infeasibility to update_single_primal_infeasibility at line 2858.

The previous two calls to update_primal_infeasibilities (lines 2837 and 2849) correctly accumulate squared infeasibility changes into primal_infeasibility_squared. However, update_single_primal_infeasibility also operates on squared infeasibilities internally (computing infeas * infeas and accumulating via primal_inf + (new_val - old_val)), so it should update the same accumulator. Passing primal_infeasibility instead breaks infeasibility tracking—the entering variable's squared infeasibility delta is accumulated into the wrong variable, leaving primal_infeasibility_squared incomplete.

🧹 Nitpick comments (8)
cpp/src/mip/diversity/recombiners/sub_mip.cuh (1)

108-114: Diving heuristics temporarily restricted with clear intent.

The explicit configuration disables most diving heuristics (guided, coefficient, pseudocost) for SubMIP, restricting to line search diving only. The comment clearly indicates this is a temporary restriction pending future enablement of all heuristics.

Consider extracting this diving configuration pattern into a helper function since the same configuration appears in multiple files (sub_mip.cuh and rins.cu). This would reduce duplication and make future updates easier.

// Example helper
static auto create_line_search_only_diving_settings() {
  diving_heuristics_settings_t settings;
  settings.num_diving_tasks = 1;
  settings.disable_guided_diving = true;
  settings.disable_coefficient_diving = true;
  settings.disable_pseudocost_diving = true;
  return settings;
}

Based on coding guidelines: refactor code duplication in solver components.

cpp/src/mip/diversity/lns/rins.cu (1)

258-270: Branch-and-bound settings correctly updated for new diving API.

The configuration properly integrates the new diving_settings structure while preserving existing behavior for threading, tolerances, logging, and callbacks. The temporary restriction to line search diving only is clearly documented.

This file contains the same diving configuration pattern as sub_mip.cuh. Consider consolidating this repeated configuration into a shared helper function to reduce duplication.

Based on coding guidelines: refactor code duplication in solver components (3+ occurrences).

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

10-13: Consider adding explicit logger include.

logger_t& is used in all function signatures, but the header relies on transitive inclusion through pseudo_costs.hpp. For better header self-containment and faster compile times, consider adding an explicit include or forward declaration:

 #include <dual_simplex/basis_updates.hpp>
 #include <dual_simplex/bounds_strengthening.hpp>
 #include <dual_simplex/pseudo_costs.hpp>
+#include <dual_simplex/logger.hpp>
 #include <vector>
cpp/src/dual_simplex/basis_solves.cpp (1)

663-671: Logic flow issue: condition at line 667 may be unreachable in practice.

The current logic:

  1. Line 663: If lower == -inf && upper == inf → FREE
  2. Line 665: Else if lower > -inf → LOWER
  3. Line 667: Else if upper < inf → UPPER
  4. Line 669: Else → assert

At line 667, we know lower[bad_j] <= -inf (from failing line 665). Combined with failing line 663, we must have upper[bad_j] < inf, so the condition is always true when reached. The else at line 669 is dead code.

Consider simplifying:

     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);
+      vstatus[bad_j] = variable_status_t::NONBASIC_UPPER;
     }

The assert is unnecessary since the logic is exhaustive.

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

49-55: Edge case: empty fractional vector causes undefined behavior.

If fractional is empty, the fallback at line 50 accesses fractional[0] which is undefined, and the assertions at lines 54-55 will fail. While callers should ensure fractional is non-empty, defensive handling would improve robustness:

   if (round_dir == rounding_direction_t::NONE) {
+    assert(!fractional.empty() && "line_search_diving called with empty fractional list");
     branch_var = fractional[0];
     round_dir  = rounding_direction_t::DOWN;
   }
-
-  assert(round_dir != rounding_direction_t::NONE);
-  assert(branch_var >= 0);
cpp/src/dual_simplex/node_queue.hpp (3)

33-38: Incorrect use of std::forward with doubly-referenced type.

The Args&& in the template parameter already declares forwarding references. Using std::forward<Args&&> adds an extra &&, which is redundant and could cause issues with some compilers.

   template <typename... Args>
   void emplace(Args&&... args)
   {
-    buffer.emplace_back(std::forward<Args&&>(args)...);
+    buffer.emplace_back(std::forward<Args>(args)...);
     std::push_heap(buffer.begin(), buffer.end(), comp);
   }

136-146: Potential integer type narrowing in size accessors.

heap_t::size() returns size_t, but diving_queue_size() and best_first_queue_size() return i_t (typically int). For very large heaps exceeding INT_MAX, this could cause truncation.

Consider returning size_t or using static_cast with appropriate bounds checking:

-  i_t diving_queue_size()
+  size_t diving_queue_size()
   {
     std::lock_guard<omp_mutex_t> lock(mutex);
     return diving_heap.size();
   }

-  i_t best_first_queue_size()
+  size_t best_first_queue_size()
   {
     std::lock_guard<omp_mutex_t> lock(mutex);
     return best_first_heap.size();
   }

116-134: Stale entries in diving_heap cause O(n) iterations in pop_diving().

Both best_first_heap and diving_heap reference the same heap_entry_t objects via shared_ptr. When pop_best_first() executes std::exchange(entry.value()->node, nullptr) at line 110, it nullifies the entry's node pointer. However, the entry itself remains in diving_heap. Subsequent calls to pop_diving() must iterate through these stale entries (lines 122–130) until finding a valid one, resulting in O(n) complexity where n is the number of stale entries accumulated.

Consider maintaining a count of valid entries or implementing atomic removal from both heaps to avoid worst-case iteration when heavy best-first activity precedes diving operations.

📜 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 89ebcbb and 501c20d.

📒 Files selected for processing (23)
  • cpp/src/dual_simplex/CMakeLists.txt (1 hunks)
  • cpp/src/dual_simplex/basis_solves.cpp (3 hunks)
  • cpp/src/dual_simplex/basis_solves.hpp (1 hunks)
  • cpp/src/dual_simplex/basis_updates.cpp (2 hunks)
  • cpp/src/dual_simplex/basis_updates.hpp (1 hunks)
  • cpp/src/dual_simplex/bounds_strengthening.cpp (2 hunks)
  • cpp/src/dual_simplex/branch_and_bound.cpp (30 hunks)
  • cpp/src/dual_simplex/branch_and_bound.hpp (8 hunks)
  • cpp/src/dual_simplex/crossover.cpp (3 hunks)
  • cpp/src/dual_simplex/diving_heuristics.cpp (1 hunks)
  • cpp/src/dual_simplex/diving_heuristics.hpp (1 hunks)
  • cpp/src/dual_simplex/diving_queue.hpp (0 hunks)
  • cpp/src/dual_simplex/logger.hpp (1 hunks)
  • cpp/src/dual_simplex/mip_node.hpp (4 hunks)
  • cpp/src/dual_simplex/node_queue.hpp (1 hunks)
  • cpp/src/dual_simplex/phase2.cpp (12 hunks)
  • cpp/src/dual_simplex/primal.cpp (1 hunks)
  • cpp/src/dual_simplex/pseudo_costs.cpp (4 hunks)
  • cpp/src/dual_simplex/pseudo_costs.hpp (1 hunks)
  • cpp/src/dual_simplex/simplex_solver_settings.hpp (3 hunks)
  • cpp/src/mip/diversity/lns/rins.cu (1 hunks)
  • cpp/src/mip/diversity/recombiners/sub_mip.cuh (1 hunks)
  • cpp/src/mip/solver.cu (1 hunks)
💤 Files with no reviewable changes (1)
  • cpp/src/dual_simplex/diving_queue.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.hpp
  • cpp/src/dual_simplex/bounds_strengthening.cpp
  • cpp/src/dual_simplex/basis_solves.hpp
  • cpp/src/dual_simplex/logger.hpp
  • cpp/src/mip/diversity/recombiners/sub_mip.cuh
  • cpp/src/mip/diversity/lns/rins.cu
  • cpp/src/mip/solver.cu
  • cpp/src/dual_simplex/primal.cpp
  • cpp/src/dual_simplex/mip_node.hpp
  • cpp/src/dual_simplex/crossover.cpp
  • cpp/src/dual_simplex/basis_updates.cpp
  • cpp/src/dual_simplex/simplex_solver_settings.hpp
  • cpp/src/dual_simplex/pseudo_costs.hpp
  • cpp/src/dual_simplex/basis_solves.cpp
  • cpp/src/dual_simplex/diving_heuristics.hpp
  • cpp/src/dual_simplex/pseudo_costs.cpp
  • cpp/src/dual_simplex/phase2.cpp
  • cpp/src/dual_simplex/diving_heuristics.cpp
  • cpp/src/dual_simplex/node_queue.hpp
  • cpp/src/dual_simplex/branch_and_bound.hpp
  • cpp/src/dual_simplex/branch_and_bound.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/basis_updates.hpp
  • cpp/src/dual_simplex/basis_solves.hpp
  • cpp/src/dual_simplex/logger.hpp
  • cpp/src/dual_simplex/mip_node.hpp
  • cpp/src/dual_simplex/simplex_solver_settings.hpp
  • cpp/src/dual_simplex/pseudo_costs.hpp
  • cpp/src/dual_simplex/diving_heuristics.hpp
  • cpp/src/dual_simplex/node_queue.hpp
  • cpp/src/dual_simplex/branch_and_bound.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.hpp
  • cpp/src/dual_simplex/bounds_strengthening.cpp
  • cpp/src/dual_simplex/basis_solves.hpp
  • cpp/src/dual_simplex/logger.hpp
  • cpp/src/dual_simplex/primal.cpp
  • cpp/src/dual_simplex/mip_node.hpp
  • cpp/src/dual_simplex/crossover.cpp
  • cpp/src/dual_simplex/basis_updates.cpp
  • cpp/src/dual_simplex/simplex_solver_settings.hpp
  • cpp/src/dual_simplex/pseudo_costs.hpp
  • cpp/src/dual_simplex/basis_solves.cpp
  • cpp/src/dual_simplex/diving_heuristics.hpp
  • cpp/src/dual_simplex/pseudo_costs.cpp
  • cpp/src/dual_simplex/phase2.cpp
  • cpp/src/dual_simplex/diving_heuristics.cpp
  • cpp/src/dual_simplex/node_queue.hpp
  • cpp/src/dual_simplex/branch_and_bound.hpp
  • cpp/src/dual_simplex/branch_and_bound.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/basis_updates.hpp
  • cpp/src/dual_simplex/bounds_strengthening.cpp
  • cpp/src/dual_simplex/basis_solves.hpp
  • cpp/src/dual_simplex/logger.hpp
  • cpp/src/mip/diversity/lns/rins.cu
  • cpp/src/mip/solver.cu
  • cpp/src/dual_simplex/primal.cpp
  • cpp/src/dual_simplex/mip_node.hpp
  • cpp/src/dual_simplex/crossover.cpp
  • cpp/src/dual_simplex/basis_updates.cpp
  • cpp/src/dual_simplex/simplex_solver_settings.hpp
  • cpp/src/dual_simplex/pseudo_costs.hpp
  • cpp/src/dual_simplex/basis_solves.cpp
  • cpp/src/dual_simplex/diving_heuristics.hpp
  • cpp/src/dual_simplex/pseudo_costs.cpp
  • cpp/src/dual_simplex/phase2.cpp
  • cpp/src/dual_simplex/diving_heuristics.cpp
  • cpp/src/dual_simplex/node_queue.hpp
  • cpp/src/dual_simplex/branch_and_bound.hpp
  • cpp/src/dual_simplex/branch_and_bound.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/recombiners/sub_mip.cuh
  • cpp/src/mip/diversity/lns/rins.cu
  • cpp/src/mip/solver.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
  • cpp/src/mip/solver.cu
🧠 Learnings (18)
📓 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)
Learnt from: 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} : Validate algorithm correctness in optimization logic: simplex pivots, branch-and-bound decisions, routing heuristics, and constraint/objective handling must produce correct results
📚 Learning: 2025-11-25T10:20:49.822Z
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.hpp
  • cpp/src/dual_simplex/bounds_strengthening.cpp
  • cpp/src/dual_simplex/basis_solves.hpp
  • cpp/src/mip/solver.cu
  • cpp/src/dual_simplex/primal.cpp
  • cpp/src/dual_simplex/crossover.cpp
  • cpp/src/dual_simplex/basis_updates.cpp
  • cpp/src/dual_simplex/simplex_solver_settings.hpp
  • cpp/src/dual_simplex/basis_solves.cpp
  • cpp/src/dual_simplex/phase2.cpp
  • 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} : 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.hpp
  • cpp/src/dual_simplex/bounds_strengthening.cpp
  • cpp/src/dual_simplex/basis_solves.hpp
  • cpp/src/mip/diversity/recombiners/sub_mip.cuh
  • cpp/src/dual_simplex/CMakeLists.txt
  • cpp/src/mip/diversity/lns/rins.cu
  • cpp/src/mip/solver.cu
  • cpp/src/dual_simplex/primal.cpp
  • cpp/src/dual_simplex/mip_node.hpp
  • cpp/src/dual_simplex/crossover.cpp
  • cpp/src/dual_simplex/basis_updates.cpp
  • cpp/src/dual_simplex/simplex_solver_settings.hpp
  • cpp/src/dual_simplex/basis_solves.cpp
  • cpp/src/dual_simplex/diving_heuristics.hpp
  • cpp/src/dual_simplex/pseudo_costs.cpp
  • cpp/src/dual_simplex/phase2.cpp
  • cpp/src/dual_simplex/diving_heuristics.cpp
  • 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} : 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.hpp
  • cpp/src/dual_simplex/bounds_strengthening.cpp
  • cpp/src/dual_simplex/basis_solves.hpp
  • cpp/src/dual_simplex/CMakeLists.txt
  • cpp/src/mip/diversity/lns/rins.cu
  • cpp/src/mip/solver.cu
  • cpp/src/dual_simplex/primal.cpp
  • cpp/src/dual_simplex/crossover.cpp
  • cpp/src/dual_simplex/basis_updates.cpp
  • cpp/src/dual_simplex/simplex_solver_settings.hpp
  • cpp/src/dual_simplex/pseudo_costs.hpp
  • cpp/src/dual_simplex/basis_solves.cpp
  • cpp/src/dual_simplex/diving_heuristics.hpp
  • cpp/src/dual_simplex/pseudo_costs.cpp
  • cpp/src/dual_simplex/phase2.cpp
  • cpp/src/dual_simplex/diving_heuristics.cpp
  • cpp/src/dual_simplex/node_queue.hpp
  • 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} : 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/bounds_strengthening.cpp
  • cpp/src/mip/solver.cu
  • cpp/src/dual_simplex/crossover.cpp
  • cpp/src/dual_simplex/basis_solves.cpp
  • cpp/src/dual_simplex/diving_heuristics.hpp
  • cpp/src/dual_simplex/pseudo_costs.cpp
  • cpp/src/dual_simplex/phase2.cpp
  • cpp/src/dual_simplex/diving_heuristics.cpp
  • 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 **/*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/bounds_strengthening.cpp
  • cpp/src/dual_simplex/CMakeLists.txt
  • cpp/src/mip/diversity/lns/rins.cu
  • cpp/src/mip/solver.cu
  • cpp/src/dual_simplex/primal.cpp
  • cpp/src/dual_simplex/crossover.cpp
  • cpp/src/dual_simplex/basis_updates.cpp
  • cpp/src/dual_simplex/simplex_solver_settings.hpp
  • cpp/src/dual_simplex/basis_solves.cpp
  • cpp/src/dual_simplex/diving_heuristics.hpp
  • cpp/src/dual_simplex/phase2.cpp
  • cpp/src/dual_simplex/diving_heuristics.cpp
  • 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} : 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/bounds_strengthening.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 **/*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/bounds_strengthening.cpp
  • 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,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/bounds_strengthening.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} : 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/CMakeLists.txt
  • cpp/src/mip/diversity/lns/rins.cu
  • cpp/src/mip/solver.cu
  • cpp/src/dual_simplex/simplex_solver_settings.hpp
  • cpp/src/dual_simplex/diving_heuristics.hpp
  • cpp/src/dual_simplex/diving_heuristics.cpp
  • cpp/src/dual_simplex/node_queue.hpp
  • 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} : 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 **/*.{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/dual_simplex/CMakeLists.txt
  • cpp/src/mip/diversity/lns/rins.cu
  • 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/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,cpp,hpp,h} : Track GPU device memory allocations and deallocations to prevent memory leaks; ensure cudaMalloc/cudaFree balance and cleanup of streams/events

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 **/*benchmark*.{cpp,cu,py} : Include performance benchmarks and regression detection for GPU operations; verify near real-time performance on million-variable problems

Applied to files:

  • cpp/src/dual_simplex/CMakeLists.txt
📚 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/diving_heuristics.hpp
  • cpp/src/dual_simplex/phase2.cpp
  • cpp/src/dual_simplex/diving_heuristics.cpp
  • 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 **/*.{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/mip/solver.cu
  • cpp/src/dual_simplex/pseudo_costs.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/basis_solves.cpp
  • cpp/src/dual_simplex/branch_and_bound.hpp
🧬 Code graph analysis (11)
cpp/src/dual_simplex/basis_updates.hpp (1)
cpp/src/dual_simplex/presolve.cpp (3)
  • lower (240-240)
  • upper (85-85)
  • upper (241-241)
cpp/src/dual_simplex/logger.hpp (1)
cpp/src/dual_simplex/simplex_solver_settings.hpp (2)
  • log_filename (99-99)
  • log_filename (99-99)
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/crossover.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/basis_updates.cpp (3)
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/basis_updates.hpp (1)
  • A (374-380)
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/diving_heuristics.hpp (2)
cpp/src/dual_simplex/pseudo_costs.hpp (2)
  • fractional (46-49)
  • fractional (51-52)
cpp/src/dual_simplex/simplex_solver_settings.hpp (2)
  • log (98-98)
  • log (100-100)
cpp/src/dual_simplex/pseudo_costs.cpp (1)
cpp/src/dual_simplex/pseudo_costs.hpp (2)
  • fractional (46-49)
  • fractional (51-52)
cpp/src/dual_simplex/phase2.cpp (1)
cpp/src/dual_simplex/primal.cpp (2)
  • primal_infeasibility (202-240)
  • primal_infeasibility (202-205)
cpp/src/dual_simplex/node_queue.hpp (1)
cpp/src/dual_simplex/mip_node.hpp (4)
  • lower (97-108)
  • lower (97-99)
  • lower (112-130)
  • lower (112-114)
cpp/src/dual_simplex/branch_and_bound.hpp (1)
cpp/src/dual_simplex/node_queue.hpp (4)
  • node (21-25)
  • node (21-21)
  • node (27-31)
  • node (27-27)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.0.2, 3.11, arm64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.0.2, 3.13, arm64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.0.2, 3.10, amd64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.0.2, 3.13, amd64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.0.2, 3.11, amd64, rockylinux8
  • GitHub Check: wheel-build-cuopt-sh-client / 13.0.2, 3.10, amd64, rockylinux8
  • GitHub Check: checks / check-style
🔇 Additional comments (39)
cpp/src/dual_simplex/CMakeLists.txt (1)

34-34: LGTM!

The addition of diving_heuristics.cpp to the build aligns with the PR objectives to introduce new diving heuristic strategies.

cpp/src/dual_simplex/bounds_strengthening.cpp (2)

157-164: Logging level correctly adjusted to debug.

The change from printf to settings.log.debug() appropriately reduces stdout noise while preserving diagnostic information for debugging. This is proper logging hygiene for infeasibility detection during bounds strengthening iterations.


214-216: Logging level correctly adjusted to debug.

Consistent with the previous logging change, this infeasibility message is appropriately moved to debug level logging.

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

301-309: All basis_repair call sites have been consistently updated with bound parameters.

The call in primal.cpp at lines 301-309 correctly passes lp.lower and lp.upper as the 3rd and 4th parameters. Verification confirms this pattern is consistent across all five basis_repair call sites in the codebase:

  • primal.cpp:301 - passes lp.lower, lp.upper
  • crossover.cpp:788, 1142, 1341 - all pass lp.lower, lp.upper
  • basis_updates.cpp:2071 - passes lower, upper (local variables in templated context)

This ensures bounds-aware basis repair during algorithm phase transitions (simplex, crossover phases).

cpp/src/mip/solver.cu (1)

176-180: Threading configuration correctly refactored for new diving_settings structure.

The code properly updates the thread distribution logic to use the new diving_settings.num_diving_tasks field instead of the previous num_diving_threads. The thread allocation strategy remains unchanged (BFS threads = num_threads/4, diving tasks = remaining threads).

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

46-49: API change correctly expands pseudo-cost functionality.

The function signature update from variable_selection to variable_selection_and_obj_estimate properly reflects the expanded responsibility: returning both the selected variable index and an objective estimate. The addition of the lower_bound parameter enables more sophisticated objective estimation for diving heuristics.

All call sites have been correctly updated. The old variable_selection function is preserved as a backward-compatibility wrapper that properly unpacks the returned pair via std::tie, stores the objective estimate in node state for search tree ordering, and returns the branch decision. New call sites directly use variable_selection_and_obj_estimate with proper structured binding to capture both returned elements.

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

33-44: LGTM! Backward-compatible API refinement.

The change from std::string to const char* for the mode parameter avoids unnecessary string construction when passing literals. The default value "w" ensures backward compatibility with existing callers like set_log_filename that don't specify a mode.

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

42-51: LGTM! Bound-aware basis repair signature.

The addition of lower and upper bound vectors enables basis_repair to correctly assign variable status (NONBASIC_FREE, NONBASIC_LOWER, or NONBASIC_UPPER) for variables being removed from the basis. This is essential for maintaining dual feasibility during repair operations. Based on coding guidelines, this correctly validates variable bounds during algorithm state transitions.

cpp/src/dual_simplex/crossover.cpp (3)

788-796: LGTM! Correct bound propagation to basis repair.

The lp.lower and lp.upper bounds are correctly passed to basis_repair, enabling proper variable status assignment during the primal push refactoring phase.


1142-1150: LGTM! Consistent bound handling in crossover initialization.

Bounds are correctly propagated during the initial basis repair in the crossover function, consistent with the other call sites.


1341-1349: LGTM! Bounds passed after phase 1 completion.

The third call site correctly passes bounds after phase 1 completes, maintaining consistency across all basis_repair invocations in the crossover workflow.

cpp/src/dual_simplex/basis_updates.cpp (2)

2046-2054: LGTM! Signature update matches declaration.

The refactor_basis implementation correctly accepts the new lower and upper bound parameters, consistent with the header declaration in basis_updates.hpp.


2071-2072: LGTM! Bounds correctly forwarded to basis_repair.

When initial factorization fails, the bounds are properly passed to basis_repair to enable correct variable status assignment during the repair process. Based on coding guidelines, this ensures correct initialization of variable bounds when transitioning between algorithm phases.

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

374-380: Verify all callers of refactor_basis have been updated to pass the new bound parameters.

The refactor_basis declaration correctly adds lower and upper bound vectors as const references. All callers in phase2.cpp (lines 2248, 2910, 2917) have been properly updated to pass lp.lower and lp.upper. This is a breaking API change requiring all callers to be updated.

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

22-34: Well-structured diving configuration.

The new diving_heuristics_settings_t struct cleanly encapsulates diving-related parameters with sensible defaults. The separation of heuristic enable/disable flags and resource limits (node_limit, iteration_limit_factor, backtrack) follows good configuration design practices.

cpp/src/dual_simplex/pseudo_costs.cpp (2)

202-202: Good improvement: RAII-based locking.

Using std::lock_guard<omp_mutex_t> ensures the mutex is properly released even if an exception occurs or the function returns early. This is a safer pattern than manual lock/unlock.


256-321: Correct implementation of pseudocost-based objective estimate.

The function correctly:

  1. Uses RAII locking for thread safety
  2. Computes per-variable pseudocosts with fallback to averages for uninitialized values
  3. Applies the reliability branching score formula (product of down/up estimates)
  4. Accumulates a conservative objective estimate using the minimum of up/down contributions

The algorithm aligns with standard MIP solver literature (Achterberg Sect. 6.4).

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

17-47: Clean diving heuristics API design.

The header provides a well-organized interface for the four diving strategies from Achterberg's thesis:

  • Line search diving (Sect. 9.2.4)
  • Pseudocost diving (Sect. 9.2.5)
  • Guided diving (Sect. 9.2.3)
  • Coefficient diving (Sect. 9.2.1)

The branch_variable_t return type cleanly bundles the selected variable with its rounding direction.

cpp/src/dual_simplex/diving_heuristics.cpp (3)

66-139: Correct pseudocost diving implementation with thread safety.

The implementation correctly:

  1. Acquires mutex lock before accessing shared pseudocost data
  2. Uses fallback averages for uninitialized pseudocosts
  3. Applies the scoring formula from Achterberg Sect. 9.2.5
  4. Uses threshold-based direction selection (0.4 for root distance, 0.3/0.7 for fractionality)

The magic constants are standard heuristic values from MIP literature.


141-197: Correct guided diving implementation.

The function properly implements Achterberg's guided diving (Sect. 9.2.3):

  • Direction determined by proximity to incumbent solution
  • Score weighted 5:1 in favor of the preferred direction
  • Thread-safe pseudocost access

Note the same empty fractional edge case applies here.


229-273: Correct coefficient diving implementation.

The function implements Achterberg's coefficient diving (Sect. 9.2.1):

  • Selects variable with minimum constraint locks
  • Prefers rounding direction that creates fewer constraint violations
  • Uses fractionality as tie-breaker when locks are equal

The same empty fractional edge case consideration applies.

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

613-622: Signature extension for bound-aware basis repair is complete and correctly integrated.

The addition of lower and upper bound vectors enables correct variable status assignment during basis repair. All call sites have been updated to pass the bound vectors:

  • primal.cpp (1 call)
  • crossover.cpp (3 calls)
  • basis_updates.cpp (1 call)

All invocations properly pass lp.lower and lp.upper (or local lower/upper from the calling context). The signature change aligns with the PR's goal of propagating bound information through solver phases.

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

60-101: Well-structured dual-heap design for unified node scheduling.

The node_queue_t class effectively unifies best-first and diving heaps with shared entries, aligning with the PR objective to reduce memory consumption. The use of shared_ptr<heap_entry_t> for cross-heap entry sharing is a clean approach.

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

620-651: Signature change correctly separates squared and linear infeasibility.

The updated compute_initial_primal_infeasibilities now properly computes both forms:

  • Returns primal_inf_squared (sum of squared infeasibilities)
  • Outputs primal_inf (sum of linear infeasibilities) via reference

This distinction is important for different algorithmic needs (squared for steepest-edge pricing weights, linear for feasibility checks). The initialization of squared_infeasibilities vector at lines 631-632 ensures clean state.


2365-2373: Proper initialization of infeasibility tracking variables.

The new code correctly initializes primal_infeasibility and primal_infeasibility_squared separately, with compute_initial_primal_infeasibilities populating both values. This ensures clean state before the main iteration loop.


2248-2251: All refactor_basis call sites are consistent with the function signature.

Verification confirms that all three call sites in phase2.cpp (lines 2248, 2910–2911, and 2917–2918) correctly include the lp.lower and lp.upper bound parameters matching the function signature in basis_updates.hpp and basis_updates.cpp.

cpp/src/dual_simplex/branch_and_bound.hpp (4)

56-67: Clear documentation and well-structured enum for diving strategies.

The bnb_thread_type_t enum provides clear categorization with references to the Achterberg dissertation sections. This aligns well with the PR objective of implementing multiple diving heuristics.


75-86: Thread-safe statistics structure with atomic counters.

The bnb_stats_t struct appropriately uses omp_atomic_t for counters that may be updated from multiple threads. The last_log and nodes_since_last_log members with the comment about main-thread-only usage help clarify the intended access pattern.


182-184: Unified node queue replaces separate heap structures.

The replacement of the previous heap-based structures with node_queue_t aligns with the PR objective to "unify diving and best-first heaps into a single node_queue object." This should help reduce memory consumption as mentioned in the PR description.


261-266: variable_selection return type and parameters are well-designed.

Returning branch_variable_t<i_t> which includes both the variable index and rounding direction is cleaner than having multiple output parameters. The additional logger_t& log parameter enables per-call logging control.

cpp/src/dual_simplex/branch_and_bound.cpp (9)

558-577: Martin's criteria implementation for rounding direction.

The martin_criteria function correctly implements the rounding heuristic based on distance from the root relaxation value. The epsilon tolerance (1e-6) for tie-breaking is reasonable.


579-619: Clean dispatch pattern for variable selection strategies.

The variable_selection method uses a clear switch statement to dispatch to different diving heuristics. The objective estimate calculation is correctly scoped to only the EXPLORATION case where it's needed for heap ordering.


640-643: Appropriate fallback from GUIDED_DIVING to PSEUDOCOST_DIVING.

When there's no incumbent solution (upper_bound == inf), guided diving cannot function since it relies on the incumbent for guidance. Falling back to pseudocost diving is a sensible default.


656-661: Iteration budget enforcement for diving threads.

The iteration limit for diving threads is computed as a factor of total B&B LP iterations minus iterations already consumed by this dive. This prevents diving from consuming too many iterations.

However, if stats.total_lp_iters is close to max_iter, this could return ITERATION_LIMIT very early. Verify that the iteration_limit_factor setting provides sufficient budget for meaningful dives.


1143-1219: Deep diving implementation with backtrack limit.

The dive_from method implements the diving heuristics with proper backtracking control. Key observations:

  1. Lines 1163-1166: Local dive_stats properly tracks per-dive statistics
  2. Lines 1179-1180: Time and node limits checked within the dive loop
  3. Lines 1215-1218: Backtrack limit enforces shallow backtracking by removing nodes from the back of the stack when depth difference exceeds the limit

The algorithm correctly prioritizes depth-first exploration while limiting the breadth of backtracking.


1248-1255: Bounds propagation for diving thread efficiency.

The code correctly retrieves variable bounds from the node and applies bounds strengthening before diving. The reset_starting_bounds flag ensures bounds are properly reset when starting from a new node.


1518-1535: Diving strategy configuration is flexible and well-structured.

The diving strategies are populated based on settings, allowing users to disable specific strategies. The reserve(4) call is appropriate given the four possible strategies.


251-260: Improved lower bound computation uses node_queue.

The get_lower_bound method correctly combines:

  1. lower_bound_ceiling_ (for numerical issue tracking)
  2. node_queue.get_lower_bound() (heap minimum)
  3. local_lower_bounds_ (per-thread tracking)

This provides an accurate global lower bound across all active exploration paths.


1354-1354: Row-major matrix initialization for bounds strengthening.

Converting the constraint matrix to row-major format (Arow_) enables efficient row-based access during bounds strengthening. This is initialized once at the start of solve(), avoiding repeated conversions.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
cpp/src/dual_simplex/pseudo_costs.cpp (2)

199-214: Guard against divide-by-zero / near-zero frac in update_pseudo_costs().

change_in_obj / frac can explode if node_ptr->fractional_val is numerically (near-)integral (or if caller misclassifies), producing inf/NaN pseudo-costs that then poison selection/estimates. As per coding guidelines, this is a numerical stability footgun.

Proposed fix
 void pseudo_costs_t<i_t, f_t>::update_pseudo_costs(mip_node_t<i_t, f_t>* node_ptr,
                                                    f_t leaf_objective)
 {
   std::lock_guard<omp_mutex_t> lock(mutex);
   const f_t change_in_obj = leaf_objective - node_ptr->lower_bound;
   const f_t frac          = node_ptr->branch_dir == rounding_direction_t::DOWN
                               ? node_ptr->fractional_val - std::floor(node_ptr->fractional_val)
                               : std::ceil(node_ptr->fractional_val) - node_ptr->fractional_val;
+  constexpr f_t eps = 1e-9;
+  if (!std::isfinite(change_in_obj) || frac <= eps) { return; }

   if (node_ptr->branch_dir == rounding_direction_t::DOWN) {
     pseudo_cost_sum_down[node_ptr->branch_var] += change_in_obj / frac;
     pseudo_cost_num_down[node_ptr->branch_var]++;
   } else {
     pseudo_cost_sum_up[node_ptr->branch_var] += change_in_obj / frac;
     pseudo_cost_num_up[node_ptr->branch_var]++;
   }
 }

Based on learnings, keeping pseudo-cost state finite is important before transitioning/using it in diving/branching.


255-317: Fix potential crash when fractional is empty + score[-1] access (cppcheck Line 314).

  • branch_var = fractional[0] is UB if fractional.empty().
  • select starts at -1, and log.debug(... score[select]) can dereference score[-1] if the loop doesn’t run (empty), which matches the static analysis warning.
Proposed fix
 i_t pseudo_costs_t<i_t, f_t>::variable_selection(const std::vector<i_t>& fractional,
                                                  const std::vector<f_t>& solution,
                                                  logger_t& log)
 {
   std::lock_guard<omp_mutex_t> lock(mutex);

   const i_t num_fractional = fractional.size();
+  if (num_fractional == 0) {
+    log.debug("PC: no fractional variables; skipping pseudocost branching.\n");
+    return -1;
+  }

   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 branch_var = fractional[0];
-  f_t max_score  = -1;
-  i_t select     = -1;
+  i_t branch_var = -1;
+  f_t max_score  = -std::numeric_limits<f_t>::infinity();
+  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.debug("Pseudocost branching on %d. Value %e. Score %e.\n",
-            branch_var,
-            solution[branch_var],
-            score[select]);
+  if (select >= 0) {
+    log.debug("Pseudocost branching on %d. Value %e. Score %e.\n",
+              branch_var,
+              solution[branch_var],
+              score[select]);
+  }

   return branch_var;
 }

As per coding guidelines, this is a correctness/stability issue in solver decision logic.

🤖 Fix all issues with AI agents
In @cpp/src/dual_simplex/pseudo_costs.cpp:
- Around line 319-362: The obj_estimate() logic misinterprets negative
pseudo-costs (e.g., pseudo_cost_down < 0) by forcing their contribution to eps,
which hides beneficial branches; fix by ensuring pseudo-costs are non-negative:
clamp pseudo_cost values to >= 0 when they are recorded in update() (or at the
point you compute them), and in obj_estimate() compute
pseudo_cost_down/pseudo_cost_up as std::max(original, (f_t)0) before multiplying
with f_down/f_up. Also reduce lock contention by copying the necessary
arrays/averages (pseudo_cost_sum_down, pseudo_cost_sum_up, pseudo_cost_num_down,
pseudo_cost_num_up, pseudo_cost_down_avg, pseudo_cost_up_avg) while holding
mutex and then releasing it before the loop that iterates fractional; keep
log.debug after the loop as now but using the local estimate 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 b99a9c7 and d4c9d54.

📒 Files selected for processing (1)
  • cpp/src/dual_simplex/pseudo_costs.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/pseudo_costs.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/pseudo_costs.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/pseudo_costs.cpp
🧠 Learnings (16)
📓 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-11-25T10:20:49.822Z
Learnt from: 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/pseudo_costs.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/pseudo_costs.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/pseudo_costs.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-11-25T10:20:49.822Z
Learnt from: 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
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: 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/dual_simplex/pseudo_costs.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} : Track GPU device memory allocations and deallocations to prevent memory leaks; ensure cudaMalloc/cudaFree balance and cleanup of streams/events

Applied to files:

  • cpp/src/dual_simplex/pseudo_costs.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 race conditions are absent in multi-GPU code and multi-threaded server implementations; verify proper synchronization of shared state

Applied to files:

  • cpp/src/dual_simplex/pseudo_costs.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/pseudo_costs.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/pseudo_costs.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/pseudo_costs.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 : Verify race conditions and correctness of GPU kernel shared memory, atomics, and warp-level operations

Applied to files:

  • cpp/src/dual_simplex/pseudo_costs.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/pseudo_costs.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} : Eliminate unnecessary host-device synchronization (cudaDeviceSynchronize) in hot paths that blocks GPU pipeline; use streams and events for async execution

Applied to files:

  • cpp/src/dual_simplex/pseudo_costs.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} : For concurrent CUDA operations (barriers, async operations), explicitly create and manage dedicated streams instead of reusing the default stream; document stream lifecycle

Applied to files:

  • cpp/src/dual_simplex/pseudo_costs.cpp
🧬 Code graph analysis (1)
cpp/src/dual_simplex/pseudo_costs.cpp (1)
cpp/src/dual_simplex/pseudo_costs.hpp (4)
  • num_initialized_down (41-44)
  • fractional (46-48)
  • fractional (50-53)
  • fractional (55-56)
🪛 Cppcheck (2.19.0)
cpp/src/dual_simplex/pseudo_costs.cpp

[warning] 314-314: Array index -1 is out of bounds.

(negativeContainerIndex)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.1.0, 3.11, arm64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.1.0, 3.12, arm64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.1.0, 3.10, arm64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.1.0, 3.11, amd64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.1.0, 3.13, arm64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.1.0, 3.10, amd64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.1.0, 3.12, amd64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.1.0, 3.13, amd64, rockylinux8
  • GitHub Check: wheel-build-cuopt-sh-client / 13.1.0, 3.10, amd64, rockylinux8
  • GitHub Check: checks / check-style
🔇 Additional comments (1)
cpp/src/dual_simplex/pseudo_costs.cpp (1)

3-3: Copyright year bump looks fine.

@nguidotti nguidotti mentioned this pull request Jan 9, 2026
8 tasks
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: 1

🤖 Fix all issues with AI agents
In @cpp/src/dual_simplex/branch_and_bound.cpp:
- Around line 22-23: exploration_stats_ is mutated concurrently by OpenMP tasks
causing data races; add a mutex (e.g., mutex_stats_) as a member and use it to
guard all reads/writes to exploration_stats_ in parallel regions (wrap accesses
in exploration_ramp_up, solve_node and any task bodies with
std::lock_guard<std::mutex> or std::scoped_lock), mirroring the pattern used by
mutex_upper_ for upper_bound_, and ensure increments/decrements of
nodes_unexplored, nodes_explored, nodes_since_last_log and updates to last_log
are performed while holding the lock.
📜 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 d4c9d54 and 0f6285c.

📒 Files selected for processing (2)
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/src/mip/diversity/lns/rins.cu
🧰 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/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
**/*.{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/lns/rins.cu
  • cpp/src/dual_simplex/branch_and_bound.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/mip/diversity/lns/rins.cu
  • cpp/src/dual_simplex/branch_and_bound.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
🧠 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-11-25T10:20:49.822Z
Learnt from: 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/lns/rins.cu
  • 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/diversity/lns/rins.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/mip/diversity/lns/rins.cu
  • 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 race conditions are absent in multi-GPU code and multi-threaded server implementations; verify proper synchronization of shared state

Applied to files:

  • cpp/src/mip/diversity/lns/rins.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 **/*.{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/mip/diversity/lns/rins.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/mip/diversity/lns/rins.cu
  • 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 : Verify race conditions and correctness of GPU kernel shared memory, atomics, and warp-level operations

Applied to files:

  • cpp/src/mip/diversity/lns/rins.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/mip/diversity/lns/rins.cu
  • 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/mip/diversity/lns/rins.cu
  • 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} : For concurrent CUDA operations (barriers, async operations), explicitly create and manage dedicated streams instead of reusing the default stream; document stream lifecycle

Applied to files:

  • cpp/src/mip/diversity/lns/rins.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} : Eliminate unnecessary host-device synchronization (cudaDeviceSynchronize) in hot paths that blocks GPU pipeline; use streams and events for async execution

Applied to files:

  • cpp/src/mip/diversity/lns/rins.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,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/diversity/lns/rins.cu
  • 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/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
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: 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} : 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
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: 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} : 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
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: 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
📚 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
📚 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
🧬 Code graph analysis (2)
cpp/src/mip/diversity/lns/rins.cu (1)
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/src/dual_simplex/branch_and_bound.cpp (1)
cpp/src/dual_simplex/branch_and_bound.hpp (6)
  • node_ptr (250-262)
  • node_ptr (265-269)
  • solution (116-116)
  • solution (134-134)
  • solution (204-204)
  • node (218-218)
🪛 Clang (14.0.6)
cpp/src/dual_simplex/branch_and_bound.cpp

[error] 8-8: 'dual_simplex/branch_and_bound.hpp' file not found

(clang-diagnostic-error)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: wheel-build-cuopt-sh-client / 13.1.0, 3.10, amd64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.1.0, 3.10, amd64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.1.0, 3.11, amd64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.1.0, 3.12, arm64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.1.0, 3.13, amd64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.1.0, 3.11, arm64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.1.0, 3.10, arm64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.1.0, 3.12, amd64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.1.0, 3.13, arm64, rockylinux8
🔇 Additional comments (24)
cpp/src/dual_simplex/branch_and_bound.cpp (20)

197-207: LGTM – Worker type-aware logging symbols.

The function correctly maps worker types to logging symbols. All diving strategies share "D " while exploration uses "B ", which aligns with the unified diving framework.


253-262: LGTM – Node queue integration for lower bound tracking.

The method correctly computes the lower bound using node_queue.get_lower_bound() and combines it with local thread bounds and the ceiling. The fallback to -inf for non-finite values is appropriate.


264-283: LGTM – Heuristic solution reporting.

The method appropriately formats and logs heuristic solutions with different output based on solver state. The gap calculation and user objective conversion are correct.


286-307: LGTM – Consolidated progress reporting.

The method centralizes reporting logic with clear formatting. The statistics computation (iterations per node, gap calculation) is correct and aligns with the updated logging format mentioned in PR objectives.


561-580: LGTM – Martin's criteria implementation.

The function correctly implements Martin's preferred rounding direction criterion from the cited reference. The epsilon-based comparison (1e-6) provides appropriate tie-breaking for near-equidistant cases.


583-621: LGTM – Worker type-aware variable selection.

The method correctly dispatches to different branching strategies based on worker type. The exploration path appropriately combines pseudocost selection with Martin's criteria, while diving workers use their respective heuristics. The objective estimate computation (lines 601-602) is correctly scoped to exploration workers only.


643-649: LGTM – Guided diving fallback when no incumbent.

The fallback logic correctly switches guided diving to pseudocost diving (or coefficient diving if pseudocost is disabled) when no incumbent solution exists. This is necessary since guided diving requires an incumbent to guide the search.


779-785: LGTM – Appropriate callback scoping for exploration workers.

Restricting the node_processed_callback to exploration workers is correct, as diving workers are speculative and their node explorations shouldn't trigger external callbacks.


800-802: LGTM – Defensive assertions after variable selection.

The assertions correctly validate that variable_selection returned a valid branching variable and rounding direction, catching potential logic errors in the selection strategies.


941-1053: LGTM – Best-first plunging implementation.

The plunge_from method correctly implements best-first search with depth-first plunging. Key strengths:

  • Proper basis reuse tracking via recompute_bounds_and_basis
  • Local lower bound updates for thread-specific tracking
  • Stack-based sibling management with global queue overflow
  • Appropriate time and node limit checks

1056-1118: LGTM – Best-first search worker thread.

The method implements a proper worker thread for best-first search:

  • Correct work availability check combining active_subtrees_ and queue size
  • Appropriate cutoff check before plunging (lines 1080-1088)
  • Proper completion detection when all threads finish (lines 1111-1117)

1121-1197: LGTM – Diving heuristic implementation with proper limits.

The dive_from method correctly implements diving heuristics with:

  • Isolated subtree for speculative exploration (line 1135)
  • Separate statistics tracking to avoid contaminating exploration metrics
  • Node, time, and iteration limit enforcement (lines 1157, 1156, 1179-1180)
  • Backtrack limit based on depth (lines 1192-1195) to force branch exploration

The implementation aligns well with the diving framework described in PR objectives.


1200-1245: LGTM – Diving worker thread with proper bound management.

The diving thread correctly manages variable bounds across multiple dives:

  • Starting bounds are reset between dives (lines 1218-1223)
  • pop_diving populates bounds along the node's path (line 1226)
  • Bounds strengthening is applied before diving (line 1231)
  • Feasibility check prevents diving into infeasible space (line 1232)

1331-1331: LGTM – Arow_ properly initialized before parallel execution.

The constraint matrix is correctly converted to compressed row format and stored in Arow_ before spawning worker threads, ensuring all threads have access to properly initialized data.


1333-1354: LGTM – Flexible diving strategy configuration.

The conditional assembly of diving_strategies based on settings flags (lines 1336-1350) allows fine-grained control over which diving heuristics are active. The warning when all strategies are disabled (lines 1352-1354) helps catch configuration errors.


1499-1503: LGTM – Clear threading configuration logging.

The log message clearly communicates the thread allocation between best-first and diving workers, aiding in performance tuning and debugging.


1513-1515: LGTM – Table header updated for two-letter symbols.

The table header format correctly accommodates two-letter symbols (line 1513 shows " |" at start), aligning with PR objective #714.


1517-1548: LGTM – OpenMP task-based parallel branch-and-bound.

The parallel structure correctly uses:

  • Master section for centralized task spawning (line 1519)
  • Taskgroup to synchronize ramp-up before launching worker tasks (lines 1526-1533)
  • Separate task loops for BFS workers (lines 1535-1538) and diving workers (lines 1540-1546)
  • Round-robin assignment of diving strategies (line 1542)

The structure ensures proper initialization order and work distribution.


662-667: Iteration limit for diving workers is intentionally proportional to exploration effort.

The 5% iteration_limit_factor (lines 664–665) is a designed trade-off where diving heuristics receive a budget proportional to the main exploration phase's iteration count. This is a reasonable resource allocation strategy, not a bug. Diving is secondary to branch-and-bound exploration and starts only after min_node_depth = 5, mitigating the early-search constraint you noted.

If you believe accounting for dive depth or objective improvement would yield better heuristic quality, that would be a valid optimization to explore, but the current behavior is correct and intentional.


224-224: No action required. Arow_ is correctly initialized at line 1331 via original_lp_.A.to_compressed_row(Arow_) before OpenMP tasks are spawned. The worker methods (exploration_ramp_up, best_first_thread, diving_thread) that use Arow_ are invoked only after this initialization.

Likely an incorrect or invalid review comment.

cpp/src/mip/diversity/lns/rins.cu (4)

263-268: LGTM – Intentional restriction to guided diving for RINS.

The configuration appropriately restricts RINS sub-MIPs to guided diving only, with a clear comment (lines 263-264) indicating this is a deliberate temporary choice with plans for future expansion. This is reasonable for RINS since:

  • RINS problems are already restricted by variable fixing
  • Guided diving can leverage the incumbent from the main MIP
  • Reducing diving diversity decreases overhead in frequent RINS calls

260-261: LGTM – Threading configuration updated to new API.

The changes correctly update from the deprecated num_bfs_threads to num_bfs_workers (line 261) and introduce the structured diving_settings.num_diving_workers (line 265), maintaining the same 2-thread configuration (1 BFS + 1 diving).


271-274: LGTM – Solution callback correctly captures queue.

The lambda callback correctly captures rins_solution_queue by reference and accepts the standard two-parameter signature (solution, objective), consistent with the callback pattern shown in cpp/src/mip/solver.cu lines 63-67.


259-268: No action needed—the fallback concern does not apply here.

RINS always calls set_initial_guess() before solve() (line 277), providing the refined incumbent as the initial guess. This ensures upper_bound != inf when the fallback logic at branch_and_bound.cpp lines 643–649 is evaluated, so the fallback case (no incumbent) never occurs. The configuration is correct because RINS is a refinement heuristic that inherently operates with an incumbent solution.

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

🧹 Nitpick comments (4)
cpp/src/dual_simplex/branch_and_bound.cpp (1)

801-806: Add defensive check for empty fractional list.

If variable_selection returns branch_var = -1 (e.g., due to an empty fractional list in an edge case), the assertions on lines 805-806 will trigger. Consider adding a defensive check before the assertions or ensuring fractional is never empty at this point.

♻️ Suggested defensive check
       // Choose fractional variable to branch on
       auto [branch_var, round_dir] =
         variable_selection(node_ptr, leaf_fractional, leaf_solution.x, thread_type);

       assert(leaf_vstatus.size() == leaf_problem.num_cols);
-      assert(branch_var >= 0);
-      assert(round_dir != rounding_direction_t::NONE);
+      if (branch_var < 0 || round_dir == rounding_direction_t::NONE) {
+        log.printf("Warning: variable_selection failed for node %d\n", node_ptr->node_id);
+        search_tree.update(node_ptr, node_status_t::NUMERICAL);
+        return node_solve_info_t::NUMERICAL;
+      }
cpp/src/dual_simplex/node_queue.hpp (1)

144-153: Potential performance concern: diving heap traversal under lock.

The while (!diving_heap.empty()) loop pops entries until finding one with a non-null node. With many consumed entries, this could hold the lock for extended periods, blocking other threads.

Consider periodically cleaning up consumed entries or using a separate cleanup mechanism to reduce contention. However, this is a minor concern if the ratio of consumed entries is typically low.

cpp/src/dual_simplex/diving_heuristics.cpp (2)

28-38: Potential division by near-zero in line search diving.

When solution[j] is very close to root_solution[j] (within eps), neither branch is taken and score remains inf. However, if |solution[j] - root_solution[j]| is small but > eps, d could be very small, causing numerical instability in score = f / d.

♻️ Add minimum denominator check
     if (solution[j] < root_solution[j] - eps) {
       f_t f = solution[j] - std::floor(solution[j]);
       f_t d = root_solution[j] - solution[j];
-      score = f / d;
+      score = d > eps ? f / d : inf;
       dir   = rounding_direction_t::DOWN;

     } else if (solution[j] > root_solution[j] + eps) {
       f_t f = std::ceil(solution[j]) - solution[j];
       f_t d = solution[j] - root_solution[j];
-      score = f / d;
+      score = d > eps ? f / d : inf;
       dir   = rounding_direction_t::UP;
     }

241-260: Unused variable eps in coefficient diving.

The variable eps is declared on line 239 but never used in the function.

♻️ Either use eps in tie-breaking or remove it
   i_t branch_var                 = -1;
   i_t min_locks                  = std::numeric_limits<i_t>::max();
   rounding_direction_t round_dir = rounding_direction_t::NONE;
-  constexpr f_t eps              = 1e-6;

   for (auto j : fractional) {
     f_t f_down                = solution[j] - std::floor(solution[j]);
     f_t f_up                  = std::ceil(solution[j]) - solution[j];
     auto [up_lock, down_lock] = calculate_variable_locks(lp_problem, j);
     i_t locks                 = std::min(up_lock, down_lock);

     if (min_locks > locks) {
       min_locks  = locks;
       branch_var = j;

       if (up_lock < down_lock) {
         round_dir = rounding_direction_t::UP;
       } else if (up_lock > down_lock) {
         round_dir = rounding_direction_t::DOWN;
-      } else if (f_down < f_up + eps) {
+      } else if (f_down < f_up) {
         round_dir = rounding_direction_t::DOWN;
       } else {
📜 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 0f6285c and 2d10ace.

📒 Files selected for processing (5)
  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/src/dual_simplex/branch_and_bound.hpp
  • cpp/src/dual_simplex/diving_heuristics.cpp
  • cpp/src/dual_simplex/node_queue.hpp
  • cpp/src/dual_simplex/simplex_solver_settings.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/branch_and_bound.cpp
  • cpp/src/dual_simplex/simplex_solver_settings.hpp
  • cpp/src/dual_simplex/branch_and_bound.hpp
  • cpp/src/dual_simplex/diving_heuristics.cpp
  • cpp/src/dual_simplex/node_queue.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/simplex_solver_settings.hpp
  • cpp/src/dual_simplex/branch_and_bound.hpp
  • cpp/src/dual_simplex/diving_heuristics.cpp
  • cpp/src/dual_simplex/node_queue.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/simplex_solver_settings.hpp
  • cpp/src/dual_simplex/branch_and_bound.hpp
  • cpp/src/dual_simplex/diving_heuristics.cpp
  • cpp/src/dual_simplex/node_queue.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/simplex_solver_settings.hpp
  • cpp/src/dual_simplex/branch_and_bound.hpp
  • cpp/src/dual_simplex/node_queue.hpp
🧠 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 **/*.{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/simplex_solver_settings.hpp
  • cpp/src/dual_simplex/branch_and_bound.hpp
  • cpp/src/dual_simplex/diving_heuristics.cpp
  • cpp/src/dual_simplex/node_queue.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/simplex_solver_settings.hpp
  • cpp/src/dual_simplex/branch_and_bound.hpp
  • cpp/src/dual_simplex/diving_heuristics.cpp
  • cpp/src/dual_simplex/node_queue.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/simplex_solver_settings.hpp
  • cpp/src/dual_simplex/branch_and_bound.hpp
  • cpp/src/dual_simplex/diving_heuristics.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/simplex_solver_settings.hpp
  • 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} : 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/branch_and_bound.hpp
  • cpp/src/dual_simplex/diving_heuristics.cpp
  • cpp/src/dual_simplex/node_queue.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/simplex_solver_settings.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/diving_heuristics.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/node_queue.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/simplex_solver_settings.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/simplex_solver_settings.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/branch_and_bound.cpp
  • cpp/src/dual_simplex/diving_heuristics.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
  • cpp/src/dual_simplex/node_queue.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 race conditions are absent in multi-GPU code and multi-threaded server implementations; verify proper synchronization of shared state

Applied to files:

  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/src/dual_simplex/simplex_solver_settings.hpp
  • cpp/src/dual_simplex/node_queue.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 : Verify race conditions and correctness of GPU kernel shared memory, atomics, and warp-level operations

Applied to files:

  • cpp/src/dual_simplex/branch_and_bound.cpp
  • cpp/src/dual_simplex/simplex_solver_settings.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/branch_and_bound.cpp
  • cpp/src/dual_simplex/simplex_solver_settings.hpp
  • cpp/src/dual_simplex/branch_and_bound.hpp
  • cpp/src/dual_simplex/diving_heuristics.cpp
  • cpp/src/dual_simplex/node_queue.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/simplex_solver_settings.hpp
  • cpp/src/dual_simplex/branch_and_bound.hpp
  • cpp/src/dual_simplex/diving_heuristics.cpp
  • cpp/src/dual_simplex/node_queue.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/dual_simplex/branch_and_bound.hpp
  • cpp/src/dual_simplex/diving_heuristics.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/simplex_solver_settings.hpp
  • 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} : 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/dual_simplex/simplex_solver_settings.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} : Track GPU device memory allocations and deallocations to prevent memory leaks; ensure cudaMalloc/cudaFree balance and cleanup of streams/events

Applied to files:

  • cpp/src/dual_simplex/simplex_solver_settings.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} : Eliminate unnecessary host-device synchronization (cudaDeviceSynchronize) in hot paths that blocks GPU pipeline; use streams and events for async execution

Applied to files:

  • cpp/src/dual_simplex/simplex_solver_settings.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} : For concurrent CUDA operations (barriers, async operations), explicitly create and manage dedicated streams instead of reusing the default stream; document stream lifecycle

Applied to files:

  • cpp/src/dual_simplex/simplex_solver_settings.hpp
🧬 Code graph analysis (4)
cpp/src/dual_simplex/branch_and_bound.cpp (7)
cpp/src/dual_simplex/node_queue.hpp (9)
  • buffer (47-55)
  • buffer (57-57)
  • buffer (58-58)
  • buffer (59-59)
  • buffer (60-60)
  • node (28-32)
  • node (28-28)
  • node (34-38)
  • node (34-34)
cpp/src/dual_simplex/branch_and_bound.hpp (8)
  • obj (200-200)
  • symbol (201-201)
  • node_ptr (249-261)
  • node_ptr (264-267)
  • solution (116-116)
  • solution (134-134)
  • solution (204-204)
  • node (218-218)
cpp/src/dual_simplex/solve.cpp (7)
  • compute_user_objective (88-93)
  • compute_user_objective (88-88)
  • compute_user_objective (96-100)
  • compute_user_objective (96-96)
  • compute_user_objective (661-662)
  • compute_user_objective (664-664)
  • basic_list (113-113)
cpp/src/dual_simplex/solve.hpp (2)
  • compute_user_objective (37-37)
  • compute_user_objective (40-40)
cpp/src/dual_simplex/pseudo_costs.hpp (4)
  • node_ptr (31-31)
  • fractional (46-48)
  • fractional (50-53)
  • fractional (55-56)
cpp/src/dual_simplex/diving_heuristics.hpp (3)
  • line_search_diving (24-27)
  • pseudocost_diving (30-34)
  • guided_diving (37-41)
cpp/src/dual_simplex/presolve.hpp (1)
  • uncrush_primal_solution (162-165)
cpp/src/dual_simplex/branch_and_bound.hpp (2)
cpp/src/dual_simplex/node_queue.hpp (4)
  • node (28-32)
  • node (28-28)
  • node (34-38)
  • node (34-34)
cpp/src/dual_simplex/pseudo_costs.hpp (4)
  • node_ptr (31-31)
  • fractional (46-48)
  • fractional (50-53)
  • fractional (55-56)
cpp/src/dual_simplex/diving_heuristics.cpp (1)
cpp/src/dual_simplex/pseudo_costs.hpp (4)
  • fractional (46-48)
  • fractional (50-53)
  • fractional (55-56)
  • num_initialized_down (41-44)
cpp/src/dual_simplex/node_queue.hpp (1)
cpp/src/dual_simplex/mip_node.hpp (4)
  • lower (97-108)
  • lower (97-99)
  • lower (112-130)
  • lower (112-114)
🪛 Clang (14.0.6)
cpp/src/dual_simplex/branch_and_bound.cpp

[error] 8-8: 'dual_simplex/branch_and_bound.hpp' file not found

(clang-diagnostic-error)

cpp/src/dual_simplex/branch_and_bound.hpp

[error] 10-10: 'dual_simplex/diving_heuristics.hpp' file not found

(clang-diagnostic-error)

cpp/src/dual_simplex/diving_heuristics.cpp

[error] 8-8: 'dual_simplex/diving_heuristics.hpp' file not found

(clang-diagnostic-error)

cpp/src/dual_simplex/node_queue.hpp

[error] 8-8: 'algorithm' file not found

(clang-diagnostic-error)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.1.0, 3.12, amd64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.1.0, 3.11, amd64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.1.0, 3.12, arm64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.1.0, 3.10, arm64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.1.0, 3.13, arm64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.1.0, 3.11, arm64, rockylinux8
  • GitHub Check: wheel-build-cuopt-sh-client / 13.1.0, 3.10, amd64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.1.0, 3.10, amd64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.1.0, 3.13, amd64, rockylinux8
🔇 Additional comments (27)
cpp/src/dual_simplex/branch_and_bound.cpp (10)

197-207: LGTM - Worker type symbol mapping is clear and consistent.

The switch statement correctly maps all bnb_worker_type_t values to two-letter symbols for logging. All diving types map to "D " while exploration maps to "B ".


561-580: Algorithm correctness: Martin's criteria implementation looks correct.

The rounding direction is determined by comparing down_dist and up_dist based on the root solution value. The epsilon tolerance prevents numerical instability when distances are nearly equal.


582-626: Race condition addressed: incumbent_.x is correctly protected by mutex in GUIDED_DIVING.

The mutex lock around reading incumbent_.x (lines 617-619) prevents the race condition mentioned in the PR commit "fix race condition on guided diving". This ensures the incumbent solution isn't modified while being read for guided diving.


647-654: Verify fallback behavior when guided diving lacks incumbent.

The fallback from GUIDED_DIVING to PSEUDOCOST_DIVING or COEFFICIENT_DIVING when upper_bound == inf is correct. This ensures guided diving doesn't fail when no incumbent exists yet.


667-672: Iteration limit enforcement for diving threads.

The iteration limit calculation max_iter = settings_.diving_settings.iteration_limit_factor * bnb_lp_iters and early return when lp_settings.iteration_limit <= 0 prevents diving threads from consuming excessive iterations. This addresses the PR objective of encouraging exploration of different branches.


1133-1200: Dive limit enforcement with backtracking is well-implemented.

The dive_from function correctly:

  1. Enforces node_limit via dive_stats.nodes_explored > node_limit
  2. Handles iteration limits with early break on ITERATION_LIMIT
  3. Implements backtracking by pruning the stack when depth exceeds backtrack threshold (lines 1197-1199)

This aligns with the PR objective of adding "node, iteration, and backtrack limits to encourage exploration."


1337-1358: Diving strategies initialization is clear and configurable.

The strategy vector is populated based on settings flags, allowing users to enable/disable specific diving heuristics. The warning on line 1357 when all strategies are disabled is helpful for debugging.


1517-1519: Updated log header accommodates two-letter symbols.

The header format change addresses PR objective "#714" to allow two-letter symbols in the B&B log table. The spacing is consistent with the report format.


1544-1549: The round-robin distribution of diving workers across strategies is appropriate.

The assignment diving_strategies[k % num_strategies] distributes num_diving_workers (calculated as max(num_threads - num_bfs_workers, 1)) across available strategies. Each diving_thread call operates on an independent copy of the LP problem (leaf_problem = original_lp_), so multiple workers executing the same strategy do not contend for shared mutable state. The work is properly synchronized within the #pragma omp taskgroup context, making this pattern sound for the solver's concurrent exploration.


250-261: No issue found: callers correctly handle -inf return value from get_lower_bound()

The -inf sentinel is properly handled by all callers. When passed to gap calculation functions, infinite bounds result in a gap of infinity, which is correctly formatted as " - " for display. The code behaves as intended when no finite lower bound exists.

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

23-36: Well-structured diving settings with sensible defaults.

The new diving_heuristics_settings_t struct cleanly encapsulates all diving-related configuration. Default values appear reasonable:

  • iteration_limit_factor = 0.05 (5% of B&B iterations)
  • node_limit = 500 per dive
  • backtrack = 5 levels
  • min_node_depth = 10 before diving starts

156-158: LGTM - Settings member placement and naming are clear.

The num_bfs_workers rename from num_bfs_threads better reflects the worker-model semantics, and diving_settings provides a clean namespace for diving configuration.


88-96: The code functions correctly. With num_threads=1, both num_bfs_workers and num_diving_workers are set to 1, which is intentional design using std::max(..., 1) to ensure at least one worker of each type. OpenMP's task model gracefully handles task oversubscription—with fewer threads than tasks, the work-stealing scheduler executes tasks dynamically. Each task (best_first_thread and diving_thread) is independent and maintains its own problem state, so having both worker types present with a single thread causes no functional issues. This is standard OpenMP usage.

cpp/src/dual_simplex/node_queue.hpp (4)

22-65: Well-designed generic heap implementation.

The heap_t template provides a clean abstraction over STL heap functions with proper move semantics support via the rvalue push overload and emplace. The pop() returning std::optional<T> is a safe pattern.


120-131: Thread-safe pop with atomic counter increment - correctly prevents premature exit.

The comment explains the rationale well: incrementing active_subtree atomically with the pop prevents worker threads from incorrectly concluding the solver has finished. The std::exchange(entry.value()->node, nullptr) prevents the same node from being processed twice (once by best-first, once by diving).


138-156: pop_diving correctly handles node lifecycle to prevent use-after-free.

The method:

  1. Holds the lock while traversing the heap
  2. Skips entries where node_ptr == nullptr (already consumed by best-first)
  3. Extracts bounds via get_variable_bounds while node is still valid
  4. Returns a detach_copy() instead of the original node pointer

This design prevents the race condition where a best-first thread could fathom/delete the original node while a diving thread still holds a reference.


70-80: This concern is unfounded—objective_estimate is always safely initialized.

The mip_node_t constructor initializes objective_estimate to std::numeric_limits<f_t>::infinity() by default. Additionally, all code paths that push nodes to the heap (at lines 939–940 in exploration_ramp_up and line 1043 in plunge_from) occur only after solve_node is called with bnb_worker_type_t::EXPLORATION, which computes objective_estimate at line 603–604. The code comment confirms this invariant: "exploration thread is the only one that can insert new nodes into the heap, and thus, we only need to calculate the objective estimate here." Heap ordering remains correct because objective_estimate is never uninitialized at the point of use.

Likely an incorrect or invalid review comment.

cpp/src/dual_simplex/branch_and_bound.hpp (4)

56-67: Well-documented worker type enum.

The bnb_worker_type_t enum clearly documents each worker type with references to Achterberg (2007) sections. This is helpful for understanding the algorithm implementation.


75-86: Thread-safe statistics structure using OpenMP atomics.

The bnb_stats_t struct uses omp_atomic_t for counters that may be updated by multiple threads. The comment on line 83 clarifies that last_log and nodes_since_last_log are main-thread only, though they're still atomic (which is safe but potentially unnecessary overhead).


182-183: Unified node queue replaces separate heap structures.

The change from separate mip_node_heap_t and diving queue to a single node_queue_t aligns with the PR objective of reducing memory usage by sharing information between diving and best-first heaps.


263-267: Variable selection signature updated for worker-type awareness.

The new signature accepts bnb_worker_type_t type and returns branch_variable_t<i_t>, enabling different variable selection strategies per worker type.

cpp/src/dual_simplex/diving_heuristics.cpp (6)

13-65: Line search diving implementation is correct.

The algorithm finds the variable with minimum f/d ratio where:

  • f = fractional part in the preferred direction
  • d = distance from root solution

The fallback on lines 50-53 handles the edge case where all variables equal their root values. The epsilon tolerance on line 19 prevents numerical instability.


67-140: Pseudocost diving correctly uses mutex for thread-safe access.

The std::lock_guard<omp_mutex_t> lock(pc.mutex) on line 74 ensures exclusive access to pseudocost data. The fallback to averages when a variable's pseudocost is uninitialized (lines 92-97) is appropriate.


142-198: Guided diving implementation is correct per Achterberg §9.2.3.

The algorithm:

  1. Determines direction based on distance to incumbent
  2. Uses weighted pseudocost scores with 5:1 weighting for preferred direction
  3. Falls back gracefully when incumbent is unavailable (handled in caller)

Thread safety is ensured via mutex lock on line 149.


200-228: Variable lock calculation correctly analyzes constraint structure.

The calculate_variable_locks function counts how many constraints would be "locked" by rounding a variable up or down. The logic correctly handles:

  • Equality constraints (both directions locked)
  • Inequality constraints (direction depends on coefficient sign and bound type)

230-274: Coefficient diving implementation is correct.

The algorithm selects the variable with minimum locks and breaks ties using:

  1. Fewer locks in one direction → round that way
  2. Equal locks → round toward nearer integer

This implements Achterberg §9.2.1 (coefficient diving).


99-100: Score formulas are numerically stable.

The score formulas std::sqrt(f_up) * (1 + pc_up) / (1 + pc_down) are safe from overflow/underflow. Fractional parts are bounded [0,1], pseudocost values are checked for finiteness and default to 1.0, and the (1 + pc) guard ensures the denominator is always ≥ 2, preventing division by zero or near-zero values. No numerical instability risk detected.

}

inline const char* feasible_solution_symbol(thread_type_t type)
inline const char* feasible_solution_symbol(bnb_worker_type_t type)
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: return a single char and add the space outside this routine

}

template <typename i_t, typename f_t>
void branch_and_bound_t<i_t, f_t>::report(std::string symbol,
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: make symbol a char

std::vector<f_t> current_incumbent;

switch (type) {
case bnb_worker_type_t::EXPLORATION:
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: I guess EXPLORATION is supposed to be a thread improving the lower bound. I would suggest renaming this to BEST_FIRST or BEST_FIRST_SEARCH

}

template <typename i_t, typename f_t>
branch_variable_t<i_t> branch_and_bound_t<i_t, f_t>::variable_selection(
Copy link
Contributor

Choose a reason for hiding this comment

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

It might be better for this to just do variable selection for the diving threads and handle the best-first threads separately.

const f_t abs_fathom_tol = settings_.absolute_mip_gap_tol / 10;
const f_t upper_bound = get_upper_bound();

// If there is no incumbent, use pseudocost diving instead of guided diving
Copy link
Contributor

Choose a reason for hiding this comment

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

Could this be moved into the variable_selection routine?

// Choose fractional variable to branch on
const i_t branch_var =
pc_.variable_selection(leaf_fractional, leaf_solution.x, lp_settings.log);
auto [branch_var, round_dir] =
Copy link
Contributor

Choose a reason for hiding this comment

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

I would split variable selection up based on the thread type here.

For best-first threads retain the

i_t branch_var = pc_.variable_selection()

For diving threads call your variable_selection routine. Which I would rename to diving_variable_selection.

return node_solve_info_t::TIME_LIMIT;

} else if (lp_status == dual::status_t::ITERATION_LIMIT) {
return node_solve_info_t::ITERATION_LIMIT;
Copy link
Contributor

Choose a reason for hiding this comment

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

As discussed does ITERATION_LIMIT need to be a status codes in node_solve_info_t. This is also the name of a solve status. At the very least it would be good to rename to DIVE_ITERATION_LIMIT. Better still if you could just signal that you want to stop here.

std::vector<bnb_worker_type_t> diving_strategies;
diving_strategies.reserve(4);

if (!settings_.diving_settings.disable_pseudocost_diving) {
Copy link
Contributor

Choose a reason for hiding this comment

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

It's strange to have a setting disable_pseudo_cost_diving. This inverts the logic. I think it would be better if the setting was just pseudo_cost_diving. You can use -1 for automatic, 0 to disable, and 1 to enable.

diving_strategies.push_back(bnb_worker_type_t::PSEUDOCOST_DIVING);
}

if (!settings_.diving_settings.disable_line_search_diving) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same for all these settings.

settings_.num_threads - settings_.num_bfs_workers);

exploration_stats_.nodes_explored = 0;
exploration_stats_.nodes_explored = 1;
Copy link
Contributor

Choose a reason for hiding this comment

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

This is counting the root node as being explored?

EXPLORATION = 0, // Best-First + Plunging. Pseudocost branching + Martin's criteria.
DIVING = 1,
enum class bnb_worker_type_t {
EXPLORATION = 0, // Best-First + Plunging.
Copy link
Contributor

Choose a reason for hiding this comment

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

Change EXPLORATION to BEST_FIRST or BEST_FIRST_PLUNGING

f_t min_score = std::numeric_limits<f_t>::max();
rounding_direction_t round_dir = rounding_direction_t::NONE;

for (auto j : fractional) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: use i_t instead of auto to make it clear the type for the reader.

i_t end = lp_problem.A.col_start[var_idx + 1];

for (i_t k = start; k < end; ++k) {
f_t nz_val = lp_problem.A.x[k];
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: most other code would call this x or val and avoid the nz prefix

Copy link
Contributor

Choose a reason for hiding this comment

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

f_t val = lp_problem.A.x[p];

i_t start = lp_problem.A.col_start[var_idx];
i_t end = lp_problem.A.col_start[var_idx + 1];

for (i_t k = start; k < end; ++k) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: the rest of the code use p or q for the variable that loops over nonzeros.


for (i_t k = start; k < end; ++k) {
f_t nz_val = lp_problem.A.x[k];
i_t nz_row = lp_problem.A.i[k];
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: the rest uses the convention
i_t i = lp_problem.A.i[p];

We use the convention of i for rows and j for columns

f_t nz_val = lp_problem.A.x[k];
i_t nz_row = lp_problem.A.i[k];

if (std::isfinite(lp_problem.upper[nz_row]) && std::isfinite(lp_problem.lower[nz_row])) {
Copy link
Contributor

@chris-maes chris-maes Jan 14, 2026

Choose a reason for hiding this comment

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

Is this correct? nz_row corresponds to a row (constraint) index. upper and lower are indexed by columns (variables).

continue;
}

f_t sign = std::isfinite(lp_problem.upper[nz_row]) ? 1 : -1;
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here. This indexing is incorrect.

rounding_direction_t round_dir = rounding_direction_t::NONE;
constexpr f_t eps = 1e-6;

for (auto j : fractional) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: use i_t here to make the type clear to the reader

struct diving_heuristics_settings_t {
i_t num_diving_workers = -1;

bool disable_line_search_diving = false;
Copy link
Contributor

Choose a reason for hiding this comment

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

See suggestions above about the naming of these settings.

i_t line_search_diving = 1;

bool disable_coefficient_diving = false;

i_t min_node_depth = 10;
i_t node_limit = 500;
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: node_limit -> diving_node_limit to avoid confusion with a node limit coming from the user.

Copy link
Contributor

@chris-maes chris-maes left a comment

Choose a reason for hiding this comment

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

Most of the comments are minor.

The most important comment to address is the likely bug with row/column indicies in calculate_variable_locks. The only reasons this isn't causing a segfault right now is because we extend with artifical variables so there are always more columns than rows. But you are likely incorrectly computing the variable locks.

The other comment is around the variable selection for diving and best first threads/workers. I think its best to split this up rather than trying to unify into a single routine.

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

Labels

improvement Improves an existing functionality mip non-breaking Introduces a non-breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants