Skip to content

Conversation

@nguidotti
Copy link
Contributor

@nguidotti nguidotti commented Oct 9, 2025

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.472358 with 224 feasible solutions
This PR: 14.058718 with 226 feasible solutions

On average, the main branch explored 1012412 nodes. This PR increases to 1607821 nodes (i.e., 58% more nodes) for the same time frame.

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

Summary by CodeRabbit

Release Notes

  • New Features

    • Integrated bounds strengthening into branch-and-bound presolver for tighter variable bound management.
    • Enhanced logging configuration with customizable file output modes.
  • Improvements

    • Improved solver status tracking and internal thread-type handling for better diagnostics.
    • Optimized basis update threshold tolerances for numerical stability.
  • Refactor

    • Restructured internal solver APIs for cleaner architecture and improved maintainability.
    • Migrated constraint bound management to unified presolver framework.

@nguidotti nguidotti added this to the 25.12 milestone Oct 9, 2025
@nguidotti nguidotti self-assigned this Oct 9, 2025
@nguidotti nguidotti added non-breaking Introduces a non-breaking change improvement Improves an existing functionality labels Oct 9, 2025
@copy-pr-bot
Copy link

copy-pr-bot bot commented Oct 9, 2025

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.

@nguidotti
Copy link
Contributor Author

/ok to test f44b134

@nguidotti nguidotti marked this pull request as ready for review October 14, 2025 13:06
@nguidotti nguidotti requested a review from a team as a code owner October 14, 2025 13:06
@nguidotti nguidotti requested review from aliceb-nv and rg20 October 14, 2025 13:06
Copy link

@greptile-apps greptile-apps bot left a 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.hpp to 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"
Loading

3 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

@nguidotti nguidotti changed the base branch from branch-25.10 to branch-25.12 October 14, 2025 13:39
@nguidotti nguidotti requested review from a team as code owners October 14, 2025 13:39
@nguidotti nguidotti requested review from jakirkham and rgsl888prabhu and removed request for a team October 14, 2025 13:39
@rg20 rg20 changed the base branch from branch-25.12 to branch-25.10 October 14, 2025 13:56
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);
Copy link
Contributor

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 {
Copy link
Contributor

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

Copy link
Contributor

@chris-maes chris-maes left a 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!

@akifcorduk
Copy link
Contributor

/merge

@nguidotti nguidotti requested review from a team, AyodeAwe and tmckayus and removed request for a team November 21, 2025 12:59
@rapids-bot rapids-bot bot merged commit a9b279f into NVIDIA:release/25.12 Nov 24, 2025
352 of 358 checks passed
@nguidotti nguidotti deleted the bounds-propagation branch November 24, 2025 09:20
rapids-bot bot pushed a commit that referenced this pull request Nov 25, 2025
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

improvement Improves an existing functionality non-breaking Introduces a non-breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants