Skip to content

Fix root cut CPUFJ solutions being dropped by the GPU heuristics - #1497

Merged
rapids-bot[bot] merged 2 commits into
NVIDIA:mainfrom
aliceb-nv:fix/issue-1488-cpufj-incumbent
Jul 2, 2026
Merged

Fix root cut CPUFJ solutions being dropped by the GPU heuristics#1497
rapids-bot[bot] merged 2 commits into
NVIDIA:mainfrom
aliceb-nv:fix/issue-1488-cpufj-incumbent

Conversation

@aliceb-nv

@aliceb-nv aliceb-nv commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Root cut CPUFJ solutions were only registered at the B&B layer, which resulted in GPU heuristics assuming the early heuristic incumbent was the best available solution and dropping the root cut solutions.

This is addressed by sending rootcut CPUFJ solutions to gpu heursitic callbacks, and ensuring that the B&B solution is taken at the end of the solve if it is better than the last known incumbent on the gpu heuristic side.

Closes #1488

Description

Issue

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

@aliceb-nv aliceb-nv added this to the 26.08 milestone Jul 1, 2026
@aliceb-nv
aliceb-nv requested a review from a team as a code owner July 1, 2026 16:25
@aliceb-nv aliceb-nv added bug Something isn't working non-breaking Introduces a non-breaking change labels Jul 1, 2026
@aliceb-nv
aliceb-nv requested review from akifcorduk and hlinsen July 1, 2026 16:25
@aliceb-nv

Copy link
Copy Markdown
Contributor Author

/ok to test 5906fc1

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

CI Test Summary

✅ All 31 test job(s) passed.

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Two independent small changes: branch_and_bound.cpp adds a solution_callback invocation in the nondeterministic Root Cut CPUFJ improvement path, and solver.cu adds logic to import branch-and-bound's solver-space incumbent into the final returned solution when it is feasible and improves upon the current solution.

Changes

B&B Solution Reporting and Incumbent Import

Layer / File(s) Summary
Nondeterministic solution callback
cpp/src/branch_and_bound/branch_and_bound.cpp
In the Root Cut CPUFJ improvement callback's nondeterministic branch, calls settings_.solution_callback(user_assignment, obj) before set_solution_from_heuristics.
Incumbent import into final solution
cpp/src/mip_heuristics/solver.cu
After dm.run_solver() returns, checks for a B&B solver-space incumbent, builds a solution_t from branch_and_bound_solution.x, computes feasibility, and replaces sol when feasible and better or when sol was infeasible.

Estimated code review effort: 2 (Simple) | ~10 minutes

Related PRs: None identified.

Suggested labels: branch-and-bound, mip-heuristics

Suggested reviewers: None identified.

🐰 A hop through branches, incumbents in tow,
A callback whispers what solvers should know,
Fourteen lines change, both quiet and small,
Yet nudge the solution to stand tall.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main fix: preserving root cut CPUFJ solutions through GPU heuristics.
Description check ✅ Passed The description accurately describes the root cut CPUFJ and B&B incumbent handling changes in the PR.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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/branch_and_bound.cpp`:
- Around line 2694-2696: The `solution_callback` call in `branch_and_bound.cpp`
is firing too early inside the CPUFJ improvement path. Update this branch to
mirror the gating used in `repair_heuristic_solutions` and
`add_feasible_solution`: only invoke `settings_.solution_callback` after
`improves_incumbent(...)` succeeds and the candidate has passed feasibility
validation through `set_solution_from_heuristics`/`check_guess`. Keep the
callback aligned with a genuine new incumbent so
`dm->rins.new_best_incumbent_callback` is only notified for validated incumbent
improvements.
🪄 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: 8908fd70-28df-45a2-9675-f2bee5baecfd

📥 Commits

Reviewing files that changed from the base of the PR and between 52ca23a and a7df065.

📒 Files selected for processing (2)
  • cpp/src/branch_and_bound/branch_and_bound.cpp
  • cpp/src/mip_heuristics/solver.cu

Comment on lines +2694 to +2696
if (settings_.solution_callback != nullptr) {
settings_.solution_callback(user_assignment, obj);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Unconditional solution_callback invocation bypasses the incumbent-improvement/feasibility gate used elsewhere.

Every other call site (repair_heuristic_solutions, add_feasible_solution) only invokes settings_.solution_callback after confirming the solution actually improves the incumbent (improves_incumbent(...)) and is feasibility-checked. Here the callback fires unconditionally for any CPUFJ improvement, before set_solution_from_heuristics validates feasibility via check_guess. Since the callback drives dm->rins.new_best_incumbent_callback, which is documented/named to represent a genuine new best incumbent, this can report non-improving or not-yet-validated solutions to RINS/population, polluting downstream state.

🐛 Proposed fix to gate the callback on actual incumbent improvement
       } else {
-        if (settings_.solution_callback != nullptr) {
-          settings_.solution_callback(user_assignment, obj);
-        }
+        if (settings_.solution_callback != nullptr && improves_incumbent(obj)) {
+          settings_.solution_callback(user_assignment, obj);
+        }
         set_solution_from_heuristics(user_assignment);
       }
📝 Committable suggestion

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

Suggested change
if (settings_.solution_callback != nullptr) {
settings_.solution_callback(user_assignment, obj);
}
if (settings_.solution_callback != nullptr && improves_incumbent(obj)) {
settings_.solution_callback(user_assignment, obj);
}
🤖 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 2694 - 2696, The
`solution_callback` call in `branch_and_bound.cpp` is firing too early inside
the CPUFJ improvement path. Update this branch to mirror the gating used in
`repair_heuristic_solutions` and `add_feasible_solution`: only invoke
`settings_.solution_callback` after `improves_incumbent(...)` succeeds and the
candidate has passed feasibility validation through
`set_solution_from_heuristics`/`check_guess`. Keep the callback aligned with a
genuine new incumbent so `dm->rins.new_best_incumbent_callback` is only notified
for validated incumbent improvements.

sol = dm.run_solver();
} // implicit barrier for all tasks created in B&B and heuristics

if (!context.settings.heuristics_only && branch_and_bound->has_solver_space_incumbent()) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why is this needed? The callback already registers it on the heuristic thread, right? Is it because of the different thread completion orders?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It's not, I added this to err on the safe side in case future additions neglect to run the gpu heuristic callback

@aliceb-nv

Copy link
Copy Markdown
Contributor Author

/merge

@rapids-bot
rapids-bot Bot merged commit cb0a2b6 into NVIDIA:main Jul 2, 2026
91 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working non-breaking Introduces a non-breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] cuopt_cli MIP emits stale suboptimal incumbent when optimal solution found quickly

2 participants