Skip to content
4 changes: 2 additions & 2 deletions ci/validate_wheel.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ PYDISTCHECK_ARGS=(
if [[ "${package_dir}" == "python/libcuopt" ]]; then
if [[ "${RAPIDS_CUDA_MAJOR}" == "12" ]]; then
PYDISTCHECK_ARGS+=(
--max-allowed-size-compressed '650Mi'
--max-allowed-size-compressed '660Mi'
)
else
PYDISTCHECK_ARGS+=(
--max-allowed-size-compressed '495Mi'
--max-allowed-size-compressed '520Mi'
)
fi
elif [[ "${package_dir}" != "python/cuopt" ]] && \
Expand Down
1 change: 0 additions & 1 deletion cpp/src/branch_and_bound/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

set(BRANCH_AND_BOUND_SRC_FILES
${CMAKE_CURRENT_SOURCE_DIR}/branch_and_bound.cpp
${CMAKE_CURRENT_SOURCE_DIR}/mip_node.cpp
${CMAKE_CURRENT_SOURCE_DIR}/pseudo_costs.cpp
${CMAKE_CURRENT_SOURCE_DIR}/diving_heuristics.cpp
)
Expand Down
57 changes: 34 additions & 23 deletions cpp/src/branch_and_bound/branch_and_bound.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
/* clang-format on */

#include <branch_and_bound/branch_and_bound.hpp>
#include <branch_and_bound/diving_heuristics.hpp>
#include <branch_and_bound/mip_node.hpp>
#include <branch_and_bound/pseudo_costs.hpp>

Expand Down Expand Up @@ -35,15 +36,12 @@
#include <deque>
#include <future>
#include <limits>
#include <map>
#include <optional>
#include <string>
#include <thread>
#include <unordered_map>
#include <vector>

namespace cuopt::linear_programming::dual_simplex {

namespace {

template <typename f_t>
Expand Down Expand Up @@ -1039,7 +1037,9 @@ struct deterministic_bfs_policy_t
const std::vector<i_t>& fractional,
const std::vector<f_t>& x) override
{
i_t var = this->worker.pc_snapshot.variable_selection(fractional, x);
logger_t log;
log.log = false;
i_t var = this->worker.pc_snapshot.variable_selection(fractional, x, log);
auto dir = martin_criteria(x[var], this->bnb.root_relax_soln_.x[var]);
return {var, dir};
}
Expand All @@ -1048,8 +1048,10 @@ struct deterministic_bfs_policy_t
const std::vector<i_t>& fractional,
const std::vector<f_t>& x) override
{
logger_t log;
log.log = false;
node->objective_estimate =
this->worker.pc_snapshot.obj_estimate(fractional, x, node->lower_bound);
this->worker.pc_snapshot.obj_estimate(fractional, x, node->lower_bound, log);
}

void on_node_completed(mip_node_t<i_t, f_t>* node,
Expand Down Expand Up @@ -1114,33 +1116,36 @@ struct deterministic_diving_policy_t
const std::vector<i_t>& fractional,
const std::vector<f_t>& x) override
{
logger_t log;
log.log = false;

switch (this->worker.diving_type) {
case search_strategy_t::PSEUDOCOST_DIVING:
return this->worker.variable_selection_from_snapshot(fractional, x);
return pseudocost_diving(
this->worker.pc_snapshot, fractional, x, *this->worker.root_solution, log);

case search_strategy_t::LINE_SEARCH_DIVING:
if (this->worker.root_solution) {
logger_t log;
log.log = false;
return line_search_diving<i_t, f_t>(fractional, x, *this->worker.root_solution, log);
}
return this->worker.variable_selection_from_snapshot(fractional, x);
return line_search_diving<i_t, f_t>(fractional, x, *this->worker.root_solution, log);

case search_strategy_t::GUIDED_DIVING:
return this->worker.guided_variable_selection(fractional, x);
if (this->worker.incumbent_snapshot.empty()) {
return pseudocost_diving(
this->worker.pc_snapshot, fractional, x, *this->worker.root_solution, log);
} else {
return guided_diving(
this->worker.pc_snapshot, fractional, x, this->worker.incumbent_snapshot, log);
}

case search_strategy_t::COEFFICIENT_DIVING: {
logger_t log;
log.log = false;
return coefficient_diving<i_t, f_t>(this->bnb.original_lp_,
return coefficient_diving<i_t, f_t>(this->worker.leaf_problem,
fractional,
x,
this->bnb.var_up_locks_,
this->bnb.var_down_locks_,
log);
}

default: return this->worker.variable_selection_from_snapshot(fractional, x);
default: CUOPT_LOG_ERROR("Invalid diving method!"); return {-1, rounding_direction_t::NONE};
Comment thread
nguidotti marked this conversation as resolved.
}
}

Expand Down Expand Up @@ -1188,6 +1193,9 @@ std::pair<node_status_t, rounding_direction_t> branch_and_bound_t<i_t, f_t>::upd
node_status_t status = node_status_t::PENDING;
rounding_direction_t round_dir = rounding_direction_t::NONE;

worker->recompute_basis = true;
worker->recompute_bounds = true;
Comment on lines +1190 to +1191
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this could be turned into a member function of worker (or a policy function)? something like invalidate_basis or something of the sort (probably not an ideal suggestion, but I feel like we could be higher-level here)


if (lp_status == dual::status_t::DUAL_UNBOUNDED) {
node_ptr->lower_bound = inf;
policy.graphviz(search_tree, node_ptr, "infeasible", 0.0);
Expand Down Expand Up @@ -1247,6 +1255,8 @@ std::pair<node_status_t, rounding_direction_t> branch_and_bound_t<i_t, f_t>::upd
assert(dir != rounding_direction_t::NONE);

policy.update_objective_estimate(node_ptr, leaf_fractional, leaf_solution.x);
worker->recompute_basis = false;
worker->recompute_bounds = false;

logger_t log;
log.log = false;
Expand Down Expand Up @@ -2506,7 +2516,7 @@ mip_status_t branch_and_bound_t<i_t, f_t>::solve(mip_solution_t<i_t, f_t>& solut
set_uninitialized_steepest_edge_norms(original_lp_, basic_list, edge_norms_);

pc_.resize(original_lp_.num_cols);
original_lp_.A.transpose(pc_.AT);
original_lp_.A.transpose(*pc_.AT);
{
raft::common::nvtx::range scope_sb("BB::strong_branching");
strong_branching<i_t, f_t>(original_lp_,
Expand Down Expand Up @@ -3321,11 +3331,12 @@ template <typename PoolT>
void branch_and_bound_t<i_t, f_t>::deterministic_broadcast_snapshots(
PoolT& pool, const std::vector<f_t>& incumbent_snapshot)
{
deterministic_snapshot_t<i_t, f_t> snap;
snap.upper_bound = upper_bound_.load();
snap.total_lp_iters = exploration_stats_.total_lp_iters.load();
snap.incumbent = incumbent_snapshot;
snap.pc_snapshot = pc_.create_snapshot();
deterministic_snapshot_t<i_t, f_t> snap{
.upper_bound = upper_bound_,
.pc_snapshot = pc_,
.incumbent = incumbent_snapshot,
.total_lp_iters = exploration_stats_.total_lp_iters,
};

for (auto& worker : pool) {
worker.set_snapshots(snap);
Expand Down
6 changes: 3 additions & 3 deletions cpp/src/branch_and_bound/branch_and_bound.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
#pragma once

#include <branch_and_bound/bb_event.hpp>
#include <branch_and_bound/branch_and_bound_worker.hpp>
#include <branch_and_bound/deterministic_workers.hpp>
#include <branch_and_bound/diving_heuristics.hpp>
#include <branch_and_bound/mip_node.hpp>
#include <branch_and_bound/node_queue.hpp>
#include <branch_and_bound/pseudo_costs.hpp>
#include <branch_and_bound/worker.hpp>
#include <branch_and_bound/worker_pool.hpp>

#include <cuts/cuts.hpp>

Expand Down Expand Up @@ -107,7 +107,7 @@ class branch_and_bound_t {
}
}

// Set a solution based on the user problem during the course of the solve
// Set a solution based on the user problem during solve time
void set_new_solution(const std::vector<f_t>& solution);

// This queues the solution to be processed at the correct work unit timestamp
Expand Down
31 changes: 31 additions & 0 deletions cpp/src/branch_and_bound/constants.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/* clang-format off */
/*
* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
/* clang-format on */

#pragma once

namespace cuopt::linear_programming::dual_simplex {

constexpr int num_search_strategies = 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 search_strategy_t : int {
BEST_FIRST = 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)
};

enum class rounding_direction_t { NONE = -1, DOWN = 0, UP = 1 };

enum class branch_and_bound_mode_t { PARALLEL = 0, DETERMINISTIC = 1 };

} // namespace cuopt::linear_programming::dual_simplex
24 changes: 4 additions & 20 deletions cpp/src/branch_and_bound/deterministic_workers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
#pragma once

#include <branch_and_bound/bb_event.hpp>
#include <branch_and_bound/branch_and_bound_worker.hpp>
#include <branch_and_bound/diving_heuristics.hpp>
#include <branch_and_bound/node_queue.hpp>
#include <branch_and_bound/worker.hpp>

#include <utilities/work_limit_context.hpp>

Expand Down Expand Up @@ -58,7 +58,7 @@ struct deterministic_snapshot_t {
f_t upper_bound;
pseudo_cost_snapshot_t<i_t, f_t> pc_snapshot;
std::vector<f_t> incumbent;
i_t total_lp_iters;
int64_t total_lp_iters;
};
Comment thread
nguidotti marked this conversation as resolved.

template <typename i_t, typename f_t, typename Derived>
Expand All @@ -74,7 +74,7 @@ class deterministic_worker_base_t : public branch_and_bound_worker_t<i_t, f_t> {

// Diving-specific snapshots (ignored by BFS workers)
std::vector<f_t> incumbent_snapshot;
i_t total_lp_iters_snapshot{0};
int64_t total_lp_iters_snapshot{0};

std::vector<queued_integer_solution_t<i_t, f_t>> integer_solutions;
int next_solution_seq{0};
Expand All @@ -90,7 +90,7 @@ class deterministic_worker_base_t : public branch_and_bound_worker_t<i_t, f_t> {
const std::vector<variable_type_t>& var_types,
const simplex_solver_settings_t<i_t, f_t>& settings,
const std::string& context_name)
: base_t(id, original_lp, Arow, var_types, settings), work_context(context_name)
: base_t(id, original_lp, Arow, var_types, settings), work_context(context_name), pc_snapshot(1)
{
work_context.deterministic = true;
}
Expand Down Expand Up @@ -342,22 +342,6 @@ class deterministic_diving_worker_t
{objective, solution, depth, this->worker_id, this->next_solution_seq++});
++this->total_integer_solutions;
}

branch_variable_t<i_t> variable_selection_from_snapshot(const std::vector<i_t>& fractional,
const std::vector<f_t>& solution) const
{
assert(root_solution != nullptr);
return this->pc_snapshot.pseudocost_diving(fractional, solution, *root_solution);
}

branch_variable_t<i_t> guided_variable_selection(const std::vector<i_t>& fractional,
const std::vector<f_t>& solution) const
{
if (this->incumbent_snapshot.empty()) {
return variable_selection_from_snapshot(fractional, solution);
}
return this->pc_snapshot.guided_diving(fractional, solution, this->incumbent_snapshot);
}
};

template <typename i_t, typename f_t, typename WorkerT, typename Derived>
Expand Down
Loading
Loading