Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion conda/environments/all_cuda-129_arch-aarch64.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ dependencies:
- libcurand-dev
- libcusolver-dev
- libcusparse-dev
- libgrpc
- libgrpc >=1.78.0,<1.80.0a0
- libprotobuf
- libraft-headers==26.4.*,>=0.0.0a0
- librmm==26.4.*,>=0.0.0a0
Expand Down
2 changes: 1 addition & 1 deletion conda/environments/all_cuda-129_arch-x86_64.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ dependencies:
- libcurand-dev
- libcusolver-dev
- libcusparse-dev
- libgrpc
- libgrpc >=1.78.0,<1.80.0a0
- libprotobuf
- libraft-headers==26.4.*,>=0.0.0a0
- librmm==26.4.*,>=0.0.0a0
Expand Down
2 changes: 1 addition & 1 deletion conda/environments/all_cuda-131_arch-aarch64.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ dependencies:
- libcurand-dev
- libcusolver-dev
- libcusparse-dev
- libgrpc
- libgrpc >=1.78.0,<1.80.0a0
- libprotobuf
- libraft-headers==26.4.*,>=0.0.0a0
- librmm==26.4.*,>=0.0.0a0
Expand Down
2 changes: 1 addition & 1 deletion conda/environments/all_cuda-131_arch-x86_64.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ dependencies:
- libcurand-dev
- libcusolver-dev
- libcusparse-dev
- libgrpc
- libgrpc >=1.78.0,<1.80.0a0
- libprotobuf
- libraft-headers==26.4.*,>=0.0.0a0
- librmm==26.4.*,>=0.0.0a0
Expand Down
22 changes: 16 additions & 6 deletions conda/recipes/libcuopt/recipe.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ cache:
- bzip2
- openssl
- c-ares
- libgrpc
- libgrpc >=1.78.0,<1.80.0a0
- libprotobuf
- libabseil
- re2
Expand Down Expand Up @@ -127,14 +127,22 @@ outputs:
- bzip2
ignore_run_exports:
by_name:
- c-ares
- cuda-nvtx
- cuda-version
- libcurand
- libabseil
- libboost
- libcudss
- libcurand
- libcusparse
- libgrpc
- libprotobuf
- librmm
- libzlib
- libbz2
- libzlib
- openssl
- re2
- tbb
tests:
- package_contents:
files:
Expand Down Expand Up @@ -174,10 +182,11 @@ outputs:
- openssl
- c-ares
- libuuid
- libgrpc
- libgrpc >=1.78.0,<1.80.0a0
- libprotobuf
- libabseil
- re2
- tbb-devel
run:
- ${{ pin_compatible("cuda-version", upper_bound="x", lower_bound="x") }}
- ${{ pin_subpackage("libmps-parser", exact=True) }}
Expand All @@ -188,9 +197,10 @@ outputs:
- openssl
- c-ares
- libuuid
- libgrpc
- libgrpc >=1.78.0,<1.80.0a0
- libprotobuf
- libabseil
- tbb
ignore_run_exports:
by_name:
- cuda-nvtx
Expand Down Expand Up @@ -235,7 +245,7 @@ outputs:
- libcusparse-dev
- openssl
- c-ares
- libgrpc
- libgrpc >=1.78.0,<1.80.0a0
- libprotobuf
- libabseil
run:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ class mip_solution_t : public base_solution_t {
const std::vector<std::string>& get_variable_names() const;
const std::vector<rmm::device_uvector<f_t>>& get_solution_pool() const;
void write_to_sol_file(std::string_view filename, rmm::cuda_stream_view stream_view) const;
void log_detailed_summary() const;
void log_summary() const;

private:
Expand Down
78 changes: 41 additions & 37 deletions cpp/src/branch_and_bound/branch_and_bound.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,12 @@ f_t branch_and_bound_t<i_t, f_t>::get_lower_bound()
}
}

template <typename i_t, typename f_t>
void branch_and_bound_t<i_t, f_t>::set_initial_upper_bound(f_t bound)
{
upper_bound_ = bound;
}

template <typename i_t, typename f_t>
void branch_and_bound_t<i_t, f_t>::report_heuristic(f_t obj)
{
Expand Down Expand Up @@ -469,10 +475,7 @@ void branch_and_bound_t<i_t, f_t>::set_new_solution(const std::vector<f_t>& solu
mutex_original_lp_.unlock();
bool is_feasible = false;
bool attempt_repair = false;
mutex_upper_.lock();
f_t current_upper_bound = upper_bound_;
mutex_upper_.unlock();
if (obj < current_upper_bound) {
if (!incumbent_.has_incumbent || obj < incumbent_.objective) {
f_t primal_err;
f_t bound_err;
i_t num_fractional;
Expand All @@ -487,8 +490,8 @@ void branch_and_bound_t<i_t, f_t>::set_new_solution(const std::vector<f_t>& solu
original_lp_, settings_, var_types_, crushed_solution, primal_err, bound_err, num_fractional);
mutex_original_lp_.unlock();
mutex_upper_.lock();
if (is_feasible && obj < upper_bound_) {
upper_bound_ = obj;
if (is_feasible && improves_incumbent(obj)) {
upper_bound_ = std::min(upper_bound_.load(), obj);
incumbent_.set_incumbent_solution(obj, crushed_solution);
} else {
attempt_repair = true;
Expand Down Expand Up @@ -648,8 +651,8 @@ void branch_and_bound_t<i_t, f_t>::repair_heuristic_solutions()
if (is_feasible) {
mutex_upper_.lock();

if (repaired_obj < upper_bound_) {
upper_bound_ = repaired_obj;
if (improves_incumbent(repaired_obj)) {
upper_bound_ = std::min(upper_bound_.load(), repaired_obj);
incumbent_.set_incumbent_solution(repaired_obj, repaired_solution);
report_heuristic(repaired_obj);

Expand Down Expand Up @@ -735,7 +738,9 @@ void branch_and_bound_t<i_t, f_t>::set_final_solution(mip_solution_t<i_t, f_t>&
if (gap <= settings_.absolute_mip_gap_tol || gap_rel <= settings_.relative_mip_gap_tol) {
solver_status_ = mip_status_t::OPTIMAL;
#ifdef CHECK_CUTS_AGAINST_SAVED_SOLUTION
if (settings_.sub_mip == 0) { write_solution_for_cut_verification(original_lp_, incumbent_.x); }
if (settings_.sub_mip == 0 && has_solver_space_incumbent()) {
write_solution_for_cut_verification(original_lp_, incumbent_.x);
}
#endif
if (gap > 0 && gap <= settings_.absolute_mip_gap_tol) {
settings_.log.printf("Optimal solution found within absolute MIP gap tolerance (%.1e)\n",
Expand All @@ -762,11 +767,10 @@ void branch_and_bound_t<i_t, f_t>::set_final_solution(mip_solution_t<i_t, f_t>&
}
}

if (upper_bound_ != inf) {
assert(incumbent_.has_incumbent);
if (has_solver_space_incumbent()) {
uncrush_primal_solution(original_problem_, original_lp_, incumbent_.x, solution.x);
solution.objective = incumbent_.objective;
}
solution.objective = incumbent_.objective;
solution.lower_bound = lower_bound;
solution.nodes_explored = exploration_stats_.nodes_explored;
solution.simplex_iterations = exploration_stats_.total_lp_iters;
Expand All @@ -785,17 +789,17 @@ void branch_and_bound_t<i_t, f_t>::add_feasible_solution(f_t leaf_objective,
compute_user_objective(original_lp_, leaf_objective));

mutex_upper_.lock();
if (leaf_objective < upper_bound_) {
if (improves_incumbent(leaf_objective)) {
incumbent_.set_incumbent_solution(leaf_objective, leaf_solution);
upper_bound_ = leaf_objective;
upper_bound_ = std::min(upper_bound_.load(), leaf_objective);
report(feasible_solution_symbol(thread_type), leaf_objective, get_lower_bound(), leaf_depth, 0);
send_solution = true;
}

if (send_solution && settings_.solution_callback != nullptr) {
std::vector<f_t> original_x;
uncrush_primal_solution(original_problem_, original_lp_, incumbent_.x, original_x);
settings_.solution_callback(original_x, upper_bound_);
settings_.solution_callback(original_x, leaf_objective);
}
mutex_upper_.unlock();
}
Expand Down Expand Up @@ -921,7 +925,7 @@ struct nondeterministic_policy_t : tree_update_policy_t<i_t, f_t> {
{
}

f_t upper_bound() const override { return bnb.get_cutoff(); }
f_t upper_bound() const override { return bnb.get_upper_bound(); }

void update_pseudo_costs(mip_node_t<i_t, f_t>* node, f_t leaf_obj) override
{
Expand Down Expand Up @@ -1339,7 +1343,7 @@ dual::status_t branch_and_bound_t<i_t, f_t>::solve_node_lp(
simplex_solver_settings_t lp_settings = settings_;
lp_settings.concurrent_halt = &node_concurrent_halt_;
lp_settings.set_log(false);
f_t cutoff = get_cutoff();
f_t cutoff = upper_bound_.load();
if (original_lp_.objective_is_integral) {
lp_settings.cut_off = std::ceil(cutoff - settings_.integer_tol) + settings_.dual_tol;
} else {
Expand Down Expand Up @@ -1452,7 +1456,7 @@ void branch_and_bound_t<i_t, f_t>::plunge_with(branch_and_bound_worker_t<i_t, f_
// - The lower bound of the parent is lower or equal to its children
worker->lower_bound = node_ptr->lower_bound;

if (node_ptr->lower_bound > get_cutoff()) {
if (node_ptr->lower_bound > upper_bound_.load()) {
search_tree_.graphviz_node(settings_.log, node_ptr, "cutoff", node_ptr->lower_bound);
search_tree_.update(node_ptr, node_status_t::FATHOMED);
worker->recompute_basis = true;
Expand Down Expand Up @@ -1590,7 +1594,7 @@ void branch_and_bound_t<i_t, f_t>::dive_with(branch_and_bound_worker_t<i_t, f_t>

worker->lower_bound = node_ptr->lower_bound;

if (node_ptr->lower_bound > get_cutoff()) {
if (node_ptr->lower_bound > upper_bound_.load()) {
worker->recompute_basis = true;
worker->recompute_bounds = true;
continue;
Expand Down Expand Up @@ -1649,7 +1653,7 @@ void branch_and_bound_t<i_t, f_t>::run_scheduler()
diving_heuristics_settings_t<i_t, f_t> diving_settings = settings_.diving_settings;
const i_t num_workers = 2 * settings_.num_threads;

if (!std::isfinite(upper_bound_)) { diving_settings.guided_diving = false; }
if (!has_solver_space_incumbent()) { diving_settings.guided_diving = false; }
std::vector<search_strategy_t> strategies = get_search_strategies(diving_settings);
std::array<i_t, num_search_strategies> max_num_workers_per_type =
get_max_workers(num_workers, strategies);
Expand Down Expand Up @@ -1682,7 +1686,7 @@ void branch_and_bound_t<i_t, f_t>::run_scheduler()
// If the guided diving was disabled previously due to the lack of an incumbent solution,
// re-enable as soon as a new incumbent is found.
if (settings_.diving_settings.guided_diving != diving_settings.guided_diving) {
if (std::isfinite(upper_bound_)) {
if (has_solver_space_incumbent()) {
diving_settings.guided_diving = settings_.diving_settings.guided_diving;
strategies = get_search_strategies(diving_settings);
max_num_workers_per_type = get_max_workers(num_workers, strategies);
Expand Down Expand Up @@ -1733,7 +1737,7 @@ void branch_and_bound_t<i_t, f_t>::run_scheduler()
std::optional<mip_node_t<i_t, f_t>*> start_node = node_queue_.pop_best_first();

if (!start_node.has_value()) { continue; }
if (get_cutoff() < start_node.value()->lower_bound) {
if (upper_bound_.load() < start_node.value()->lower_bound) {
// This node was put on the heap earlier but its lower bound is now greater than the
// current upper bound
search_tree_.graphviz_node(
Expand All @@ -1757,7 +1761,7 @@ void branch_and_bound_t<i_t, f_t>::run_scheduler()
std::optional<mip_node_t<i_t, f_t>*> start_node = node_queue_.pop_diving();

if (!start_node.has_value()) { continue; }
if (get_cutoff() < start_node.value()->lower_bound ||
if (upper_bound_.load() < start_node.value()->lower_bound ||
start_node.value()->depth < diving_settings.min_node_depth) {
continue;
}
Expand Down Expand Up @@ -1831,7 +1835,7 @@ void branch_and_bound_t<i_t, f_t>::single_threaded_solve()
std::optional<mip_node_t<i_t, f_t>*> start_node = node_queue_.pop_best_first();

if (!start_node.has_value()) { continue; }
if (get_cutoff() < start_node.value()->lower_bound) {
if (upper_bound_.load() < start_node.value()->lower_bound) {
// This node was put on the heap earlier but its lower bound is now greater than the
// current upper bound
search_tree_.graphviz_node(
Expand Down Expand Up @@ -2331,12 +2335,12 @@ mip_status_t branch_and_bound_t<i_t, f_t>::solve(mip_solution_t<i_t, f_t>& solut
return mip_status_t::NUMERICAL;
}

if (settings_.reduced_cost_strengthening >= 1 && get_cutoff() < last_upper_bound) {
if (settings_.reduced_cost_strengthening >= 1 && upper_bound_.load() < last_upper_bound) {
mutex_upper_.lock();
last_upper_bound = get_cutoff();
last_upper_bound = upper_bound_.load();
std::vector<f_t> lower_bounds;
std::vector<f_t> upper_bounds;
find_reduced_cost_fixings(get_cutoff(), lower_bounds, upper_bounds);
find_reduced_cost_fixings(upper_bound_.load(), lower_bounds, upper_bounds);
mutex_upper_.unlock();
mutex_original_lp_.lock();
original_lp_.lower = lower_bounds;
Expand Down Expand Up @@ -2468,7 +2472,7 @@ mip_status_t branch_and_bound_t<i_t, f_t>::solve(mip_solution_t<i_t, f_t>& solut
f_t rel_gap = user_relative_gap(original_lp_, upper_bound_.load(), root_objective_);
f_t abs_gap = compute_user_abs_gap(original_lp_, upper_bound_.load(), root_objective_);
if (rel_gap < settings_.relative_mip_gap_tol || abs_gap < settings_.absolute_mip_gap_tol) {
set_solution_at_root(solution, cut_info);
if (num_fractional == 0) { set_solution_at_root(solution, cut_info); }
set_final_solution(solution, root_objective_);
return mip_status_t::OPTIMAL;
}
Expand Down Expand Up @@ -2528,10 +2532,10 @@ mip_status_t branch_and_bound_t<i_t, f_t>::solve(mip_solution_t<i_t, f_t>& solut
return solver_status_;
}

if (settings_.reduced_cost_strengthening >= 2 && get_cutoff() < last_upper_bound) {
if (settings_.reduced_cost_strengthening >= 2 && upper_bound_.load() < last_upper_bound) {
std::vector<f_t> lower_bounds;
std::vector<f_t> upper_bounds;
i_t num_fixed = find_reduced_cost_fixings(get_cutoff(), lower_bounds, upper_bounds);
i_t num_fixed = find_reduced_cost_fixings(upper_bound_.load(), lower_bounds, upper_bounds);
if (num_fixed > 0) {
std::vector<bool> bounds_changed(original_lp_.num_cols, true);
std::vector<char> row_sense;
Expand Down Expand Up @@ -2636,7 +2640,7 @@ mip_status_t branch_and_bound_t<i_t, f_t>::solve(mip_solution_t<i_t, f_t>& solut
std::optional<mip_node_t<i_t, f_t>*> start_node = node_queue_.pop_best_first();

if (!start_node.has_value()) { continue; }
if (get_cutoff() < start_node.value()->lower_bound) {
if (upper_bound_.load() < start_node.value()->lower_bound) {
// This node was put on the heap earlier but its lower bound is now greater than the
// current upper bound
search_tree_.graphviz_node(
Expand Down Expand Up @@ -3279,8 +3283,8 @@ void branch_and_bound_t<i_t, f_t>::deterministic_process_worker_solutions(
deterministic_current_horizon_);

bool improved = false;
if (sol->objective < upper_bound_) {
upper_bound_ = sol->objective;
if (improves_incumbent(sol->objective)) {
upper_bound_ = std::min(upper_bound_.load(), sol->objective);
incumbent_.set_incumbent_solution(sol->objective, sol->solution);
current_upper = sol->objective;
improved = true;
Expand Down Expand Up @@ -3442,8 +3446,8 @@ void branch_and_bound_t<i_t, f_t>::deterministic_sort_replay_events(
// Process heuristic solution at its correct work unit timestamp position
f_t new_upper = std::numeric_limits<f_t>::infinity();

if (hsol.objective < upper_bound_) {
upper_bound_ = hsol.objective;
if (improves_incumbent(hsol.objective)) {
upper_bound_ = std::min(upper_bound_.load(), hsol.objective);
incumbent_.set_incumbent_solution(hsol.objective, hsol.solution);
new_upper = hsol.objective;
}
Expand Down Expand Up @@ -3477,7 +3481,7 @@ void branch_and_bound_t<i_t, f_t>::deterministic_sort_replay_events(
template <typename i_t, typename f_t>
void branch_and_bound_t<i_t, f_t>::deterministic_prune_worker_nodes_vs_incumbent()
{
f_t upper_bound = get_cutoff();
f_t upper_bound = upper_bound_.load();

for (auto& worker : *deterministic_workers_) {
// Check nodes in plunge stack - filter in place
Expand Down Expand Up @@ -3613,7 +3617,7 @@ void branch_and_bound_t<i_t, f_t>::deterministic_populate_diving_heap()
const int num_diving = deterministic_diving_workers_->size();
constexpr int target_nodes_per_worker = 10;
const int target_total = num_diving * target_nodes_per_worker;
f_t cutoff = get_cutoff();
f_t cutoff = upper_bound_.load();

// Collect candidate nodes from BFS worker backlog heaps
std::vector<std::pair<mip_node_t<i_t, f_t>*, f_t>> candidates;
Expand Down
Loading
Loading