Skip to content

Vector length + Farkas diving#1401

Open
nguidotti wants to merge 5 commits into
NVIDIA:mainfrom
nguidotti:new-diving-heuristics
Open

Vector length + Farkas diving#1401
nguidotti wants to merge 5 commits into
NVIDIA:mainfrom
nguidotti:new-diving-heuristics

Conversation

@nguidotti
Copy link
Copy Markdown
Contributor

@nguidotti nguidotti commented Jun 8, 2026

Implemented Farkas [1] and vector length [Section 9.2.6, 2] diving heuristics. It includes #1364.

Farkas diving is designed to move the current LP relaxation in direction to a valid Farkas proof. It pushes all variables towards the so-called pseudo solution, in which each variable assumes the best bound with respect to its objective coefficient as the solution value. Generally, this is very optimistic and most of the time, it will violate the constraints of the problem. Yet, if this finds a feasible solution, then it is expected to be very good.

Vector length diving is tailored for set partitioning and set covering. It chooses a rounding that coverst the largest number of constraints with the smallest objective value deterioration.

Benchmark Results

MIPLIB2017, 10min, GH200

All

================================================================================
 main-2026-06-01 (1) vs new-diving-heuristics (2)
================================================================================

------------------------------------------------------------------------------------------------------------------------------
|                                        |       Run 1        |       Run 2        |     Abs. Diff.     |   Rel. Diff. (%)   |
------------------------------------------------------------------------------------------------------------------------------
| Imported                                                 240                  240                   +0                 --- |
| Feasible                                                 226                  227                   +1                 --- |
| Optimal                                                   83                   84                   +1                 --- |
| Solutions with <0.1% primal gap                          134                  139                   +5                 --- |
| Nodes explored (mean)                              1.329e+07            1.295e+07           -3.335e+05               -2.51 |
| Nodes explored (shifted geomean)                   1.426e+04            1.372e+04               -539.2               -3.78 |
| Relative MIP gap (mean)                               0.2893               0.2854            -0.003857               -1.33 |
| Relative MIP gap (shifted geomean)                   0.09705              0.09449            -0.002568               -2.65 |
| Solve time (mean)                                      426.6                428.7               +2.022              +0.474 |
| Solve time (shifted geomean)                           202.4                207.1               +4.726               +2.33 |
| Primal gap (mean)                                      11.31                10.89              -0.4276               -3.78 |
| Primal gap (shifted geomean)                          0.5319               0.4957             -0.03619                -6.8 |
| Primal integral (mean)                                 32.45                31.21               -1.242               -3.83 |
| Primal integral (shifted geomean)                      6.582                6.147              -0.4357               -6.62 |
------------------------------------------------------------------------------------------------------------------------------

Vector length diving

================================================================================
 main-2026-06-01 (1) vs vector-length-diving (2)
================================================================================

------------------------------------------------------------------------------------------------------------------------------
|                                        |       Run 1        |       Run 2        |     Abs. Diff.     |   Rel. Diff. (%)   |
------------------------------------------------------------------------------------------------------------------------------
| Imported                                                 240                  240                   +0                 --- |
| Feasible                                                 226                  228                   +2                 --- |
| Optimal                                                   83                   84                   +1                 --- |
| Solutions with <0.1% primal gap                          134                  136                   +2                 --- |
| Nodes explored (mean)                              1.329e+07            1.285e+07           -4.331e+05               -3.26 |
| Nodes explored (shifted geomean)                   1.426e+04            1.312e+04                -1134               -7.95 |
| Relative MIP gap (mean)                               0.2893               0.3086             +0.01929               +6.67 |
| Relative MIP gap (shifted geomean)                   0.09705              0.09751            +0.000458              +0.472 |
| Solve time (mean)                                      426.6                421.3               -5.318               -1.25 |
| Solve time (shifted geomean)                           202.4                195.7                -6.75               -3.33 |
| Primal gap (mean)                                      11.31                10.47              -0.8411               -7.43 |
| Primal gap (shifted geomean)                          0.5319               0.5132             -0.01875               -3.53 |
| Primal integral (mean)                                 32.45                33.07              +0.6235               +1.92 |
| Primal integral (shifted geomean)                      6.582                 6.62             +0.03761              +0.571 |
------------------------------------------------------------------------------------------------------------------------------

Farkas diving

================================================================================
 main-2026-06-01 (1) vs farkas-diving (2)
================================================================================

------------------------------------------------------------------------------------------------------------------------------
|                                        |       Run 1        |       Run 2        |     Abs. Diff.     |   Rel. Diff. (%)   |
------------------------------------------------------------------------------------------------------------------------------
| Imported                                                 240                  240                   +0                 --- |
| Feasible                                                 226                  228                   +2                 --- |
| Optimal                                                   83                   84                   +1                 --- |
| Solutions with <0.1% primal gap                          134                  132                   -2                 --- |
| Nodes explored (mean)                              1.329e+07            1.322e+07            -6.36e+04              -0.479 |
| Nodes explored (shifted geomean)                   1.426e+04            1.436e+04               +107.1              +0.751 |
| Relative MIP gap (mean)                               0.2893                0.291            +0.001754              +0.606 |
| Relative MIP gap (shifted geomean)                   0.09705              0.09512            -0.001935               -1.99 |
| Solve time (mean)                                      426.6                429.4               +2.721              +0.638 |
| Solve time (shifted geomean)                           202.4                205.5                 +3.1               +1.53 |
| Primal gap (mean)                                      11.31                10.46              -0.8573               -7.58 |
| Primal gap (shifted geomean)                          0.5319               0.5099             -0.02204               -4.14 |
| Primal integral (mean)                                 32.45                33.11              +0.6652               +2.05 |
| Primal integral (shifted geomean)                      6.582                6.342              -0.2408               -3.66 |
------------------------------------------------------------------------------------------------------------------------------

References

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

[2] 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

nguidotti added 4 commits June 2, 2026 12:36
…udo_costs, worker and diving_heuristics.

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

Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
@nguidotti nguidotti added this to the 26.08 milestone Jun 8, 2026
@nguidotti nguidotti self-assigned this Jun 8, 2026
@nguidotti nguidotti requested a review from a team as a code owner June 8, 2026 07:55
@nguidotti nguidotti requested a review from rg20 June 8, 2026 07:55
@nguidotti nguidotti added the non-breaking Introduces a non-breaking change label Jun 8, 2026
@nguidotti nguidotti requested a review from Kh4ster June 8, 2026 07:55
@nguidotti nguidotti added improvement Improves an existing functionality mip labels Jun 8, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 8, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds templated diving hyper-parameters, implements Farkas and vector-length diving heuristics, extends strategy enums and enablement logic, updates solver settings and worker wiring, and registers new configuration parameters.

Changes

Diving Heuristics Configuration and Integration

