Skip to content

Commit 546b116

Browse files
committed
unified the best-first and diving heap into a single object. imposed iteration and node limit to the diving threads.
1 parent 89ebcbb commit 546b116

File tree

7 files changed

+337
-257
lines changed

7 files changed

+337
-257
lines changed

cpp/src/dual_simplex/branch_and_bound.cpp

Lines changed: 90 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -244,25 +244,15 @@ f_t branch_and_bound_t<i_t, f_t>::get_upper_bound()
244244
template <typename i_t, typename f_t>
245245
f_t branch_and_bound_t<i_t, f_t>::get_lower_bound()
246246
{
247-
f_t lower_bound = lower_bound_ceiling_.load();
248-
mutex_heap_.lock();
249-
if (heap_.size() > 0) { lower_bound = std::min(heap_.top()->lower_bound, lower_bound); }
250-
mutex_heap_.unlock();
247+
f_t lower_bound = lower_bound_ceiling_.load();
248+
f_t heap_lower_bound = node_queue.get_lower_bound();
249+
lower_bound = std::min(heap_lower_bound, lower_bound);
251250

252251
for (i_t i = 0; i < local_lower_bounds_.size(); ++i) {
253252
lower_bound = std::min(local_lower_bounds_[i].load(), lower_bound);
254253
}
255254

256-
return lower_bound;
257-
}
258-
259-
template <typename i_t, typename f_t>
260-
i_t branch_and_bound_t<i_t, f_t>::get_heap_size()
261-
{
262-
mutex_heap_.lock();
263-
i_t size = heap_.size();
264-
mutex_heap_.unlock();
265-
return size;
255+
return std::isfinite(lower_bound) ? lower_bound : -inf;
266256
}
267257

268258
template <typename i_t, typename f_t>
@@ -589,6 +579,7 @@ node_solve_info_t branch_and_bound_t<i_t, f_t>::solve_node(
589579
bool recompute_bounds_and_basis,
590580
const std::vector<f_t>& root_lower,
591581
const std::vector<f_t>& root_upper,
582+
bnb_stats_t<i_t, f_t>& stats,
592583
logger_t& log)
593584
{
594585
const f_t abs_fathom_tol = settings_.absolute_mip_gap_tol / 10;
@@ -605,6 +596,13 @@ node_solve_info_t branch_and_bound_t<i_t, f_t>::solve_node(
605596
lp_settings.time_limit = settings_.time_limit - toc(exploration_stats_.start_time);
606597
lp_settings.scale_columns = false;
607598

599+
if (thread_type != thread_type_t::EXPLORATION) {
600+
i_t bnb_lp_iters = exploration_stats_.total_lp_iters;
601+
f_t max_iter = 0.05 * bnb_lp_iters;
602+
lp_settings.iteration_limit = max_iter - stats.total_lp_iters;
603+
if (lp_settings.iteration_limit <= 0) { return node_solve_info_t::ITERATION_LIMIT; }
604+
}
605+
608606
#ifdef LOG_NODE_SIMPLEX
609607
lp_settings.set_log(true);
610608
std::stringstream ss;
@@ -679,10 +677,8 @@ node_solve_info_t branch_and_bound_t<i_t, f_t>::solve_node(
679677
lp_status = convert_lp_status_to_dual_status(second_status);
680678
}
681679

682-
if (thread_type == thread_type_t::EXPLORATION) {
683-
exploration_stats_.total_lp_solve_time += toc(lp_start_time);
684-
exploration_stats_.total_lp_iters += node_iter;
685-
}
680+
stats.total_lp_solve_time += toc(lp_start_time);
681+
stats.total_lp_iters += node_iter;
686682
}
687683

688684
if (lp_status == dual::status_t::DUAL_UNBOUNDED) {
@@ -726,8 +722,9 @@ node_solve_info_t branch_and_bound_t<i_t, f_t>::solve_node(
726722

727723
} else if (leaf_objective <= upper_bound + abs_fathom_tol) {
728724
// Choose fractional variable to branch on
729-
const i_t branch_var =
730-
pc_.variable_selection(leaf_fractional, leaf_solution.x, lp_settings.log);
725+
auto [branch_var, obj_estimate] = pc_.variable_selection_and_obj_estimate(
726+
leaf_fractional, leaf_solution.x, node_ptr->lower_bound, log);
727+
node_ptr->objective_estimate = obj_estimate;
731728

732729
assert(leaf_vstatus.size() == leaf_problem.num_cols);
733730
search_tree.branch(
@@ -751,6 +748,9 @@ node_solve_info_t branch_and_bound_t<i_t, f_t>::solve_node(
751748
search_tree.graphviz_node(log, node_ptr, "timeout", 0.0);
752749
return node_solve_info_t::TIME_LIMIT;
753750

751+
} else if (lp_status == dual::status_t::ITERATION_LIMIT) {
752+
return node_solve_info_t::ITERATION_LIMIT;
753+
754754
} else {
755755
if (thread_type == thread_type_t::EXPLORATION) {
756756
fetch_min(lower_bound_ceiling_, node_ptr->lower_bound);
@@ -854,6 +854,7 @@ void branch_and_bound_t<i_t, f_t>::exploration_ramp_up(mip_node_t<i_t, f_t>* nod
854854
true,
855855
original_lp_.lower,
856856
original_lp_.upper,
857+
exploration_stats_,
857858
settings_.log);
858859

859860
++exploration_stats_.nodes_since_last_log;
@@ -877,23 +878,21 @@ void branch_and_bound_t<i_t, f_t>::exploration_ramp_up(mip_node_t<i_t, f_t>* nod
877878

878879
} else {
879880
// We've generated enough nodes, push further nodes onto the heap
880-
mutex_heap_.lock();
881-
heap_.push(node->get_down_child());
882-
heap_.push(node->get_up_child());
883-
mutex_heap_.unlock();
881+
node_queue.push(node->get_down_child());
882+
node_queue.push(node->get_up_child());
884883
}
885884
}
886885
}
887886

888887
template <typename i_t, typename f_t>
889-
void branch_and_bound_t<i_t, f_t>::explore_subtree(i_t task_id,
890-
mip_node_t<i_t, f_t>* start_node,
891-
search_tree_t<i_t, f_t>& search_tree,
892-
lp_problem_t<i_t, f_t>& leaf_problem,
893-
bounds_strengthening_t<i_t, f_t>& node_presolver,
894-
basis_update_mpf_t<i_t, f_t>& basis_factors,
895-
std::vector<i_t>& basic_list,
896-
std::vector<i_t>& nonbasic_list)
888+
void branch_and_bound_t<i_t, f_t>::plunge_from(i_t task_id,
889+
mip_node_t<i_t, f_t>* start_node,
890+
search_tree_t<i_t, f_t>& search_tree,
891+
lp_problem_t<i_t, f_t>& leaf_problem,
892+
bounds_strengthening_t<i_t, f_t>& node_presolver,
893+
basis_update_mpf_t<i_t, f_t>& basis_factors,
894+
std::vector<i_t>& basic_list,
895+
std::vector<i_t>& nonbasic_list)
897896
{
898897
bool recompute_bounds_and_basis = true;
899898
std::deque<mip_node_t<i_t, f_t>*> stack;
@@ -922,6 +921,7 @@ void branch_and_bound_t<i_t, f_t>::explore_subtree(i_t task_id,
922921
if (lower_bound > upper_bound || rel_gap < settings_.relative_mip_gap_tol) {
923922
search_tree.graphviz_node(settings_.log, node_ptr, "cutoff", node_ptr->lower_bound);
924923
search_tree.update(node_ptr, node_status_t::FATHOMED);
924+
recompute_bounds_and_basis = true;
925925
--exploration_stats_.nodes_unexplored;
926926
continue;
927927
}
@@ -977,6 +977,7 @@ void branch_and_bound_t<i_t, f_t>::explore_subtree(i_t task_id,
977977
recompute_bounds_and_basis,
978978
original_lp_.lower,
979979
original_lp_.upper,
980+
exploration_stats_,
980981
settings_.log);
981982

982983
recompute_bounds_and_basis = !has_children(status);
@@ -997,28 +998,7 @@ void branch_and_bound_t<i_t, f_t>::explore_subtree(i_t task_id,
997998
if (stack.size() > 0) {
998999
mip_node_t<i_t, f_t>* node = stack.back();
9991000
stack.pop_back();
1000-
1001-
// The order here matters. We want to create a copy of the node
1002-
// before adding to the global heap. Otherwise,
1003-
// some thread may consume the node (possibly fathoming it)
1004-
// before we had the chance to add to the diving queue.
1005-
// This lead to a SIGSEGV. Although, in this case, it
1006-
// would be better if we discard the node instead.
1007-
if (get_heap_size() > settings_.num_bfs_threads) {
1008-
std::vector<f_t> lower = original_lp_.lower;
1009-
std::vector<f_t> upper = original_lp_.upper;
1010-
std::fill(
1011-
node_presolver.bounds_changed.begin(), node_presolver.bounds_changed.end(), false);
1012-
node->get_variable_bounds(lower, upper, node_presolver.bounds_changed);
1013-
1014-
mutex_dive_queue_.lock();
1015-
diving_queue_.emplace(node->detach_copy(), std::move(lower), std::move(upper));
1016-
mutex_dive_queue_.unlock();
1017-
}
1018-
1019-
mutex_heap_.lock();
1020-
heap_.push(node);
1021-
mutex_heap_.unlock();
1001+
node_queue.push(node);
10221002
}
10231003

10241004
exploration_stats_.nodes_unexplored += 2;
@@ -1056,37 +1036,31 @@ void branch_and_bound_t<i_t, f_t>::best_first_thread(i_t task_id,
10561036

10571037
while (solver_status_ == mip_exploration_status_t::RUNNING &&
10581038
abs_gap > settings_.absolute_mip_gap_tol && rel_gap > settings_.relative_mip_gap_tol &&
1059-
(active_subtrees_ > 0 || get_heap_size() > 0)) {
1060-
mip_node_t<i_t, f_t>* start_node = nullptr;
1061-
1039+
(active_subtrees_ > 0 || node_queue.best_first_queue_size() > 0)) {
10621040
// If there any node left in the heap, we pop the top node and explore it.
1063-
mutex_heap_.lock();
1064-
if (heap_.size() > 0) {
1065-
start_node = heap_.top();
1066-
heap_.pop();
1067-
active_subtrees_++;
1068-
}
1069-
mutex_heap_.unlock();
1041+
std::optional<mip_node_t<i_t, f_t>*> start_node = node_queue.pop_best_first(active_subtrees_);
1042+
1043+
if (start_node.has_value()) {
1044+
mip_node_t<i_t, f_t>* node = start_node.value();
10701045

1071-
if (start_node != nullptr) {
1072-
if (get_upper_bound() < start_node->lower_bound) {
1046+
if (get_upper_bound() < node->lower_bound) {
10731047
// This node was put on the heap earlier but its lower bound is now greater than the
10741048
// current upper bound
1075-
search_tree.graphviz_node(settings_.log, start_node, "cutoff", start_node->lower_bound);
1076-
search_tree.update(start_node, node_status_t::FATHOMED);
1049+
search_tree.graphviz_node(settings_.log, node, "cutoff", node->lower_bound);
1050+
search_tree.update(node, node_status_t::FATHOMED);
10771051
active_subtrees_--;
10781052
continue;
10791053
}
10801054

10811055
// Best-first search with plunging
1082-
explore_subtree(task_id,
1083-
start_node,
1084-
search_tree,
1085-
leaf_problem,
1086-
node_presolver,
1087-
basis_factors,
1088-
basic_list,
1089-
nonbasic_list);
1056+
plunge_from(task_id,
1057+
node,
1058+
search_tree,
1059+
leaf_problem,
1060+
node_presolver,
1061+
basis_factors,
1062+
basic_list,
1063+
nonbasic_list);
10901064

10911065
active_subtrees_--;
10921066
}
@@ -1123,22 +1097,39 @@ void branch_and_bound_t<i_t, f_t>::diving_thread(const csr_matrix_t<i_t, f_t>& A
11231097
std::vector<i_t> basic_list(m);
11241098
std::vector<i_t> nonbasic_list;
11251099

1100+
std::vector<f_t> start_lower;
1101+
std::vector<f_t> start_upper;
1102+
bool reset_starting_bounds = true;
1103+
11261104
while (solver_status_ == mip_exploration_status_t::RUNNING &&
1127-
(active_subtrees_ > 0 || get_heap_size() > 0)) {
1128-
std::optional<diving_root_t<i_t, f_t>> start_node;
1105+
(active_subtrees_ > 0 || node_queue.best_first_queue_size() > 0)) {
1106+
if (reset_starting_bounds) {
1107+
start_lower = original_lp_.lower;
1108+
start_upper = original_lp_.upper;
1109+
std::fill(node_presolver.bounds_changed.begin(), node_presolver.bounds_changed.end(), false);
1110+
reset_starting_bounds = false;
1111+
}
11291112

1130-
mutex_dive_queue_.lock();
1131-
if (diving_queue_.size() > 0) { start_node = diving_queue_.pop(); }
1132-
mutex_dive_queue_.unlock();
1113+
std::optional<mip_node_t<i_t, f_t>> start_node =
1114+
node_queue.pop_diving(start_lower, start_upper, node_presolver.bounds_changed);
11331115

11341116
if (start_node.has_value()) {
1135-
if (get_upper_bound() < start_node->node.lower_bound) { continue; }
1117+
reset_starting_bounds = true;
1118+
1119+
bool is_feasible = node_presolver.bounds_strengthening(start_lower, start_upper, settings_);
1120+
if (get_upper_bound() < start_node->lower_bound || !is_feasible) { continue; }
11361121

11371122
bool recompute_bounds_and_basis = true;
1138-
search_tree_t<i_t, f_t> subtree(std::move(start_node->node));
1123+
search_tree_t<i_t, f_t> subtree(std::move(start_node.value()));
11391124
std::deque<mip_node_t<i_t, f_t>*> stack;
11401125
stack.push_front(&subtree.root);
11411126

1127+
bnb_stats_t<i_t, f_t> dive_stats;
1128+
dive_stats.total_lp_iters = 0;
1129+
dive_stats.total_lp_solve_time = 0;
1130+
dive_stats.nodes_explored = 0;
1131+
dive_stats.nodes_unexplored = 0;
1132+
11421133
while (stack.size() > 0 && solver_status_ == mip_exploration_status_t::RUNNING) {
11431134
mip_node_t<i_t, f_t>* node_ptr = stack.front();
11441135
stack.pop_front();
@@ -1150,7 +1141,8 @@ void branch_and_bound_t<i_t, f_t>::diving_thread(const csr_matrix_t<i_t, f_t>& A
11501141
continue;
11511142
}
11521143

1153-
if (toc(exploration_stats_.start_time) > settings_.time_limit) { return; }
1144+
if (toc(exploration_stats_.start_time) > settings_.time_limit) { break; }
1145+
if (dive_stats.nodes_explored > 500) { break; }
11541146

11551147
node_solve_info_t status = solve_node(node_ptr,
11561148
subtree,
@@ -1161,15 +1153,19 @@ void branch_and_bound_t<i_t, f_t>::diving_thread(const csr_matrix_t<i_t, f_t>& A
11611153
node_presolver,
11621154
thread_type_t::DIVING,
11631155
recompute_bounds_and_basis,
1164-
start_node->lower,
1165-
start_node->upper,
1156+
start_lower,
1157+
start_upper,
1158+
dive_stats,
11661159
log);
1167-
1160+
dive_stats.nodes_explored++;
11681161
recompute_bounds_and_basis = !has_children(status);
11691162

11701163
if (status == node_solve_info_t::TIME_LIMIT) {
11711164
solver_status_ = mip_exploration_status_t::TIME_LIMIT;
1172-
return;
1165+
break;
1166+
1167+
} else if (status == node_solve_info_t::ITERATION_LIMIT) {
1168+
break;
11731169

11741170
} else if (has_children(status)) {
11751171
if (status == node_solve_info_t::UP_CHILD_FIRST) {
@@ -1181,24 +1177,8 @@ void branch_and_bound_t<i_t, f_t>::diving_thread(const csr_matrix_t<i_t, f_t>& A
11811177
}
11821178
}
11831179

1184-
if (stack.size() > 1) {
1185-
// If the diving thread is consuming the nodes faster than the
1186-
// best first search, then we split the current subtree at the
1187-
// lowest possible point and move to the queue, so it can
1188-
// be picked by another thread.
1189-
if (std::lock_guard<omp_mutex_t> lock(mutex_dive_queue_);
1190-
diving_queue_.size() < min_diving_queue_size_) {
1191-
mip_node_t<i_t, f_t>* new_node = stack.back();
1192-
stack.pop_back();
1193-
1194-
std::vector<f_t> lower = start_node->lower;
1195-
std::vector<f_t> upper = start_node->upper;
1196-
std::fill(
1197-
node_presolver.bounds_changed.begin(), node_presolver.bounds_changed.end(), false);
1198-
new_node->get_variable_bounds(lower, upper, node_presolver.bounds_changed);
1199-
1200-
diving_queue_.emplace(new_node->detach_copy(), std::move(lower), std::move(upper));
1201-
}
1180+
if (stack.size() > 1 && stack.front()->depth - stack.back()->depth > 5) {
1181+
stack.pop_back();
12021182
}
12031183
}
12041184
}
@@ -1421,7 +1401,8 @@ mip_status_t branch_and_bound_t<i_t, f_t>::solve(mip_solution_t<i_t, f_t>& solut
14211401
}
14221402

14231403
// Choose variable to branch on
1424-
i_t branch_var = pc_.variable_selection(fractional, root_relax_soln_.x, log);
1404+
auto [branch_var, obj_estimate] =
1405+
pc_.variable_selection_and_obj_estimate(fractional, root_relax_soln_.x, root_objective_, log);
14251406

14261407
search_tree_.root = std::move(mip_node_t<i_t, f_t>(root_objective_, root_vstatus_));
14271408
search_tree_.num_nodes = 0;
@@ -1450,7 +1431,6 @@ mip_status_t branch_and_bound_t<i_t, f_t>::solve(mip_solution_t<i_t, f_t>& solut
14501431
exploration_stats_.nodes_since_last_log = 0;
14511432
exploration_stats_.last_log = tic();
14521433
active_subtrees_ = 0;
1453-
min_diving_queue_size_ = 4 * settings_.num_diving_threads;
14541434
solver_status_ = mip_exploration_status_t::RUNNING;
14551435
lower_bound_ceiling_ = inf;
14561436
should_report_ = true;
@@ -1486,7 +1466,8 @@ mip_status_t branch_and_bound_t<i_t, f_t>::solve(mip_solution_t<i_t, f_t>& solut
14861466
}
14871467
}
14881468

1489-
f_t lower_bound = heap_.size() > 0 ? heap_.top()->lower_bound : search_tree_.root.lower_bound;
1469+
f_t lower_bound = node_queue.best_first_queue_size() > 0 ? node_queue.get_lower_bound()
1470+
: search_tree_.root.lower_bound;
14901471
return set_final_solution(solution, lower_bound);
14911472
}
14921473

0 commit comments

Comments
 (0)