-
Notifications
You must be signed in to change notification settings - Fork 110
Propagate the bounds from the parent to the child nodes #473
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Propagate the bounds from the parent to the child nodes #473
Conversation
…s when detaching the node from the tree. add backtracking argument to diving.
|
Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually. Contributors can view more details about this message here. |
|
/ok to test f44b134 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Greptile Overview
Greptile Summary
This PR implements bounds propagation optimization in the branch-and-bound solver for mixed-integer programming (MIP). The key change is that bounds (variable upper/lower limits) are now efficiently propagated from parent nodes to child nodes during tree traversal, rather than being recomputed from scratch at every node. The implementation tracks which bounds have changed using a bounds_changed vector and selectively triggers full recomputation only when necessary (when nodes become infeasible or integer feasible solutions are found). The changes span three core files: branch_and_bound.hpp updates the method signature, mip_node.hpp refactors bounds update logic into separate methods, and branch_and_bound.cpp implements the conditional bounds update mechanism with performance optimizations for the dual simplex solver.
Important Files Changed
Changed Files
| Filename | Score | Overview |
|---|---|---|
| cpp/src/dual_simplex/branch_and_bound.hpp | 5/5 | Updated solve_node method signature to support bounds propagation tracking |
| cpp/src/dual_simplex/mip_node.hpp | 4/5 | Refactored bounds update logic with new update_variable_bound method and optimized traversal |
| cpp/src/dual_simplex/branch_and_bound.cpp | 4/5 | Implemented conditional bounds recomputation logic with performance optimizations |
Confidence score: 4/5
- This PR implements a well-understood optimization for branch-and-bound algorithms with clear performance benefits
- Score reflects solid implementation but complexity in bounds management logic could introduce subtle bugs if bounds tracking becomes inconsistent
- Pay close attention to the bounds update logic in
mip_node.hppto ensure tighter bounds are never overwritten by looser ones
Sequence Diagram
sequenceDiagram
participant User
participant BranchAndBound as "Branch & Bound Solver"
participant Node as "MIP Node"
participant LP as "LP Solver"
User->>BranchAndBound: "solve(solution)"
BranchAndBound->>BranchAndBound: "solve_node()"
Note over BranchAndBound: Bounds propagation logic
BranchAndBound->>Node: "get_variable_bounds(lower, upper, bounds_changed)"
Node->>Node: "update_variable_bound() for current node"
loop For each parent node up to root
Node->>Node: "update_variable_bound() from parent"
Note over Node: Propagates bounds from parent to child
end
Node-->>BranchAndBound: "Updated bounds with bounds_changed flags"
BranchAndBound->>LP: "bound_strengthening()"
LP-->>BranchAndBound: "feasible status"
alt Node is feasible
BranchAndBound->>LP: "dual_phase2() with updated bounds"
LP-->>BranchAndBound: "LP solution"
alt Solution is integer feasible
BranchAndBound->>BranchAndBound: "add_feasible_solution()"
else Solution is fractional
BranchAndBound->>Node: "branch() - create child nodes"
Note over Node: Children inherit parent bounds
Node->>Node: "add_children(down_child, up_child)"
end
else Node is infeasible
BranchAndBound->>Node: "set_status(INFEASIBLE)"
end
BranchAndBound-->>User: "Final solution with bounds propagation"
3 files reviewed, no comments
| i_t nodes_unexplored = (--stats_.nodes_unexplored); | ||
| stats_.nodes_since_last_log++; | ||
| i_t nodes_explored = (++exploration_stats_.nodes_explored); | ||
| i_t nodes_unexplored = (--exploration_stats_.nodes_unexplored); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: same here why is this different than other increments and decrements in the code?
|
|
||
| template <typename i_t, typename f_t> | ||
| void upper_bound_callback(f_t upper_bound); | ||
| enum class node_children_status_t { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See comment on other PR about mixing status codes with info about which node to process
chris-maes
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. But let's either merge this PR or the other PR first.
Thanks!
|
/merge |
a9b279f
into
NVIDIA:release/25.12
With this PR, the child node can reuse the basis factorisation from the parent, reducing the time required for solving the LP relaxation of the child. The solver recomputes the basis factorisation when the branch reaches the end. This is a follow-up of #383 and #473. Average (Primal) Gap over the MIPLIB2017 dataset: `main` (10f116b): `14.472358` with `224` feasible solutions This PR: `~14.26` with `225` feasible solutions On average, the `main` branch explored `1012412` nodes. This PR increases to `1850797` nodes (i.e., `82%` more nodes) for the same time frame. Compared to #473, it explores `15%` more nodes. This also fixes an incorrect report of an infeasible solution after a timeout and there are no nodes in the heap. Authors: - Nicolas L. Guidotti (https://github.com/nguidotti) - Chris Maes (https://github.com/chris-maes) - Alice Boucher (https://github.com/aliceb-nv) Approvers: - Chris Maes (https://github.com/chris-maes) URL: #492
With this PR, the solver propagates the bounds from the parent to the child nodes. If the solver reaches the end of a branch (i.e., when the node becomes infeasible or an integer feasible solution is found), the bounds are recomputed from scratch. This allows the solver to reuse the tightened bounds from the parent and re-apply the bounds strengthening on the branched variable.
Average (Primal) Gap over the MIPLIB2017 dataset:
main(10f116b):14.472358with224feasible solutionsThis PR:
14.058718with226feasible solutionsOn average, the
mainbranch explored1012412nodes. This PR increases to1607821nodes (i.e.,58%more nodes) for the same time frame.Checklist
Summary by CodeRabbit
Release Notes
New Features
Improvements
Refactor