Layer / File(s) Summary
Diving hyper-parameters data contract
cpp/include/cuopt/linear_programming/constants.h, cpp/include/cuopt/linear_programming/mip/diving_hyper_params.hpp
New mip_diving_hyper_params_t<i_t, f_t> struct defines diving strategy toggles (line-search, pseudocost, guided, coefficient, Farkas, vector-length), depth/node/iteration/backtrack limits, Farkas coefficient dynamism tolerance, and logging control, along with string constant parameter keys.
Configuration type templating and integration
cpp/include/cuopt/linear_programming/mip/heuristics_hyper_params.hpp, cpp/include/cuopt/linear_programming/mip/solver_settings.hpp, cpp/src/dual_simplex/simplex_solver_settings.hpp
mip_heuristics_hyper_params_t converted to template over (i_t, f_t) for type flexibility; mip_solver_settings_t updated to template and include diving_params member; simplex_solver_settings_t switched from local diving_heuristics_settings_t to public mip_diving_hyper_params_t.
Search strategy enumeration and enablement
cpp/src/branch_and_bound/constants.hpp, cpp/src/branch_and_bound/diving_heuristics.hpp
Added FARKAS_DIVING and VECTOR_LENGTH_DIVING to enum and constants (total 7 strategies); introduced feasible_solution_symbol(strategy, log_diving_type) for runtime symbol generation and is_search_strategy_enabled(strategy, settings) to gate strategies based on nonzero hyper-parameter fields.
Farkas and vector-length diving implementations
cpp/src/branch_and_bound/diving_heuristics.cpp, cpp/src/branch_and_bound/diving_heuristics.hpp
Implemented farkas_diving() selecting variables using objective-coefficient magnitude and LP bounds; implemented vector_length_diving() selecting by objective sign and column length; added debug logging to pseudocost_diving() and guided_diving(); provided explicit instantiations for int/double.
LP problem coefficient tracking
cpp/src/dual_simplex/presolve.hpp
Added max_abs_obj_coeff and min_abs_obj_coeff fields to lp_problem_t to enable objective dynamism checks guiding Farkas strategy enablement.
Branch-and-bound diving integration
cpp/src/branch_and_bound/branch_and_bound.cpp
Updated variable selection switch to route FARKAS_DIVING and VECTOR_LENGTH_DIVING to their implementations; integrated diving settings lifecycle management in best-first search (disable guided without incumbent, disable Farkas if low dynamism, re-enable guided when incumbent acquired); updated feasible-solution reporting to use diving-type-aware symbols.
Worker diving enablement refactoring
cpp/src/branch_and_bound/worker.hpp, cpp/src/branch_and_bound/pseudo_costs.hpp
Centralized is_search_strategy_enabled() logic in diving_heuristics.hpp; updated calculate_num_diving_workers() to accept mip_diving_hyper_params_t directly (removing has_incumbent parameter); replaced full header include with forward declarations in pseudo_costs.hpp.
Configuration wiring and tests
cpp/src/math_optimization/solver_settings.cu, cpp/src/mip_heuristics/solver.cu, cpp/tests/mip/heuristics_hyper_params_test.cu
Registered diving hyperparameters (float iteration factor, int toggles/limits, bool show type) in solver parameter registries; wired context.settings.diving_params to branch-and-bound settings during solver setup; updated test to explicitly instantiate templated heuristic-params type.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested labels

feature request

Suggested reviewers

  • rg20
  • rgsl888prabhu
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% 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 'Vector length + Farkas diving' clearly and concisely summarizes the two main diving heuristics being implemented, which are the primary focus of this pull request.
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 comprehensively details the implementation of two new diving heuristics (Farkas and vector length), explains their design principles, provides benchmark results, and references relevant literature.

✏️ 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: 5

🧹 Nitpick comments (1)
cpp/src/branch_and_bound/pseudo_costs.hpp (1)

32-37: 🏗️ Heavy lift

Forward declarations here conflict with the repository header policy.

This header now depends on forward declarations for branch_and_bound_worker_t/branch_and_bound_stats_t. Please refactor the include graph (for example by splitting strategy-enable utilities into a lighter header) so this header can include concrete dependencies instead of forward-declaring them.

As per coding guidelines: "Avoid forward declarations in favor of including headers in C++."

🤖 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/pseudo_costs.hpp` around lines 32 - 37, The forward
declarations of branch_and_bound_worker_t and branch_and_bound_stats_t in
pseudo_costs.hpp violate the header policy; replace these forward declarations
by including the concrete headers that define branch_and_bound_worker_t and
branch_and_bound_stats_t (or refactor by extracting the minimal strategy
utilities used by pseudo_costs.hpp into a lightweight header that both this file
and the full worker/stats headers can include). Update pseudo_costs.hpp to
`#include` the new/lightweight header (or the original concrete headers) so the
types are available without forward declarations and adjust any include guards
or dependencies accordingly.

Source: Coding guidelines

🤖 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/include/cuopt/linear_programming/constants.h`:
- Around line 117-130: The constants header is missing the config key for the
parameter defined as farkas_obj_dynamism_tol in diving_hyper_params.hpp; add a
matching string constant (e.g., CUOPT_MIP_HYPER_DIVING_FARKAS_OBJ_DYNAMISM_TOL)
to cpp/include/cuopt/linear_programming/constants.h next to the other
diving-related defines so the config loader can find
"mip_hyper_diving_farkas_obj_dynamism_tol"; ensure the macro name matches the
naming pattern used for other keys (CUOPT_MIP_HYPER_DIVING_*) and the string
matches the parameter name used in diving_hyper_params.hpp.

In `@cpp/src/branch_and_bound/branch_and_bound.cpp`:
- Around line 1754-1757: Guard the division by checking objective magnitudes
before computing obj_dyn: if original_lp_.max_abs_obj_coeff == 0 set
diving_settings.farkas_diving = 0 (all-zero objective -> treat as low dynamism),
else only compute obj_dyn = log10(max/min) when original_lp_.min_abs_obj_coeff >
0 (use a tiny epsilon if you prefer to treat near-zero as zero); if min <= 0
skip the log10 check (leave farkas_diving enabled) to avoid inf/NaN, and keep
the existing comparison against diving_settings.farkas_obj_dynamism_tol when
obj_dyn is valid. Ensure you reference original_lp_.max_abs_obj_coeff,
original_lp_.min_abs_obj_coeff, diving_settings.farkas_diving, and
diving_settings.farkas_obj_dynamism_tol in the fix.

In `@cpp/src/branch_and_bound/diving_heuristics.cpp`:
- Around line 292-299: The Farkas score uses the wrong post-branch distances:
when dir == branch_direction_t::UP the branch bounds fix x_j >=
std::ceil(solution[j]) so compute remaining distance as lp.upper[j] -
std::ceil(solution[j]) (not lp.upper[j] - std::floor(solution[j])), and when dir
== branch_direction_t::DOWN use std::floor(solution[j]) - lp.lower[j] (not
std::ceil(solution[j]) - lp.lower[j]); update the calculation of score (the
block around score, lp.upper, lp.lower, solution[j], f_up, f_down, c, and f_t)
to use these post-branch bounds and keep the infinity fallback for non-finite
bounds.

In `@cpp/src/math_optimization/solver_settings.cu`:
- Line 175: The current option for CUOPT_MIP_HYPER_DIVING_BACKTRACK_LIMIT
exposes a raw upper bound of std::numeric_limits<i_t>::max(), but code
calculating the DFS stack uses (diving_backtrack_limit + 4) and can overflow;
change the option's maximum to a safe cap such as
(std::numeric_limits<i_t>::max() - 4) (or define a named constant like
MAX_SAFE_BACKTRACK = std::numeric_limits<i_t>::max() - 4) and use that instead
for the max value of mip_settings.diving_params.backtrack_limit so adding 4
cannot overflow.

---

Nitpick comments:
In `@cpp/src/branch_and_bound/pseudo_costs.hpp`:
- Around line 32-37: The forward declarations of branch_and_bound_worker_t and
branch_and_bound_stats_t in pseudo_costs.hpp violate the header policy; replace
these forward declarations by including the concrete headers that define
branch_and_bound_worker_t and branch_and_bound_stats_t (or refactor by
extracting the minimal strategy utilities used by pseudo_costs.hpp into a
lightweight header that both this file and the full worker/stats headers can
include). Update pseudo_costs.hpp to `#include` the new/lightweight header (or the
original concrete headers) so the types are available without forward
declarations and adjust any include guards or dependencies accordingly.
🪄 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: b33259e4-7a93-4f0f-9856-e8d5aea35845

📥 Commits

Reviewing files that changed from the base of the PR and between 2384454 and 414d940.

📒 Files selected for processing (15)
  • cpp/include/cuopt/linear_programming/constants.h
  • cpp/include/cuopt/linear_programming/mip/diving_hyper_params.hpp
  • cpp/include/cuopt/linear_programming/mip/heuristics_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/constants.hpp
  • cpp/src/branch_and_bound/diving_heuristics.cpp
  • cpp/src/branch_and_bound/diving_heuristics.hpp
  • cpp/src/branch_and_bound/pseudo_costs.hpp
  • cpp/src/branch_and_bound/worker.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/solver.cu
  • cpp/tests/mip/heuristics_hyper_params_test.cu

Comment thread cpp/include/cuopt/linear_programming/constants.h
Comment thread cpp/src/branch_and_bound/branch_and_bound.cpp
Comment thread cpp/src/branch_and_bound/branch_and_bound.cpp
Comment thread cpp/src/branch_and_bound/diving_heuristics.cpp
Comment thread cpp/src/math_optimization/solver_settings.cu Outdated
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
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.

1 participant