-
Notifications
You must be signed in to change notification settings - Fork 164
Master-Worker Framework for B&B #754
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
Closed
Closed
Changes from 76 commits
Commits
Show all changes
77 commits
Select commit
Hold shift + click to select a range
c9081a6
implemented diving heuristics. sorted starting nodes for diving based…
nguidotti 046a501
moved diving heuristics to a separated file
nguidotti 6ea6d72
organized code. added toggle to disable each type of diving.
nguidotti 5422b97
restrict calling RINS to the best-first threads
nguidotti 73c1a63
fix invalid branch var in line search diving
nguidotti 3a77cca
moved asserts
nguidotti 0f7af4e
replace inf and max with STL calls
nguidotti 79368c3
Fix incorrect infeasible list
chris-maes 6334ad7
implemented diving heuristics. sorted starting nodes for diving based…
nguidotti 2c94a7c
moved diving heuristics to a separated file
nguidotti 0e815e1
organized code. added toggle to disable each type of diving.
nguidotti 29a2a33
moved asserts
nguidotti 5a3ef60
unified global node heap. fathom node in the diving node that was alr…
nguidotti c181ccf
refactoring code
nguidotti 501c20d
fix style
nguidotti 0ee757a
small fixes
nguidotti 546b116
unified the best-first and diving heap into a single object. imposed …
nguidotti 06d531a
adjusted column spacing in bnb logs. added opening mode for logger.
nguidotti ae742c2
revert fixes for dual simplex. changed RINS and SubMIP to use guided …
nguidotti b5f2c7e
moved bound propagation logs to debug mode
nguidotti eb5c695
addressing code rabbit suggestions
nguidotti 5cf5ac0
added explicit conversion to float
nguidotti d7dfb0d
missing code revert in basis update
nguidotti 55e64cc
fixed variable type
nguidotti a26a816
Merge remote-tracking branch 'origin/main' into node-queue
nguidotti 17d3d7c
Merge remote-tracking branch 'origin/main' into diving-heuristics
nguidotti 0b1e994
added comments
nguidotti 9effdc8
added missing spacing
nguidotti 6d43e03
updated logs
nguidotti 98f670f
Merge branch 'main' into adjust-bnb-logs
nguidotti d54ecbd
Merge branch 'main' into node-queue
nguidotti 0aa344e
Merge branch 'main' into diving-heuristics
nguidotti fbb9966
refactoring
nguidotti 7a5284e
Merge branch 'adjust-bnb-logs' into diving-heuristics
nguidotti 78f38a4
adjust header spacing
nguidotti 668da14
Merge branch 'adjust-bnb-logs' into diving-heuristics
nguidotti dd8955f
fix compilation error
nguidotti 9f07cbb
Merge branch 'main' into diving-heuristics
nguidotti cb1208f
Merge branch 'main' into diving-heuristics
nguidotti ed20d95
Merge branch 'main' into node-queue
nguidotti 426b445
added cli option for disabling each diving heuristic
nguidotti 319bd22
fix style
nguidotti 3e52ffe
fix infeasible list (#694)
nguidotti 1178493
Merge branch 'main' into diving-heuristics
nguidotti 972b187
created a struct with all persistent data used by each worker
nguidotti d076da2
replace hashmap with a vector
nguidotti d8e3541
fix missing initialization
nguidotti 4796733
added a setting for each type of task. changed how to set the number …
nguidotti 308237e
first version of the new parallel b&b
nguidotti a0fa4e1
refactoring and fixing bugs
nguidotti 25b0bed
several bug fixes
nguidotti 25dac02
handle master suspension. added overdecomposition.
nguidotti 85eab64
fix incorrect number of workers
nguidotti 227d7f8
removed ramp-up phase
nguidotti 5aecd63
added some comments
nguidotti 8dd2d08
fix incorrect termination status
nguidotti 1dcee03
replace upper bound lock with atomic
nguidotti 9e2e5c7
improve idling master thread
nguidotti 34f7eaa
added ramp-up-phase
nguidotti 769a3d8
refactoring
nguidotti c54033c
updating code to match the new parallel bnb
nguidotti 28c61b8
Merge branch 'main' into diving-heuristics
nguidotti 4bcf801
removed command line options
nguidotti d91369d
fix style
nguidotti 4ee57f9
fix compilation failure
nguidotti b99a9c7
separated objective estimate and variable selection
nguidotti 43f8b31
separating objective estimate from variable selection
nguidotti a36bf03
added log
nguidotti d4c9d54
Merge branch 'node-queue' into diving-heuristics
nguidotti b9a14bf
Merge branch 'diving-heuristics' into parallel-bnb-v2
nguidotti 7c5c996
small refactor
nguidotti 5753de8
code cleanup
nguidotti 421cbfd
fix reporting frequency
nguidotti 6faeed0
fix style
nguidotti d7046e3
added missing stl headers. fix incorrect round-robin.
nguidotti 14441d1
refactor to eliminate enum
nguidotti 89cc6de
fix race condition in guided diving
nguidotti File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,279 @@ | ||
| /* clang-format off */ | ||
| /* | ||
| * SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
| /* clang-format on */ | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <dual_simplex/basis_updates.hpp> | ||
| #include <dual_simplex/bounds_strengthening.hpp> | ||
| #include <dual_simplex/mip_node.hpp> | ||
| #include <dual_simplex/phase2.hpp> | ||
|
|
||
| #include <array> | ||
| #include <deque> | ||
| #include <mutex> | ||
| #include <vector> | ||
|
|
||
| namespace cuopt::linear_programming::dual_simplex { | ||
|
|
||
| constexpr int bnb_num_worker_types = 5; | ||
|
|
||
| // Indicate the search and variable selection algorithms used by each thread | ||
| // in B&B (See [1]). | ||
| // | ||
| // [1] T. Achterberg, “Constraint Integer Programming,” PhD, Technischen Universität Berlin, | ||
| // Berlin, 2007. doi: 10.14279/depositonce-1634. | ||
| enum bnb_worker_type_t : int { | ||
| EXPLORATION = 0, // Best-First + Plunging. | ||
| PSEUDOCOST_DIVING = 1, // Pseudocost diving (9.2.5) | ||
| LINE_SEARCH_DIVING = 2, // Line search diving (9.2.4) | ||
| GUIDED_DIVING = 3, // Guided diving (9.2.3). | ||
| COEFFICIENT_DIVING = 4 // Coefficient diving (9.2.1) | ||
| }; | ||
|
|
||
| template <typename i_t, typename f_t> | ||
| struct bnb_stats_t { | ||
| f_t start_time = 0.0; | ||
| omp_atomic_t<f_t> total_lp_solve_time = 0.0; | ||
| omp_atomic_t<i_t> nodes_explored = 0; | ||
| omp_atomic_t<i_t> nodes_unexplored = 0; | ||
| omp_atomic_t<f_t> total_lp_iters = 0; | ||
| omp_atomic_t<i_t> nodes_since_last_log = 0; | ||
| omp_atomic_t<f_t> last_log = 0.0; | ||
| }; | ||
|
|
||
| template <typename i_t, typename f_t> | ||
| class bnb_worker_data_t { | ||
| public: | ||
| const i_t worker_id; | ||
| omp_atomic_t<bnb_worker_type_t> worker_type; | ||
| omp_atomic_t<bool> is_active; | ||
| omp_atomic_t<f_t> lower_bound; | ||
|
|
||
| lp_problem_t<i_t, f_t> leaf_problem; | ||
| lp_solution_t<i_t, f_t> leaf_solution; | ||
|
|
||
| basis_update_mpf_t<i_t, f_t> basis_factors; | ||
| std::vector<i_t> basic_list; | ||
| std::vector<i_t> nonbasic_list; | ||
|
|
||
| bounds_strengthening_t<i_t, f_t> node_presolver; | ||
| std::vector<bool> bounds_changed; | ||
|
|
||
| std::vector<f_t> start_lower; | ||
| std::vector<f_t> start_upper; | ||
| mip_node_t<i_t, f_t>* start_node; | ||
|
|
||
| bool recompute_basis = true; | ||
| bool recompute_bounds = true; | ||
|
|
||
| bnb_worker_data_t(i_t worker_id, | ||
| const lp_problem_t<i_t, f_t>& original_lp, | ||
| const csr_matrix_t<i_t, f_t>& Arow, | ||
| const std::vector<variable_type_t>& var_type, | ||
| const simplex_solver_settings_t<i_t, f_t>& settings) | ||
| : worker_id(worker_id), | ||
| worker_type(EXPLORATION), | ||
| is_active(false), | ||
| lower_bound(-std::numeric_limits<f_t>::infinity()), | ||
| leaf_problem(original_lp), | ||
| leaf_solution(original_lp.num_rows, original_lp.num_cols), | ||
| basis_factors(original_lp.num_rows, settings.refactor_frequency), | ||
| basic_list(original_lp.num_rows), | ||
| nonbasic_list(), | ||
| node_presolver(leaf_problem, Arow, {}, var_type), | ||
| bounds_changed(original_lp.num_cols, false) | ||
| { | ||
| } | ||
|
|
||
| // Set the `start_node` for best-first search. | ||
| void init_best_first(mip_node_t<i_t, f_t>* node, const lp_problem_t<i_t, f_t>& original_lp) | ||
| { | ||
| start_node = node; | ||
| start_lower = original_lp.lower; | ||
| start_upper = original_lp.upper; | ||
| worker_type = EXPLORATION; | ||
| lower_bound = node->lower_bound; | ||
| is_active = true; | ||
| } | ||
|
|
||
| // Initialize the worker for diving, setting the `start_node`, `start_lower` and | ||
| // `start_upper`. Returns `true` if the starting node is feasible via | ||
| // bounds propagation. | ||
| bool init_diving(mip_node_t<i_t, f_t>* node, | ||
| bnb_worker_type_t type, | ||
| const lp_problem_t<i_t, f_t>& original_lp, | ||
| const simplex_solver_settings_t<i_t, f_t>& settings) | ||
| { | ||
| internal_node = node->detach_copy(); | ||
| start_node = &internal_node; | ||
|
|
||
| start_lower = original_lp.lower; | ||
| start_upper = original_lp.upper; | ||
| worker_type = type; | ||
| lower_bound = node->lower_bound; | ||
| is_active = true; | ||
|
|
||
| std::fill(bounds_changed.begin(), bounds_changed.end(), false); | ||
| node->get_variable_bounds(start_lower, start_upper, bounds_changed); | ||
|
|
||
| return node_presolver.bounds_strengthening(start_lower, start_upper, bounds_changed, settings); | ||
| } | ||
|
|
||
| // Set the variables bounds for the LP relaxation of the current node. | ||
| bool set_lp_variable_bounds_for(mip_node_t<i_t, f_t>* node_ptr, | ||
| const simplex_solver_settings_t<i_t, f_t>& settings) | ||
| { | ||
| // Reset the bound_changed markers | ||
| std::fill(bounds_changed.begin(), bounds_changed.end(), false); | ||
|
|
||
| // Set the correct bounds for the leaf problem | ||
| if (recompute_bounds) { | ||
| leaf_problem.lower = start_lower; | ||
| leaf_problem.upper = start_upper; | ||
| node_ptr->get_variable_bounds(leaf_problem.lower, leaf_problem.upper, bounds_changed); | ||
|
|
||
| } else { | ||
| node_ptr->update_branched_variable_bounds( | ||
| leaf_problem.lower, leaf_problem.upper, bounds_changed); | ||
| } | ||
|
|
||
| return node_presolver.bounds_strengthening( | ||
| leaf_problem.lower, leaf_problem.upper, bounds_changed, settings); | ||
| } | ||
|
|
||
| private: | ||
| // For diving, we need to store the full node instead of | ||
| // of just a pointer, since it is not store in the tree anymore. | ||
| // To keep the same interface across all worker types, | ||
| // this will be used as a temporary storage and | ||
| // will be pointed by `start_node`. | ||
| // For exploration, this will not be used. | ||
| mip_node_t<i_t, f_t> internal_node; | ||
| }; | ||
|
|
||
| template <typename i_t, typename f_t> | ||
| class bnb_worker_pool_t { | ||
| public: | ||
| void init(i_t num_workers, | ||
| const lp_problem_t<i_t, f_t>& original_lp, | ||
| const csr_matrix_t<i_t, f_t>& Arow, | ||
| const std::vector<variable_type_t>& var_type, | ||
| const simplex_solver_settings_t<i_t, f_t>& settings) | ||
| { | ||
| workers_.resize(num_workers); | ||
| num_idle_workers_ = num_workers; | ||
| for (i_t i = 0; i < num_workers; ++i) { | ||
| workers_[i] = | ||
| std::make_unique<bnb_worker_data_t<i_t, f_t>>(i, original_lp, Arow, var_type, settings); | ||
| idle_workers_.push_front(i); | ||
| } | ||
| } | ||
|
|
||
| bnb_worker_data_t<i_t, f_t>* get_idle_worker() | ||
| { | ||
| std::lock_guard<omp_mutex_t> lock(mutex_); | ||
|
|
||
| if (idle_workers_.empty()) { | ||
| return nullptr; | ||
| } else { | ||
| i_t idx = idle_workers_.front(); | ||
| return workers_[idx].get(); | ||
| } | ||
| } | ||
|
|
||
| void pop_idle_worker() | ||
| { | ||
| std::lock_guard<omp_mutex_t> lock(mutex_); | ||
| if (!idle_workers_.empty()) { | ||
| idle_workers_.pop_front(); | ||
| num_idle_workers_--; | ||
| } | ||
| } | ||
|
|
||
| bnb_worker_data_t<i_t, f_t>* get_and_pop_idle_worker() | ||
| { | ||
| std::lock_guard<omp_mutex_t> lock(mutex_); | ||
|
|
||
| if (idle_workers_.empty()) { | ||
| return nullptr; | ||
| } else { | ||
| i_t idx = idle_workers_.front(); | ||
| idle_workers_.pop_front(); | ||
| num_idle_workers_--; | ||
| return workers_[idx].get(); | ||
| } | ||
| } | ||
|
|
||
| void return_worker_to_pool(bnb_worker_data_t<i_t, f_t>* worker) | ||
| { | ||
| worker->is_active = false; | ||
| std::lock_guard<omp_mutex_t> lock(mutex_); | ||
| idle_workers_.push_back(worker->worker_id); | ||
| num_idle_workers_++; | ||
| } | ||
|
|
||
| f_t get_lower_bounds() | ||
| { | ||
| f_t lower_bound = std::numeric_limits<f_t>::infinity(); | ||
|
|
||
| for (i_t i = 0; i < workers_.size(); ++i) { | ||
| if (workers_[i]->worker_type == EXPLORATION && workers_[i]->is_active) { | ||
| lower_bound = std::min(workers_[i]->lower_bound.load(), lower_bound); | ||
| } | ||
| } | ||
|
|
||
| return lower_bound; | ||
| } | ||
|
|
||
| i_t num_idle_workers() { return num_idle_workers_; } | ||
|
|
||
| private: | ||
| // Worker pool | ||
| std::vector<std::unique_ptr<bnb_worker_data_t<i_t, f_t>>> workers_; | ||
|
|
||
| omp_mutex_t mutex_; | ||
| std::deque<i_t> idle_workers_; | ||
| omp_atomic_t<i_t> num_idle_workers_; | ||
| }; | ||
|
|
||
| template <typename f_t, typename i_t> | ||
| std::vector<bnb_worker_type_t> bnb_get_worker_types(diving_heuristics_settings_t<i_t, f_t> settings) | ||
| { | ||
| std::vector<bnb_worker_type_t> types; | ||
| types.reserve(bnb_num_worker_types); | ||
| types.push_back(EXPLORATION); | ||
| if (!settings.disable_pseudocost_diving) { types.push_back(PSEUDOCOST_DIVING); } | ||
| if (!settings.disable_line_search_diving) { types.push_back(LINE_SEARCH_DIVING); } | ||
| if (!settings.disable_guided_diving) { types.push_back(GUIDED_DIVING); } | ||
| if (!settings.disable_coefficient_diving) { types.push_back(COEFFICIENT_DIVING); } | ||
| return types; | ||
| } | ||
|
|
||
| template <typename f_t, typename i_t> | ||
| std::array<i_t, bnb_num_worker_types> bnb_get_num_workers_round_robin( | ||
| i_t num_threads, diving_heuristics_settings_t<i_t, f_t> settings) | ||
| { | ||
| std::array<i_t, bnb_num_worker_types> max_num_workers; | ||
| auto worker_types = bnb_get_worker_types(settings); | ||
|
|
||
| max_num_workers.fill(0); | ||
| max_num_workers[EXPLORATION] = std::max(1, num_threads / 2); | ||
|
|
||
| i_t diving_workers = 2 * settings.num_diving_workers; | ||
| i_t m = worker_types.size() - 1; | ||
|
|
||
| for (size_t i = 1, k = 0; i < worker_types.size(); ++i) { | ||
| i_t start = (double)k * diving_workers / m; | ||
| i_t end = (double)(k + 1) * diving_workers / m; | ||
| max_num_workers[worker_types[i]] = end - start; | ||
| ++k; | ||
| } | ||
|
|
||
| return max_num_workers; | ||
| } | ||
|
|
||
| } // namespace cuopt::linear_programming::dual_simplex | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.