Skip to content

Fix bug where batch PDLP for strong branching was running on problem without cuts#951

Open
chris-maes wants to merge 1 commit intoNVIDIA:mainfrom
chris-maes:strong_branching_batch_lp_translate_lp_problem
Open

Fix bug where batch PDLP for strong branching was running on problem without cuts#951
chris-maes wants to merge 1 commit intoNVIDIA:mainfrom
chris-maes:strong_branching_batch_lp_translate_lp_problem

Conversation

@chris-maes
Copy link
Contributor

@chris-maes chris-maes commented Mar 11, 2026

Thanks to Nicolas for discovering the issue.

We now translate the problem with cuts directly to a problem batch PDLP can solve.

@chris-maes chris-maes requested a review from a team as a code owner March 11, 2026 17:00
@chris-maes chris-maes requested review from hlinsen and rg20 March 11, 2026 17:00
@copy-pr-bot
Copy link

copy-pr-bot bot commented Mar 11, 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.

@Kh4ster Kh4ster added bug Something isn't working non-breaking Introduces a non-breaking change pdlp mip labels Mar 11, 2026
@coderabbitai
Copy link

coderabbitai bot commented Mar 11, 2026

📝 Walkthrough

Walkthrough

The pull request refactors the strong_branching function signature to remove the user_problem_t parameter and introduce new parameters including new_slacks, root_objective, root_vstatus, edge_norms, and pseudo_costs_t. The supporting simplex_problem_to_mps_data_model function is also updated to accept lp_problem_t directly along with slack-related data.

Changes

Cohort / File(s) Summary
Strong Branching Signature Update
cpp/src/branch_and_bound/pseudo_costs.hpp, cpp/src/branch_and_bound/pseudo_costs.cpp
Refactored strong_branching function signature to remove user_problem_t parameter and add new_slacks, root_objective, root_vstatus, edge_norms, and pseudo_costs_t& pc parameters. Updated simplex_problem_to_mps_data_model to accept lp_problem_t instead of user_problem_t, along with new slack indices and root solution vectors. Implementation updated to match new signature and dependencies.
Strong Branching Call Sites
cpp/src/branch_and_bound/branch_and_bound.cpp
Updated two strong_branching call sites within the solve method: first call adds new_slacks_ parameter; second call adds new_slacks_, root_objective_, root_vstatus_, edge_norms_, and pc_ parameters. Both calls remove original_problem_ argument and reorder remaining parameters.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the bug being fixed: batch PDLP for strong branching running on problems without cuts, which aligns with the implementation changes.
Description check ✅ Passed The description is directly related to the changeset, explaining the fix involves translating the problem with cuts to one batch PDLP can solve.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

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)

2409-2420: ⚠️ Potential issue | 🟠 Major

Batch PDLP still receives the cut-free root state.

remove_cuts(...) already ran on Lines 2339-2355, and fractional is recomputed after that on Lines 2360-2361. So this call still feeds the stripped original_lp_ / root_relax_soln_ / root_vstatus_ state into strong_branching, which means the new lp_problem_t-based path never sees the cut-strengthened root relaxation. If the goal of this PR is to score strong branches against the root LP with cuts, this needs to move before remove_cuts(...) or use a snapshot captured before that projection. As per coding guidelines, "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".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cpp/src/branch_and_bound/branch_and_bound.cpp` around lines 2409 - 2420,
strong_branching is being called with the cut-stripped state (original_lp_,
root_relax_soln_, root_vstatus_, fractional) after remove_cuts(...) runs, so the
branch scoring never sees the cut-strengthened root LP; move the
strong_branching<i_t,f_t>(...) invocation to run before remove_cuts(...) or else
capture a snapshot of the pre-remove_cuts state (including original_lp_,
root_relax_soln_, root_vstatus_, and the recomputed fractional) and pass that
snapshot into strong_branching so the function evaluates branches against the
cut-strengthened root relaxation.
🧹 Nitpick comments (1)
cpp/src/branch_and_bound/pseudo_costs.cpp (1)

243-279: Project the non-slack vectors with the same mask as the matrix.

A_no_slacks.remove_columns(cols_to_remove) is order-agnostic, but original_root_soln_x, objective, lower, and upper are still built by taking the first n entries. That keeps this helper coupled to the current “slacks are suffix columns” layout. Rebuilding those arrays with cols_to_remove would make the translator self-contained and safer if column insertion order ever changes.

♻️ Suggested direction
-  int n = lp.num_cols - new_slacks.size();
-  original_root_soln_x.resize(n);
+  int n = lp.num_cols - new_slacks.size();
+  std::vector<f_t> objective_no_slacks;
+  std::vector<f_t> lower_no_slacks;
+  std::vector<f_t> upper_no_slacks;
+  objective_no_slacks.reserve(n);
+  lower_no_slacks.reserve(n);
+  upper_no_slacks.reserve(n);
+  original_root_soln_x.clear();
+  original_root_soln_x.reserve(n);
@@
-  for (i_t j = 0; j < n; j++) {
-    original_root_soln_x[j] = root_soln[j];
-  }
+  for (i_t j = 0; j < lp.num_cols; ++j) {
+    if (cols_to_remove[j]) { continue; }
+    objective_no_slacks.push_back(lp.objective[j]);
+    lower_no_slacks.push_back(lp.lower[j]);
+    upper_no_slacks.push_back(lp.upper[j]);
+    original_root_soln_x.push_back(root_soln[j]);
+  }
@@
-  mps_model.set_objective_coefficients(lp.objective.data(), n);
+  mps_model.set_objective_coefficients(objective_no_slacks.data(), n);
@@
-  mps_model.set_variable_lower_bounds(lp.lower.data(), n);
-  mps_model.set_variable_upper_bounds(lp.upper.data(), n);
+  mps_model.set_variable_lower_bounds(lower_no_slacks.data(), n);
+  mps_model.set_variable_upper_bounds(upper_no_slacks.data(), n);

Based on learnings, "Reduce tight coupling between solver components (presolve, simplex, basis, barrier); increase modularity and reusability of optimization algorithms".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cpp/src/branch_and_bound/pseudo_costs.cpp` around lines 243 - 279, The code
currently trims columns from A via A_no_slacks.remove_columns(cols_to_remove)
but still builds original_root_soln_x and passes lp.objective, lp.lower,
lp.upper by taking the first n entries, assuming slacks were trailing; change
this to project those vectors using the same cols_to_remove mask so their
entries align with the column order of A_no_slacks: build a
projected_original_root_soln_x by iterating over the full root_soln and copying
entries where cols_to_remove[j]==0 (maintaining original column order),
similarly construct projected_objective, projected_lower, and projected_upper
from lp.objective, lp.lower, lp.upper using cols_to_remove, then pass those
projected arrays to mps_model.set_objective_coefficients and
set_variable_lower_bounds/set_variable_upper_bounds and use
projected_original_root_soln_x instead of slicing the first n elements.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@cpp/src/branch_and_bound/branch_and_bound.cpp`:
- Around line 2409-2420: strong_branching is being called with the cut-stripped
state (original_lp_, root_relax_soln_, root_vstatus_, fractional) after
remove_cuts(...) runs, so the branch scoring never sees the cut-strengthened
root LP; move the strong_branching<i_t,f_t>(...) invocation to run before
remove_cuts(...) or else capture a snapshot of the pre-remove_cuts state
(including original_lp_, root_relax_soln_, root_vstatus_, and the recomputed
fractional) and pass that snapshot into strong_branching so the function
evaluates branches against the cut-strengthened root relaxation.

---

Nitpick comments:
In `@cpp/src/branch_and_bound/pseudo_costs.cpp`:
- Around line 243-279: The code currently trims columns from A via
A_no_slacks.remove_columns(cols_to_remove) but still builds original_root_soln_x
and passes lp.objective, lp.lower, lp.upper by taking the first n entries,
assuming slacks were trailing; change this to project those vectors using the
same cols_to_remove mask so their entries align with the column order of
A_no_slacks: build a projected_original_root_soln_x by iterating over the full
root_soln and copying entries where cols_to_remove[j]==0 (maintaining original
column order), similarly construct projected_objective, projected_lower, and
projected_upper from lp.objective, lp.lower, lp.upper using cols_to_remove, then
pass those projected arrays to mps_model.set_objective_coefficients and
set_variable_lower_bounds/set_variable_upper_bounds and use
projected_original_root_soln_x instead of slicing the first n elements.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c1065a0a-3686-4088-bff9-6c0dcd8e7f4f

📥 Commits

Reviewing files that changed from the base of the PR and between d862480 and 721a56a.

📒 Files selected for processing (3)
  • cpp/src/branch_and_bound/branch_and_bound.cpp
  • cpp/src/branch_and_bound/pseudo_costs.cpp
  • cpp/src/branch_and_bound/pseudo_costs.hpp

@anandhkb anandhkb added this to the 26.04 milestone Mar 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants