@@ -244,25 +244,15 @@ f_t branch_and_bound_t<i_t, f_t>::get_upper_bound()
244244template <typename i_t , typename f_t >
245245f_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
268258template <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
888887template <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