-
Notifications
You must be signed in to change notification settings - Fork 112
Simple diving for Branch-and-Bound #305
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
Conversation
Signed-off-by: nicolas <nguidotti@nvidia.com>
|
/ok to test f9fb5ad |
|
/ok to test 041e104 |
|
/ok to test dd7e340 |
| ? (lower_bound == 0.0 ? 0.0 : std::numeric_limits<f_t>::infinity()) | ||
| : std::abs(obj_value - lower_bound) / std::abs(obj_value); | ||
| if (user_mip_gap != user_mip_gap) { return std::numeric_limits<f_t>::infinity(); } | ||
| // Handle NaNs (i.e., NaN != NaN) |
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.
The test user_mip_gap != user_mip_gap is equivalent to std::is_nan
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.
This change was suggested by Alice here
| i_t nodes_explored = 0; | ||
|
|
||
| while (node_stack.size() > 0) { | ||
| repair_heuristic_solutions(lower_bound, solution); |
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.
Why do you need to repair a heuristic solution in the dive?
I would let the best-first search thread handle repairs.
| lower_bound = lower_bound_ = root_node.lower_bound; | ||
| mutex_lower.unlock(); | ||
| gap = get_upper_bound() - lower_bound; | ||
| if (settings_.bnb_search_strategy == search_strategy_t::DEPTH_FIRST) { |
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.
We are in a dive. Don't we know what the search strategy is?
| stats_.nodes_unexplored = 0; | ||
| stats_.num_nodes = 1; | ||
|
|
||
| if (settings_.bnb_search_strategy == search_strategy_t::DEPTH_FIRST) { |
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.
Why would the search strategy ever be depth first here?
I think we only want best first or multithreaded best first with diving
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.
@akifcorduk wanted to have a diving only option for submip (for this reason, I have the checks above). However, for simplicity sake, we have just a single search strategy: bfs + diving. Is that OK?
|
|
||
| enum class search_strategy_t { | ||
| BEST_FIRST = 0, | ||
| DEPTH_FIRST = 1, |
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.
Remove DEPTH_FIRST?
| mutex_gap_.unlock(); | ||
| } | ||
| } | ||
|
|
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.
To confirm: in a multithreaded bfs + dives, the nodes explored and unexplored only comes from the BFS thread?
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.
Thanks for simplifying the PR. I left a few more comments. These are mostly nitpicks.
The only change I would suggest making before merging is to remove repairing solutions from the diving threads.
You should make sure to run the MIPLIB benchmarks and put the geomean performance improvement in the commit message. You should also run this on a MIP problem that is infeasible from the LP relaxation and one that is infeasible due to no integer solution and verify the code correctly handles these cases.
Signed-off-by: nicolas <nguidotti@nvidia.com>
|
/ok to test 413ee24 |
Signed-off-by: nicolas <nguidotti@nvidia.com>
|
/ok to test 53127f5 |
|
/ok to test 772f49e |
|
I got the following results for the MIPLIB2017 benchmark (arithmetic means of the primal gap): i.e., a 1.6% improvement over the main branch. The |
|
/merge |
This PR introduces the following changes: - Implements a simple diving procedure - Allows the branch-and-bound to switch between different search strategies: `BEST_FIRST`, `DEPTH_FIRST` and `MULTITHREADED_BEST_FIRST_WITH_DIVING` - Refactor the branch-and-bound code such that the `solve` function is now organized into separated methods - Moved some commonly used variables to be member variables in the branch-and-bound solver. Authors: - Nicolas L. Guidotti (https://github.com/nguidotti) - Ramakrishnap (https://github.com/rgsl888prabhu) - https://github.com/ahehn-nv Approvers: - Gil Forsyth (https://github.com/gforsyth) - Akif ÇÖRDÜK (https://github.com/akifcorduk) - Trevor McKay (https://github.com/tmckayus) - Chris Maes (https://github.com/chris-maes) URL: #305
This PR implement a parallel branch-and-bound procedure, which is split into two phases. In the first phase, the algorithm will greedily expand the search tree until a certain depth and then add the bottom nodes to a global heap. The parallel expansion is implemented using `omp task`. In the second phase, some threads will explore the tree using best first search with plunging, i.e., they take the first node from the global heap and then explore the entire branch that starts on this node. Any unexplored node are insert into the heap. The remaining threads will perform deep dives in order to find feasible solutions. The solver keep a small heap contains the most promising nodes to perform the dives, which is keep in sync with the global heap. This PR also - Replace the `std::thread`-based parallelization in the strong branching with OpenMP in order to use dynamic scheduling. This ensures that all threads have similar amount of work and improve parallel performance. - Fixed invalid memory access when trying to access the status of a fathomed node. - Replaced `std::mutex` with `omp atomic` whatever applicable. - Added dedicated classes `dive_queue_t` and `search_tree_t` to store the diving heap and the search tree, respectively. This is an extension of #305. Closes #320. Closes #417. ## Benchmark results (MIPLIB2017): master branch (53d6e74) ``` Average Gap: 0.2174861712 ``` This PR: ``` Average Gap: 0.1989485546 ``` i.e., a `1.8%` improvement. In terms of the geomean of the gap ratio, this is equal to `1.62x`. Authors: - Nicolas L. Guidotti (https://github.com/nguidotti) Approvers: - Rajesh Gandham (https://github.com/rg20) - Chris Maes (https://github.com/chris-maes) URL: #412
This PR introduces the following changes:
BEST_FIRST,DEPTH_FIRSTandMULTITHREADED_BEST_FIRST_WITH_DIVINGsolvefunction is now organized into separated methods