diff --git a/cpp/src/branch_and_bound/branch_and_bound.cpp b/cpp/src/branch_and_bound/branch_and_bound.cpp index 29174148b7..31ad66abe5 100644 --- a/cpp/src/branch_and_bound/branch_and_bound.cpp +++ b/cpp/src/branch_and_bound/branch_and_bound.cpp @@ -1124,7 +1124,7 @@ struct nondeterministic_policy_t : tree_update_policy_t { void on_numerical_issue(mip_node_t* node) override { if (worker->search_strategy == search_strategy_t::BEST_FIRST) { - fetch_min(bnb.lower_bound_numerical_, node->lower_bound); + bnb.lower_bound_numerical_.fetch_min(node->lower_bound); log.printf("LP returned numerical issue on node %d. Best bound set to %+10.6e.\n", node->node_id, compute_user_objective(bnb.original_lp_, bnb.lower_bound_numerical_.load())); @@ -1722,6 +1722,7 @@ void branch_and_bound_t::plunge_with(bfs_worker_t* worker, // relaxation // - The lower bound of the parent is lower or equal to its children worker->lower_bound = node_ptr->lower_bound; + exploration_stats_.max_node_depth.fetch_max(node_ptr->depth); if (node_ptr->lower_bound > upper_bound_.load()) { search_tree_.graphviz_node(settings_.log, node_ptr, "cutoff", node_ptr->lower_bound); @@ -1772,6 +1773,19 @@ void branch_and_bound_t::plunge_with(bfs_worker_t* worker, break; } + i_t max_node_depth = exploration_stats_.max_node_depth; + i_t plunge_depth = node_ptr->depth - start_node->depth; + + if (plunge_depth >= settings_.bnb_min_plunge_depth * max_node_depth) { + f_t max_bound = lower_bound + settings_.bnb_plunge_gap_factor * (upper_bound - lower_bound); + if (node_ptr->lower_bound >= max_bound || + plunge_depth >= settings_.bnb_max_plunge_depth * max_node_depth) { + stack.push_front(node_ptr); + --exploration_stats_.nodes_being_solved; + break; + } + } + decompress_vstatus( node_ptr->packed_vstatus, worker->leaf_problem.num_cols, worker->leaf_vstatus); assert(worker->leaf_vstatus.size() == worker->leaf_problem.num_cols); @@ -3457,6 +3471,7 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut root_lp_current_lower_bound_ = -inf; exploration_stats_.nodes_unexplored = 0; exploration_stats_.nodes_explored = 0; + exploration_stats_.max_node_depth = 0; original_lp_.A.to_compressed_row(Arow_); settings_.log.debug("Reduced cost strengthening enabled: %d\n", @@ -3887,6 +3902,7 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut exploration_stats_.nodes_explored = 0; exploration_stats_.nodes_unexplored = 2; + exploration_stats_.max_node_depth = 0; exploration_stats_.nodes_since_last_log = 0; exploration_stats_.last_log = tic(); min_node_queue_size_ = 20; @@ -4803,7 +4819,7 @@ void branch_and_bound_t::deterministic_sort_replay_events( deterministic_merge_pseudo_cost_updates(*deterministic_workers_); for (const auto& worker : *deterministic_workers_) { - fetch_min(lower_bound_numerical_, worker.local_lower_bound_ceiling); + lower_bound_numerical_.fetch_min(worker.local_lower_bound_ceiling); } } diff --git a/cpp/src/branch_and_bound/worker.hpp b/cpp/src/branch_and_bound/worker.hpp index bbc2836d43..7031a30178 100644 --- a/cpp/src/branch_and_bound/worker.hpp +++ b/cpp/src/branch_and_bound/worker.hpp @@ -30,6 +30,7 @@ struct branch_and_bound_stats_t { omp_atomic_t nodes_unexplored = 0; // Tracks the number of nodes being solved by the workers at a given time omp_atomic_t nodes_being_solved = 0; + omp_atomic_t max_node_depth = 0; omp_atomic_t total_simplex_iters = 0; omp_atomic_t nodes_since_last_log = 0; diff --git a/cpp/src/dual_simplex/simplex_solver_settings.hpp b/cpp/src/dual_simplex/simplex_solver_settings.hpp index 1247bcb460..1cad48d685 100644 --- a/cpp/src/dual_simplex/simplex_solver_settings.hpp +++ b/cpp/src/dual_simplex/simplex_solver_settings.hpp @@ -108,6 +108,9 @@ struct simplex_solver_settings_t { bnb_steal_chance(-1), bnb_nodes_per_steal(-1), bnb_max_steal_attempts(-1), + bnb_plunge_gap_factor(0.25), + bnb_min_plunge_depth(0.1), + bnb_max_plunge_depth(0.5), reliability_branching(-1), inside_mip(0), inside_submip(0), @@ -222,6 +225,9 @@ struct simplex_solver_settings_t { f_t bnb_steal_chance; i_t bnb_nodes_per_steal; i_t bnb_max_steal_attempts; + f_t bnb_plunge_gap_factor; + f_t bnb_min_plunge_depth; + f_t bnb_max_plunge_depth; // Settings for the reliability branching. // - -1: automatic diff --git a/cpp/src/utilities/omp_helpers.hpp b/cpp/src/utilities/omp_helpers.hpp index 3fd31d169e..06ebf73a24 100644 --- a/cpp/src/utilities/omp_helpers.hpp +++ b/cpp/src/utilities/omp_helpers.hpp @@ -214,38 +214,31 @@ class omp_atomic_t { T& underlying() { return val; } T underlying() const { return val; } - private: - T val; - - friend double fetch_min(omp_atomic_t& atomic_var, double other); - friend double fetch_max(omp_atomic_t& atomic_var, double other); -}; - -// Free non-template functions are necessary because of a clang 20 bug -// when omp atomic compare is used within a templated context. -// see https://github.com/llvm/llvm-project/issues/127466 -inline double fetch_min(omp_atomic_t& atomic_var, double other) -{ - double old; -#pragma omp atomic compare capture + T fetch_min(T other) { - old = atomic_var.val; - if (other < atomic_var.val) { atomic_var.val = other; } + T old; +#pragma omp atomic compare capture + { + old = val; + if (other < val) { val = other; } + } + return old; } - return old; -} -inline double fetch_max(omp_atomic_t& atomic_var, double other) -{ - double old; -#pragma omp atomic compare capture + T fetch_max(T other) { - old = atomic_var.val; - if (other > atomic_var.val) { atomic_var.val = other; } + T old; +#pragma omp atomic compare capture + { + old = val; + if (other > val) { val = other; } + } + return old; } - return old; -} + private: + T val; +}; } // namespace cuopt #endif