Skip to content

Expose diving hyper parameters + Vector length/Farkas diving#1298

Open
nguidotti wants to merge 109 commits into
NVIDIA:mainfrom
nguidotti:diving-heuristics-2
Open

Expose diving hyper parameters + Vector length/Farkas diving#1298
nguidotti wants to merge 109 commits into
NVIDIA:mainfrom
nguidotti:diving-heuristics-2

Conversation

@nguidotti
Copy link
Copy Markdown
Contributor

@nguidotti nguidotti commented May 26, 2026

This PR implements the following diving heuristics:

  • Vector length diving [Section 9.2.6, 1], which is particularly effective for set covering and partitioning. It tries to branch on a variable that touches the largest number of constraints with the least possible objective value deterioration.
  • Farkas diving [2] that focus on moving the dual solution of the current LP relaxation towards a valid Farkas proof. This is quite optimistic heuristics and most of the time will not be feasible for $Ax = b$. However, if this find a feasible, it may be very good.

This also expose the diving hyper parameters similarly to heuristics (#993). Note that this PR includes #1149.

Benchmark Results

Time limit is set to 10min. Ran on GH200.

All

================================================================================
 bnb-local-heap (1) vs diving-heuristics-v2 (2)
================================================================================

------------------------------------------------------------------------------------------------------------------------------
|                                        |       Run 1        |       Run 2        |     Abs. Diff.     |   Rel. Diff. (%)   |
------------------------------------------------------------------------------------------------------------------------------
| Imported                                                 240                  240                   +0                 --- |
| Feasible                                                 226                  228                   +2                 --- |
| Optimal                                                   76                   79                   +3                 --- |
| Solutions with <0.1% primal gap                          127                  133                   +6                 --- |
| Nodes explored (mean)                              1.367e+07             1.35e+07           -1.768e+05               -1.29 |
| Nodes explored (shifted geomean)                   1.275e+04            1.146e+04                -1291               -10.1 |
| Relative MIP gap (mean)                               0.3319               0.3274            -0.004484               -1.35 |
| Relative MIP gap (shifted geomean)                    0.1128               0.1125           -0.0002273              -0.202 |
| Solve time (mean)                                        433                434.5               +1.485              +0.343 |
| Solve time (shifted geomean)                           201.6                204.2               +2.633               +1.31 |
| Primal gap (mean)                                      11.64                10.65              -0.9884               -8.49 |
| Primal gap (shifted geomean)                          0.6085               0.5492             -0.05929               -9.74 |
| Primal integral (mean)                                 32.79                31.44               -1.344                -4.1 |
| Primal integral (shifted geomean)                      6.412                6.197              -0.2148               -3.35 |
------------------------------------------------------------------------------------------------------------------------------

In particular, cuopt is able prove optimality to timtab1 and ran14x18-disj-8.

Farkas diving only

(Farkas diving found an incumbent in 25/240 instances)

================================================================================
 bnb-local-heap (1) vs farkas-diving (2)
================================================================================

------------------------------------------------------------------------------------------------------------------------------
|                                        |       Run 1        |       Run 2        |     Abs. Diff.     |   Rel. Diff. (%)   |
------------------------------------------------------------------------------------------------------------------------------
| Imported                                                 240                  240                   +0                 --- |
| Feasible                                                 226                  225                   -1                 --- |
| Optimal                                                   76                   76                   +0                 --- |
| Solutions with <0.1% primal gap                          127                  126                   -1                 --- |
| Nodes explored (mean)                              1.367e+07            1.444e+07           +7.683e+05               +5.62 |
| Nodes explored (shifted geomean)                   1.275e+04            1.235e+04                 -396               -3.11 |
| Relative MIP gap (mean)                               0.3319               0.4281             +0.09619                 +29 |
| Relative MIP gap (shifted geomean)                    0.1128               0.1125           -0.0002423              -0.215 |
| Solve time (mean)                                        433                435.7               +2.619              +0.605 |
| Solve time (shifted geomean)                           201.6                212.9               +11.25               +5.58 |
| Primal gap (mean)                                      11.64                11.67             +0.02978              +0.256 |
| Primal gap (shifted geomean)                          0.6085                0.599             -0.00952               -1.56 |
| Primal integral (mean)                                 32.79                33.21              +0.4264                +1.3 |
| Primal integral (shifted geomean)                      6.412                6.851               +0.439               +6.85 |
------------------------------------------------------------------------------------------------------------------------------

Note that Farkas divings is disabled when the coefficients in the objective function is too close.

Vector length only

(Vector length diving found an incumbent in 80/240 instances)

================================================================================
 bnb-local-heap (1) vs vector-length-diving (2)
================================================================================

------------------------------------------------------------------------------------------------------------------------------
|                                        |       Run 1        |       Run 2        |     Abs. Diff.     |   Rel. Diff. (%)   |
------------------------------------------------------------------------------------------------------------------------------
| Imported                                                 240                  240                   +0                 --- |
| Feasible                                                 226                  227                   +1                 --- |
| Optimal                                                   76                   76                   +0                 --- |
| Solutions with <0.1% primal gap                          127                  125                   -2                 --- |
| Nodes explored (mean)                              1.367e+07             1.36e+07           -7.075e+04              -0.517 |
| Nodes explored (shifted geomean)                   1.275e+04            1.068e+04                -2062               -16.2 |
| Relative MIP gap (mean)                               0.3319               0.4195              +0.0876               +26.4 |
| Relative MIP gap (shifted geomean)                    0.1128               0.1136           +0.0008875              +0.787 |
| Solve time (mean)                                        433                433.8              +0.7848              +0.181 |
| Solve time (shifted geomean)                           201.6                200.6               -1.046              -0.519 |
| Primal gap (mean)                                      11.64                10.97              -0.6698               -5.76 |
| Primal gap (shifted geomean)                          0.6085               0.5693              -0.0392               -6.44 |
| Primal integral (mean)                                 32.79                30.05               -2.735               -8.34 |
| Primal integral (shifted geomean)                      6.412                 5.93              -0.4818               -7.51 |
------------------------------------------------------------------------------------------------------------------------------

References

[1] T. Achterberg, “Constraint Integer Programming,” PhD, Technischen Universität Berlin, Berlin, 2007. doi: [10.14279/depositonce-1634](https://doi.org/10.14279/depositonce-1634).
[2] J. Witzig and A. Gleixner, “Conflict-Driven Heuristics for Mixed Integer Programming,” Feb. 07, 2019, arXiv: arXiv:1902.02615. doi: [10.48550/arXiv.1902.02615](https://doi.org/10.48550/arXiv.1902.02615).

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

bdice and others added 30 commits April 3, 2026 13:51
Remove dependency on rmm::mr::device_memory_resource base class. Resources
now satisfy the cuda::mr::resource concept directly.

- Replace shared_ptr<device_memory_resource> with value types and
  cuda::mr::any_resource<cuda::mr::device_accessible> for type-erased storage
- Replace set_current_device_resource(ptr) with set_current_device_resource_ref
- Replace set_per_device_resource(id, ptr) with set_per_device_resource_ref
- Remove make_owning_wrapper usage
- Remove dynamic_cast on memory resources (no common base class)
- Remove owning_wrapper.hpp and device_memory_resource.hpp includes
- Add missing thrust/iterator/transform_output_iterator.h include
  (no longer transitively included via CCCL)
…nd deterministic mode.

Signed-off-by: Nicolas Guidotti <224634272+nguidotti@users.noreply.github.com>
Signed-off-by: Nicolas Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas Guidotti <nguidotti@nvidia.com>
… shared_ptr to avoid unnecessary copy.

Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
…l crash in work-stealing

Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
…queue for now. refactoring.

Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
… are present

Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
# Conflicts:
#	cpp/src/utilities/cuda_helpers.cuh
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
# Conflicts:
#	ci/validate_wheel.sh
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
nguidotti added 16 commits May 20, 2026 12:52
# Conflicts:
#	cpp/src/branch_and_bound/branch_and_bound.cpp
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
…rkers

Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
# Conflicts:
#	cpp/src/branch_and_bound/mip_node.hpp
#	cpp/src/branch_and_bound/pseudo_costs.cpp
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
# Conflicts:
#	cpp/src/mip_heuristics/mip_constants.hpp
# Conflicts:
#	cpp/src/math_optimization/solver_settings.cu
@nguidotti nguidotti added this to the 26.08 milestone May 26, 2026
@nguidotti nguidotti requested a review from a team as a code owner May 26, 2026 16:22
@nguidotti nguidotti added the non-breaking Introduces a non-breaking change label May 26, 2026
@nguidotti nguidotti requested review from Kh4ster and akifcorduk May 26, 2026 16:22
@nguidotti nguidotti added improvement Improves an existing functionality P1 mip labels May 26, 2026
@copy-pr-bot
Copy link
Copy Markdown

copy-pr-bot Bot commented May 26, 2026

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

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 26, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR introduces new diving heuristic strategies (Farkas and vector-length distance-based variable selection) for MIP branch-and-bound, refactors the parallel search architecture from a generic worker pool to specialized BFS and diving worker types, compresses LP bases into packed bytes within nodes, and adds consistent OpenMP task prioritization across the solver infrastructure.

Changes

Diving Heuristics and Search Architecture

Layer / File(s) Summary
Diving heuristic configuration
cpp/include/cuopt/linear_programming/constants.h, cpp/include/cuopt/linear_programming/mip/diving_hyper_params.hpp, cpp/include/cuopt/linear_programming/mip/solver_settings.hpp, cpp/src/dual_simplex/simplex_solver_settings.hpp, cpp/src/math_optimization/solver_settings.cu
New configuration keys and mip_diving_hyper_params_t struct expose diving heuristic toggles, depth/limit/tolerance parameters, and logging controls; these propagate through MIP and simplex solver settings.
Vstatus compression utilities
cpp/src/dual_simplex/initial_basis.cpp, cpp/src/dual_simplex/initial_basis.hpp
Bit-packing functions encode variable statuses into 2-bit codes and compress status vectors into compact byte arrays, reducing node basis storage from vectors to packed uint8 buffers.
MIP node basis representation
cpp/src/branch_and_bound/mip_node.hpp, cpp/src/dual_simplex/presolve.hpp
Nodes store compressed packed_vstatus instead of unpacked vectors; LP problem tracks objective coefficient extrema for Farkas diving decisions; nodes gain can_dive flag for coordination.
Farkas and vector-length diving strategies
cpp/src/branch_and_bound/diving_heuristics.cpp, cpp/src/branch_and_bound/diving_heuristics.hpp
Two new diving functions select fractional variables by Farkas objective distance or constraint column length; search strategy enum expanded from 5 to 7 strategies with logging of which diving type found incumbents.
Worker type hierarchy
cpp/src/branch_and_bound/worker.hpp
Base worker gains leaf_vstatus buffer and RNG offset parameter; new bfs_worker_t manages best-first nodes with stealing and lower-bound tracking; new diving_worker_t executes plunges with presolve bound strengthening.
Generic worker pool template
cpp/src/branch_and_bound/worker_pool.hpp
Replaces strategy-specific pool with generic worker_pool_t<WorkerType> using circular deque for idle workers, consolidated pop API, and role-agnostic lower-bound aggregation.
Node queue heap and locking
cpp/src/branch_and_bound/node_queue.hpp
Heap tracks entry count atomically, pop returns value directly (not optional), and queue exposes lock/unlock for external synchronization; diving heap pop clears can_dive flags on nodes.
Branch-and-bound orchestration
cpp/src/branch_and_bound/branch_and_bound.hpp, cpp/src/branch_and_bound/branch_and_bound.cpp
Replaces unified worker pool with BFS and diving pools; plunge_with refactored for BFS workers with circular deque stacks and time-limit checks; launch_diving_worker allocates from idle diving pool; deterministic mode decompresses vstatus per solve; feasible solution reporting logs diving heuristic provenance.
Reliability branching vstatus migration
cpp/src/branch_and_bound/pseudo_costs.cpp, cpp/src/branch_and_bound/pseudo_costs.hpp
Candidate ranking and trial branching pass worker leaf_vstatus instead of node vstatus; task_priority field removed from settings; task priorities configured via OpenMP pragma macros.
Utility infrastructure
cpp/src/utilities/circular_deque.hpp, cpp/src/utilities/omp_helpers.hpp, cpp/src/utilities/pcgenerator.hpp
New circular_deque_t implements fixed-capacity double-ended queue for diving stacks; calculate_index_range partitions work across taskloops; OpenMP fetch_min/max unconditionally available; PCG documentation clarified.
OpenMP task prioritization
cpp/src/mip_heuristics/mip_constants.hpp, cpp/src/mip_heuristics/diversity/lns/rins.cu, cpp/src/mip_heuristics/feasibility_jump/early_cpufj.cu, cpp/src/mip_heuristics/feasibility_jump/early_gpufj.cu, cpp/src/mip_heuristics/local_search/local_search.cu, cpp/src/mip_heuristics/presolve/conditional_bound_strengthening.cu, cpp/src/mip_heuristics/presolve/probing_cache.cu, cpp/src/mip_heuristics/solver.cu
Task priority macros defined (CRITICAL, HIGH, MEDIUM, DEFAULT); consistent priority clauses added to feasibility jump, local search, RINS, presolve, and solver concurrent tasks.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes


Suggested labels

feature request


Suggested reviewers

  • chris-maes
  • aliceb-nv
  • rg20
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.77% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main changes: exposing diving hyperparameters and implementing vector length/Farkas diving heuristics, which aligns with the primary objectives of the PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description clearly explains the implementation of two diving heuristics (vector length and Farkas diving), their purpose, benchmark results showing measurable improvements, and includes proper references.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

Caution

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

⚠️ Outside diff range comments (1)
cpp/src/branch_and_bound/branch_and_bound.cpp (1)

1123-1150: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Deterministic diving dispatch is incomplete for newly added strategies.

Line 1123’s switch omits FARKAS_DIVING and VECTOR_LENGTH_DIVING. Since deterministic workers are initialized from all diving strategies (Lines 3065-3066), this can fall into default (Line 1149), return branch_var = -1, and trip the assert at Line 1255.

🛠️ Minimal fix for deterministic strategy parity
   switch (this->worker.diving_type) {
     case search_strategy_t::PSEUDOCOST_DIVING:
       return pseudocost_diving(
         this->worker.pc_snapshot, fractional, x, *this->worker.root_solution, log);

     case search_strategy_t::LINE_SEARCH_DIVING:
       return line_search_diving<i_t, f_t>(fractional, x, *this->worker.root_solution, log);

     case search_strategy_t::GUIDED_DIVING:
       if (this->worker.incumbent_snapshot.empty()) {
         return pseudocost_diving(
           this->worker.pc_snapshot, fractional, x, *this->worker.root_solution, log);
       } else {
         return guided_diving(
           this->worker.pc_snapshot, fractional, x, this->worker.incumbent_snapshot, log);
       }

     case search_strategy_t::COEFFICIENT_DIVING: {
       return coefficient_diving<i_t, f_t>(this->worker.leaf_problem,
                                           fractional,
                                           x,
                                           this->bnb.var_up_locks_,
                                           this->bnb.var_down_locks_,
                                           log);
     }
+    case search_strategy_t::FARKAS_DIVING:
+      return farkas_diving(
+        this->worker.leaf_problem, fractional, x, this->bnb.settings_.zero_tol, log);
+
+    case search_strategy_t::VECTOR_LENGTH_DIVING:
+      return vector_length_diving(this->worker.leaf_problem, fractional, x, log);

     default: CUOPT_LOG_ERROR("Invalid diving method!"); return {-1, branch_direction_t::NONE};
   }

Please add deterministic-mode coverage for both strategies so this path is guarded by tests.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/src/branch_and_bound/branch_and_bound.cpp` around lines 1123 - 1150, The
switch over this->worker.diving_type lacks cases for FARKAS_DIVING and
VECTOR_LENGTH_DIVING which causes deterministic workers to hit the default and
return branch_var = -1; add explicit cases for search_strategy_t::FARKAS_DIVING
and search_strategy_t::VECTOR_LENGTH_DIVING and dispatch them to their
deterministic implementations (e.g., call the corresponding deterministic
functions similar to pseudocost_diving, line_search_diving, guided_diving,
coefficient_diving) so the deterministic path used by worker.diving_type is
covered; ensure you reference the same parameters used in nearby cases
(fractional, x, snapshots, log, and this->worker.* fields) so the assert at Line
1255 will not trip.
🧹 Nitpick comments (1)
cpp/src/dual_simplex/initial_basis.cpp (1)

47-102: ⚡ Quick win

Add round-trip tests for the new basis serialization path.

This code now sits on the node save/restore path and has edge cases around tail packing and unsupported statuses. A small gtest covering lengths 0..7 and all supported variable_status_t values would make regressions much easier to catch.

As per coding guidelines, **/*.{cpp,cc,cxx,c,h,hpp,py}: Add unit tests for code changes; refer to cpp/src/tests for C/C++ gtest examples and python/cuopt/cuopt/tests for Python pytest examples.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/src/dual_simplex/initial_basis.cpp` around lines 47 - 102, Add gtests
that perform round-trip serialization using compress_vstatus and
decompress_vstatus: for vstatus lengths 0..7 generate all combinations (or
iterate over all supported variable_status_t values per position) and assert
decompress_vstatus(compress_vstatus(v)) == v; include cases exercising the tail
path and any unsupported/invalid status behavior if applicable. Place tests
alongside other C++ tests in cpp/src/tests, use the functions compress_vstatus
and decompress_vstatus to locate the code, and ensure assertions cover equality
and packed size expectations (packed_vstatus.size() == expected n + has_tail) to
catch regressions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cpp/src/branch_and_bound/diving_heuristics.cpp`:
- Around line 262-362: Add unit tests covering the two new heuristics
farkas_diving and vector_length_diving plus the branching-strategy dispatch path
(the selector that chooses which diving heuristic) to ensure deterministic
variable/direction selection, correct behavior when fractional is empty, and
edge cases around zero_tol/tolerance boundaries; create gtest cases in
cpp/src/tests that (1) pass a controlled lp_problem_t and solution to
farkas_diving to assert the chosen branch_var and branch_direction_t for
positive/negative/zero objective coefficients and when fractional is empty, (2)
do the same for vector_length_diving including scenarios that exercise
column_length and eps effects and tie-breaking, and (3) test the dispatcher (the
function that routes to these heuristics) for correct heuristic selection and
propagation of empty-fractional and tolerance behavior.

In `@cpp/src/branch_and_bound/diving_heuristics.hpp`:
- Around line 81-86: The declaration of farkas_diving(...) lacks documentation
for the numerical tolerance parameter zero_tol; update the declaration comment
for branch_variable_t<i_t> farkas_diving(...) to state the contract for zero_tol
(e.g., that it is an absolute tolerance used to treat values |x| <= zero_tol as
zero when deciding integrality/feasibility, expected typical range/units, valid
values and behavior on edge cases such as zero or negative inputs, and
recommended example values), so callers know how to tune and what semantic role
zero_tol plays in the algorithm.

In `@cpp/src/branch_and_bound/node_queue.hpp`:
- Around line 80-82: The node_queue currently requires manual lock()/unlock()
which risks deadlocks on exceptions; add an RAII-style guard by implementing
node_queue::scoped_lock() that returns a movable/non-copyable guard which
acquires the internal mutex in its constructor and releases it in its
destructor, then update all internal mutable operations (the methods exposed
around lock()/unlock() usage between lines 86–132) and call sites to use auto lk
= node_queue.scoped_lock(); instead of raw lock()/unlock(); keep lock()/unlock()
for backward compatibility but document them as deprecated and ensure all
new/modified methods use the scoped_lock to guarantee exception-safe unlocking.

In `@cpp/src/dual_simplex/initial_basis.cpp`:
- Around line 21-45: The packed encoding currently maps
variable_status_t::NONBASIC_FIXED to 0b01 which decodes back to NONBASIC_LOWER,
losing FIXED; fix encode/decode in encode(uint8_t) and decode(uint8_t) so
NONBASIC_FIXED gets a unique 2-bit pattern (for example set case
variable_status_t::NONBASIC_FIXED: val = 0b11) and update decode to return
variable_status_t::NONBASIC_FIXED when val == 0b11; alternatively, if you prefer
to reject FIXED like SUPERBASIC, add an assert/guard in encode for
variable_status_t::NONBASIC_FIXED and do not provide a mapping — but be explicit
in either encode, decode, and call sites (compress_vstatus/decompress_vstatus
and places that reconstruct worker->leaf_vstatus) so the representation is not
lossy.

In `@cpp/src/dual_simplex/presolve.hpp`:
- Around line 53-56: The min_abs_obj_coeff field is incorrectly initialized to 0
which can cause division-by-zero/NaN when computing
log10(original_lp_.max_abs_obj_coeff / original_lp_.min_abs_obj_coeff); change
its initializer from 0 to std::numeric_limits<f_t>::infinity() and update the
comment for max_abs_obj_coeff/min_abs_obj_coeff to state that min_abs_obj_coeff
starts at +infinity and is only lowered by positive coefficient magnitudes (so
the default fails closed), ensuring downstream calls using
original_lp_.min_abs_obj_coeff are safe.

In `@cpp/src/math_optimization/solver_settings.cu`:
- Around line 118-120: The unified parameter registry is missing registration
for mip_diving_hyper_params_t::farkas_obj_dynamism_tol so that
set_parameter*/config files can control it; add an entry for this field to the
float_parameters array alongside the other diving hyper-parameters (e.g., create
a CUOPT_MIP_HYPER_* enum constant like CUOPT_MIP_HYPER_FARKAS_OBJ_DYNAMISM_TOL
and add a mapping {CUOPT_MIP_HYPER_FARKAS_OBJ_DYNAMISM_TOL,
&mip_settings.diving_params.farkas_obj_dynamism_tol, <min>, <max>, <default>,
"description"}), and repeat the same insertion for the other float_parameters
blocks noted (lines analogous to 164-174 and 192-194) so the parameter is
consistently exposed.

In `@cpp/src/mip_heuristics/presolve/probing_cache.cu`:
- Around line 899-902: Clamp num_tasks to >=1 before constructing the OpenMP
taskloop to avoid passing zero into the pragma or into calculate_index_range:
compute effective_num_tasks = max<size_t>(1, bound_presolve.settings.num_tasks ?
bound_presolve.settings.num_tasks : (omp_get_num_threads() ?
omp_get_num_threads() - 1 : 1)) (or equivalent) and use effective_num_tasks in
the pragma and the loop and in the call to calculate_index_range; also add a
regression test covering the single-thread / num_tasks==0 path (use
cpp/src/tests or python/cuopt/cuopt/tests per project conventions) to ensure
probing-cache still runs when bound_presolve.settings.num_tasks==0 or when
omp_get_num_threads()==1.

In `@cpp/src/utilities/circular_deque.hpp`:
- Around line 23-112: Add gtest unit tests for circular_deque_t to verify
default construction, sized construction, push_back/push_front,
pop_front/pop_back, size()/capacity(), empty()/full(), wrap-around behavior and
clear_resize; specifically create tests that (1) default-construct
circular_deque_t<T> and assert empty() and capacity()==0/1 expectations, (2)
construct with explicit capacity and push until full then assert full() and
correct size(), (3) alternate push_front/push_back and pop_front/pop_back to
force index wrap-around and assert invariants (size(), front(), back()), (4)
call clear_resize(new_capacity) and verify buffer semantics and that further
pushes/pops behave correctly, and (5) exercise boundary cases like popping from
single-element deque to become empty; reference circular_deque_t, push_back,
push_front, pop_front, pop_back, size, capacity, empty, full, and clear_resize
and place tests under cpp/src/tests following existing gtest examples.
- Around line 26-27: The default constructor creates a buffer of size 1 so
full() immediately returns true; change the constructor to allocate at least two
slots (e.g., buffer_(2)) and set capacity_ to buffer_.size() (capacity_ = 2) so
the circular buffer can represent an empty state correctly (keep the existing
full() check (tail_ + 1) % capacity_ == head_). Update the circular_deque_t()
constructor to allocate buffer_ with size >= 2 and initialize capacity_, head_,
tail_ accordingly so push_front/push_back become usable.

In `@cpp/src/utilities/omp_helpers.hpp`:
- Around line 18-23: The function calculate_index_range performs division by n
without validation; add a precondition/assert at the start of
calculate_index_range to ensure n > 0 (and optionally that total >= 0 if you
want stricter checks) so the function fails fast on invalid input instead of
invoking undefined behavior; update the implementation of calculate_index_range
to check n and document the invariant above the function (use assert or
equivalent in your project).

---

Outside diff comments:
In `@cpp/src/branch_and_bound/branch_and_bound.cpp`:
- Around line 1123-1150: The switch over this->worker.diving_type lacks cases
for FARKAS_DIVING and VECTOR_LENGTH_DIVING which causes deterministic workers to
hit the default and return branch_var = -1; add explicit cases for
search_strategy_t::FARKAS_DIVING and search_strategy_t::VECTOR_LENGTH_DIVING and
dispatch them to their deterministic implementations (e.g., call the
corresponding deterministic functions similar to pseudocost_diving,
line_search_diving, guided_diving, coefficient_diving) so the deterministic path
used by worker.diving_type is covered; ensure you reference the same parameters
used in nearby cases (fractional, x, snapshots, log, and this->worker.* fields)
so the assert at Line 1255 will not trip.

---

Nitpick comments:
In `@cpp/src/dual_simplex/initial_basis.cpp`:
- Around line 47-102: Add gtests that perform round-trip serialization using
compress_vstatus and decompress_vstatus: for vstatus lengths 0..7 generate all
combinations (or iterate over all supported variable_status_t values per
position) and assert decompress_vstatus(compress_vstatus(v)) == v; include cases
exercising the tail path and any unsupported/invalid status behavior if
applicable. Place tests alongside other C++ tests in cpp/src/tests, use the
functions compress_vstatus and decompress_vstatus to locate the code, and ensure
assertions cover equality and packed size expectations (packed_vstatus.size() ==
expected n + has_tail) to catch regressions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 5052667f-6bf1-4cea-b500-2f935b793979

📥 Commits

Reviewing files that changed from the base of the PR and between d3d5fd5 and 2c545ea.

📒 Files selected for processing (32)
  • cpp/include/cuopt/linear_programming/constants.h
  • cpp/include/cuopt/linear_programming/mip/diving_hyper_params.hpp
  • cpp/include/cuopt/linear_programming/mip/solver_settings.hpp
  • cpp/src/branch_and_bound/branch_and_bound.cpp
  • cpp/src/branch_and_bound/branch_and_bound.hpp
  • cpp/src/branch_and_bound/constants.hpp
  • cpp/src/branch_and_bound/deterministic_workers.hpp
  • cpp/src/branch_and_bound/diving_heuristics.cpp
  • cpp/src/branch_and_bound/diving_heuristics.hpp
  • cpp/src/branch_and_bound/mip_node.hpp
  • cpp/src/branch_and_bound/node_queue.hpp
  • cpp/src/branch_and_bound/pseudo_costs.cpp
  • cpp/src/branch_and_bound/pseudo_costs.hpp
  • cpp/src/branch_and_bound/worker.hpp
  • cpp/src/branch_and_bound/worker_pool.hpp
  • cpp/src/dual_simplex/initial_basis.cpp
  • cpp/src/dual_simplex/initial_basis.hpp
  • cpp/src/dual_simplex/presolve.hpp
  • cpp/src/dual_simplex/simplex_solver_settings.hpp
  • cpp/src/math_optimization/solver_settings.cu
  • cpp/src/mip_heuristics/diversity/lns/rins.cu
  • cpp/src/mip_heuristics/feasibility_jump/early_cpufj.cu
  • cpp/src/mip_heuristics/feasibility_jump/early_gpufj.cu
  • cpp/src/mip_heuristics/local_search/local_search.cu
  • cpp/src/mip_heuristics/mip_constants.hpp
  • cpp/src/mip_heuristics/presolve/conditional_bound_strengthening.cu
  • cpp/src/mip_heuristics/presolve/probing_cache.cu
  • cpp/src/mip_heuristics/solve.cu
  • cpp/src/mip_heuristics/solver.cu
  • cpp/src/utilities/circular_deque.hpp
  • cpp/src/utilities/omp_helpers.hpp
  • cpp/src/utilities/pcgenerator.hpp
💤 Files with no reviewable changes (1)
  • cpp/src/branch_and_bound/pseudo_costs.hpp

Comment thread cpp/src/branch_and_bound/diving_heuristics.cpp
Comment on lines +81 to +86
template <typename i_t, typename f_t>
branch_variable_t<i_t> farkas_diving(const lp_problem_t<i_t, f_t>& lp,
const std::vector<i_t>& fractional,
const std::vector<f_t>& solution,
f_t zero_tol,
logger_t& log);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Document the zero_tol contract in the declaration.

Line 85 introduces a numerical tolerance parameter, but the declaration does not explain expected scale/role (e.g., absolute tolerance vs. feasibility threshold), which makes tuning and call-site use ambiguous.

✏️ Suggested header-level clarification
 template <typename i_t, typename f_t>
+// `zero_tol` is the absolute near-zero threshold used by Farkas score computations.
+// Values with |x| <= zero_tol are treated as zero.
 branch_variable_t<i_t> farkas_diving(const lp_problem_t<i_t, f_t>& lp,
                                      const std::vector<i_t>& fractional,
                                      const std::vector<f_t>& solution,
                                      f_t zero_tol,
                                      logger_t& log);

As per coding guidelines: "Flag missing documentation for numerical tolerances or algorithm parameters".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
template <typename i_t, typename f_t>
branch_variable_t<i_t> farkas_diving(const lp_problem_t<i_t, f_t>& lp,
const std::vector<i_t>& fractional,
const std::vector<f_t>& solution,
f_t zero_tol,
logger_t& log);
template <typename i_t, typename f_t>
// `zero_tol` is the absolute near-zero threshold used by Farkas score computations.
// Values with |x| <= zero_tol are treated as zero.
branch_variable_t<i_t> farkas_diving(const lp_problem_t<i_t, f_t>& lp,
const std::vector<i_t>& fractional,
const std::vector<f_t>& solution,
f_t zero_tol,
logger_t& log);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/src/branch_and_bound/diving_heuristics.hpp` around lines 81 - 86, The
declaration of farkas_diving(...) lacks documentation for the numerical
tolerance parameter zero_tol; update the declaration comment for
branch_variable_t<i_t> farkas_diving(...) to state the contract for zero_tol
(e.g., that it is an absolute tolerance used to treat values |x| <= zero_tol as
zero when deciding integrality/feasibility, expected typical range/units, valid
values and behavior on edge cases such as zero or negative inputs, and
recommended example values), so callers know how to tune and what semantic role
zero_tol plays in the algorithm.

Comment thread cpp/src/branch_and_bound/node_queue.hpp
Comment thread cpp/src/dual_simplex/initial_basis.cpp
Comment on lines +53 to +56
// Maximum and minimum value of the coefficients in the objective function. This is used
// for determine the "objective dynamism" in Farkas diving.
f_t max_abs_obj_coeff = 0;
f_t min_abs_obj_coeff = 0;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Initialize min_abs_obj_coeff to a non-zero-safe sentinel.

These fields feed std::log10(original_lp_.max_abs_obj_coeff / original_lp_.min_abs_obj_coeff) downstream. With the new default of 0, an unset or zero minimum can turn the Farkas-diving gate into 0/0, inf, or NaN. Initializing min_abs_obj_coeff to std::numeric_limits<f_t>::infinity() and documenting that only positive coefficients should lower it makes the default fail closed.

Proposed fix
-  f_t max_abs_obj_coeff = 0;
-  f_t min_abs_obj_coeff = 0;
+  f_t max_abs_obj_coeff = 0;
+  f_t min_abs_obj_coeff = std::numeric_limits<f_t>::infinity();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Maximum and minimum value of the coefficients in the objective function. This is used
// for determine the "objective dynamism" in Farkas diving.
f_t max_abs_obj_coeff = 0;
f_t min_abs_obj_coeff = 0;
// Maximum and minimum value of the coefficients in the objective function. This is used
// for determine the "objective dynamism" in Farkas diving.
f_t max_abs_obj_coeff = 0;
f_t min_abs_obj_coeff = std::numeric_limits<f_t>::infinity();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/src/dual_simplex/presolve.hpp` around lines 53 - 56, The
min_abs_obj_coeff field is incorrectly initialized to 0 which can cause
division-by-zero/NaN when computing log10(original_lp_.max_abs_obj_coeff /
original_lp_.min_abs_obj_coeff); change its initializer from 0 to
std::numeric_limits<f_t>::infinity() and update the comment for
max_abs_obj_coeff/min_abs_obj_coeff to state that min_abs_obj_coeff starts at
+infinity and is only lowered by positive coefficient magnitudes (so the default
fails closed), ensuring downstream calls using original_lp_.min_abs_obj_coeff
are safe.

Comment on lines +118 to 120
// Diving heuristic hyper-parameters (hidden from default --help: name contains "hyper_")
{CUOPT_MIP_HYPER_DIVING_ITERATION_LIMIT_FACTOR, &mip_settings.diving_params.iteration_limit_factor, f_t(0.0), f_t(1.0), f_t(0.05), "fraction of best-first iterations allowed per dive"}
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Expose farkas_obj_dynamism_tol in the unified parameter registry.

mip_diving_hyper_params_t includes farkas_obj_dynamism_tol, but this file does not register it in float_parameters, so it cannot be configured via set_parameter*() or config files like the rest of diving knobs.

Proposed fix
--- a/cpp/include/cuopt/linear_programming/constants.h
+++ b/cpp/include/cuopt/linear_programming/constants.h
@@
 `#define` CUOPT_MIP_HYPER_DIVING_BACKTRACK_LIMIT        "mip_hyper_diving_backtrack_limit"
+#define CUOPT_MIP_HYPER_DIVING_FARKAS_OBJ_DYNAMISM_TOL "mip_hyper_diving_farkas_obj_dynamism_tol"
--- a/cpp/src/math_optimization/solver_settings.cu
+++ b/cpp/src/math_optimization/solver_settings.cu
@@
     {CUOPT_MIP_HYPER_DIVING_ITERATION_LIMIT_FACTOR, &mip_settings.diving_params.iteration_limit_factor, f_t(0.0), f_t(1.0), f_t(0.05), "fraction of best-first iterations allowed per dive"}
+   ,{CUOPT_MIP_HYPER_DIVING_FARKAS_OBJ_DYNAMISM_TOL, &mip_settings.diving_params.farkas_obj_dynamism_tol, f_t(0.0), f_t(1.0), f_t(1e-4), "minimum objective-coefficient spread to keep Farkas diving enabled"}

Also applies to: 164-174, 192-194

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/src/math_optimization/solver_settings.cu` around lines 118 - 120, The
unified parameter registry is missing registration for
mip_diving_hyper_params_t::farkas_obj_dynamism_tol so that set_parameter*/config
files can control it; add an entry for this field to the float_parameters array
alongside the other diving hyper-parameters (e.g., create a CUOPT_MIP_HYPER_*
enum constant like CUOPT_MIP_HYPER_FARKAS_OBJ_DYNAMISM_TOL and add a mapping
{CUOPT_MIP_HYPER_FARKAS_OBJ_DYNAMISM_TOL,
&mip_settings.diving_params.farkas_obj_dynamism_tol, <min>, <max>, <default>,
"description"}), and repeat the same insertion for the other float_parameters
blocks noted (lines analogous to 164-174 and 192-194) so the parameter is
consistently exposed.

Comment on lines +899 to +902
#pragma omp taskloop num_tasks(num_tasks) default(shared) priority(CUOPT_DEFAULT_TASK_PRIORITY)
for (size_t task_id = 0; task_id < num_tasks; ++task_id) {
size_t n = step_end - step_start;
size_t begin = step_start + std::floor(static_cast<f_t>(n) * task_id / num_tasks);
size_t end = step_start + std::floor(static_cast<f_t>(n) * (task_id + 1) / num_tasks);
size_t n = step_end - step_start;
auto [begin, end] = calculate_index_range(task_id, n, num_tasks);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Clamp num_tasks before building the taskloop.

num_tasks can be 0 here when bound_presolve.settings.num_tasks == 0 or when this path runs with a single OpenMP thread and falls back to omp_get_num_threads() - 1. That makes num_tasks(num_tasks) invalid and sends a zero divisor into calculate_index_range(...), so probing-cache work is skipped or the runtime trips over the pragma.

Suggested fix
-  size_t num_tasks = bound_presolve.settings.num_tasks < 0 ? omp_get_num_threads() - 1
-                                                           : bound_presolve.settings.num_tasks;
+  size_t num_tasks = bound_presolve.settings.num_tasks < 0 ? omp_get_num_threads() - 1
+                                                           : bound_presolve.settings.num_tasks;
+  num_tasks = std::max<size_t>(num_tasks, 1);

Please add a regression test for the single-thread / num_tasks == 0 path. As per coding guidelines **/*.{cpp,cc,cxx,c,h,hpp,py}: Add unit tests for code changes; refer to cpp/src/tests for C/C++ gtest examples and python/cuopt/cuopt/tests for Python pytest examples.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/src/mip_heuristics/presolve/probing_cache.cu` around lines 899 - 902,
Clamp num_tasks to >=1 before constructing the OpenMP taskloop to avoid passing
zero into the pragma or into calculate_index_range: compute effective_num_tasks
= max<size_t>(1, bound_presolve.settings.num_tasks ?
bound_presolve.settings.num_tasks : (omp_get_num_threads() ?
omp_get_num_threads() - 1 : 1)) (or equivalent) and use effective_num_tasks in
the pragma and the loop and in the call to calculate_index_range; also add a
regression test covering the single-thread / num_tasks==0 path (use
cpp/src/tests or python/cuopt/cuopt/tests per project conventions) to ensure
probing-cache still runs when bound_presolve.settings.num_tasks==0 or when
omp_get_num_threads()==1.

Comment on lines +23 to +112
template <typename T>
class circular_deque_t {
public:
circular_deque_t() : buffer_(1), capacity_(1), head_(0), tail_(0) {}

// Allocates storage for exactly `capacity` elements up front.
explicit circular_deque_t(size_t capacity)
: buffer_(capacity + 1), // +1 to distinguish full (next(tail)==head) from empty (head==tail)
capacity_(capacity + 1),
head_(0),
tail_(0)
{
assert(capacity > 0);
}

bool empty() const { return head_ == tail_; }
bool full() const { return next(tail_) == head_; }

size_t size() const { return (tail_ - head_ + capacity_) % capacity_; }
size_t capacity() const { return capacity_ - 1; }

void clear_resize(size_t new_capacity)
{
assert(new_capacity > 0);
head_ = 0;
tail_ = 0;
capacity_ = new_capacity + 1;
buffer_.resize(capacity_);
}

void push_back(T val)
{
assert(!full());
buffer_[tail_] = std::move(val);
tail_ = next(tail_);
}

void push_front(T val)
{
assert(!full());
head_ = prev(head_);
buffer_[head_] = std::move(val);
}

T pop_front()
{
assert(!empty());
T val = std::move(buffer_[head_]);
head_ = next(head_);
return val;
}

T pop_back()
{
assert(!empty());
tail_ = prev(tail_);
return std::move(buffer_[tail_]);
}

T& front()
{
assert(!empty());
return buffer_[head_];
}
const T& front() const
{
assert(!empty());
return buffer_[head_];
}

T& back()
{
assert(!empty());
return buffer_[prev(tail_)];
}
const T& back() const
{
assert(!empty());
return buffer_[prev(tail_)];
}

private:
size_t next(size_t idx) const { return (idx + 1) % capacity_; }
size_t prev(size_t idx) const { return (idx + capacity_ - 1) % capacity_; }

std::vector<T> buffer_;
size_t capacity_;
size_t head_;
size_t tail_;
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Add unit tests for wrap-around/full-empty invariants of the new deque.

This new core container needs tests for default construction, push_front/push_back, pop_front/pop_back, wrap-around behavior, and full/empty boundaries.

As per coding guidelines, "**/*.{cpp,cc,cxx,c,h,hpp,py}: Add unit tests for code changes; refer to cpp/src/tests for C/C++ gtest examples".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/src/utilities/circular_deque.hpp` around lines 23 - 112, Add gtest unit
tests for circular_deque_t to verify default construction, sized construction,
push_back/push_front, pop_front/pop_back, size()/capacity(), empty()/full(),
wrap-around behavior and clear_resize; specifically create tests that (1)
default-construct circular_deque_t<T> and assert empty() and capacity()==0/1
expectations, (2) construct with explicit capacity and push until full then
assert full() and correct size(), (3) alternate push_front/push_back and
pop_front/pop_back to force index wrap-around and assert invariants (size(),
front(), back()), (4) call clear_resize(new_capacity) and verify buffer
semantics and that further pushes/pops behave correctly, and (5) exercise
boundary cases like popping from single-element deque to become empty; reference
circular_deque_t, push_back, push_front, pop_front, pop_back, size, capacity,
empty, full, and clear_resize and place tests under cpp/src/tests following
existing gtest examples.

Comment thread cpp/src/utilities/circular_deque.hpp
Comment on lines +18 to +23
std::pair<i_t, i_t> calculate_index_range(i_t k, double total, double n)
{
i_t start = std::floor(k * total / n);
i_t end = std::floor((k + 1) * total / n);
return {start, end};
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard against invalid partition divisor in calculate_index_range.

Line 20-Line 21 divides by n without checking n > 0. Add a precondition assert to fail fast on invalid inputs.

Fix
 template <typename i_t>
 std::pair<i_t, i_t> calculate_index_range(i_t k, double total, double n)
 {
+  assert(n > 0);
   i_t start = std::floor(k * total / n);
   i_t end   = std::floor((k + 1) * total / n);
   return {start, end};
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/src/utilities/omp_helpers.hpp` around lines 18 - 23, The function
calculate_index_range performs division by n without validation; add a
precondition/assert at the start of calculate_index_range to ensure n > 0 (and
optionally that total >= 0 if you want stricter checks) so the function fails
fast on invalid input instead of invoking undefined behavior; update the
implementation of calculate_index_range to check n and document the invariant
above the function (use assert or equivalent in your project).

@nguidotti
Copy link
Copy Markdown
Contributor Author

/ok to test 2c545ea

@nguidotti
Copy link
Copy Markdown
Contributor Author

/ok to test 883a04e

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 P1

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants