Skip to content
Open
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
166 changes: 71 additions & 95 deletions cpp/src/dual_simplex/branch_and_bound.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* clang-format off */
/*
* SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
/* clang-format on */
Expand Down Expand Up @@ -183,21 +183,21 @@ std::string user_mip_gap(f_t obj_value, f_t lower_bound)
{
const f_t user_mip_gap = relative_gap(obj_value, lower_bound);
if (user_mip_gap == std::numeric_limits<f_t>::infinity()) {
return " - ";
return " - ";
} else {
constexpr int BUFFER_LEN = 32;
char buffer[BUFFER_LEN];
snprintf(buffer, BUFFER_LEN - 1, "%4.1f%%", user_mip_gap * 100);
snprintf(buffer, BUFFER_LEN - 1, "%5.1f%%", user_mip_gap * 100);
return std::string(buffer);
}
}

inline const char* feasible_solution_symbol(thread_type_t type)
{
switch (type) {
case thread_type_t::EXPLORATION: return "B";
case thread_type_t::DIVING: return "D";
default: return "U";
case thread_type_t::EXPLORATION: return "B ";
case thread_type_t::DIVING: return "D ";
default: return "U ";
}
}

Expand Down Expand Up @@ -265,6 +265,51 @@ i_t branch_and_bound_t<i_t, f_t>::get_heap_size()
return size;
}

template <typename i_t, typename f_t>
void branch_and_bound_t<i_t, f_t>::report_heuristic(f_t obj)
{
if (solver_status_ == mip_exploration_status_t::RUNNING) {
f_t user_obj = compute_user_objective(original_lp_, obj);
f_t user_lower = compute_user_objective(original_lp_, get_lower_bound());
std::string user_gap = user_mip_gap<f_t>(user_obj, user_lower);

settings_.log.printf(
"H %+13.6e %+10.6e %s %9.2f\n",
user_obj,
user_lower,
user_gap.c_str(),
toc(exploration_stats_.start_time));
} else {
settings_.log.printf("New solution from primal heuristics. Objective %+.6e. Time %.2f\n",
compute_user_objective(original_lp_, obj),
toc(exploration_stats_.start_time));
}
}

template <typename i_t, typename f_t>
void branch_and_bound_t<i_t, f_t>::report(std::string symbol,
f_t obj,
f_t lower_bound,
i_t node_depth)
{
i_t nodes_explored = exploration_stats_.nodes_explored;
i_t nodes_unexplored = exploration_stats_.nodes_unexplored;
f_t user_obj = compute_user_objective(original_lp_, obj);
f_t user_lower = compute_user_objective(original_lp_, lower_bound);
f_t iter_node = exploration_stats_.total_lp_iters / nodes_explored;
std::string user_gap = user_mip_gap<f_t>(user_obj, user_lower);
settings_.log.printf("%s%10d %10lu %+13.6e %+10.6e %6d %7.1e %s %9.2f\n",
symbol.c_str(),
nodes_explored,
nodes_unexplored,
user_obj,
user_lower,
node_depth,
iter_node,
user_gap.c_str(),
toc(exploration_stats_.start_time));
}

template <typename i_t, typename f_t>
void branch_and_bound_t<i_t, f_t>::set_new_solution(const std::vector<f_t>& solution)
{
Expand Down Expand Up @@ -303,25 +348,7 @@ void branch_and_bound_t<i_t, f_t>::set_new_solution(const std::vector<f_t>& solu
}
mutex_upper_.unlock();

if (is_feasible) {
if (solver_status_ == mip_exploration_status_t::RUNNING) {
f_t user_obj = compute_user_objective(original_lp_, obj);
f_t user_lower = compute_user_objective(original_lp_, get_lower_bound());
std::string gap = user_mip_gap<f_t>(user_obj, user_lower);

settings_.log.printf(
"H %+13.6e %+10.6e %s %9.2f\n",
user_obj,
user_lower,
gap.c_str(),
toc(exploration_stats_.start_time));
} else {
settings_.log.printf("New solution from primal heuristics. Objective %+.6e. Time %.2f\n",
compute_user_objective(original_lp_, obj),
toc(exploration_stats_.start_time));
}
}

if (is_feasible) { report_heuristic(obj); }
if (attempt_repair) {
mutex_repair_.lock();
repair_queue_.push_back(crushed_solution);
Expand Down Expand Up @@ -417,17 +444,7 @@ void branch_and_bound_t<i_t, f_t>::repair_heuristic_solutions()
if (repaired_obj < upper_bound_) {
upper_bound_ = repaired_obj;
incumbent_.set_incumbent_solution(repaired_obj, repaired_solution);

f_t obj = compute_user_objective(original_lp_, repaired_obj);
f_t lower = compute_user_objective(original_lp_, get_lower_bound());
std::string user_gap = user_mip_gap<f_t>(obj, lower);

settings_.log.printf(
"H %+13.6e %+10.6e %s %9.2f\n",
obj,
lower,
user_gap.c_str(),
toc(exploration_stats_.start_time));
report_heuristic(repaired_obj);

if (settings_.solution_callback != nullptr) {
std::vector<f_t> original_x;
Expand Down Expand Up @@ -523,29 +540,13 @@ void branch_and_bound_t<i_t, f_t>::add_feasible_solution(f_t leaf_objective,
i_t leaf_depth,
thread_type_t thread_type)
{
bool send_solution = false;
i_t nodes_explored = exploration_stats_.nodes_explored;
i_t nodes_unexplored = exploration_stats_.nodes_unexplored;
bool send_solution = false;

mutex_upper_.lock();
if (leaf_objective < upper_bound_) {
incumbent_.set_incumbent_solution(leaf_objective, leaf_solution);
upper_bound_ = leaf_objective;
f_t lower_bound = get_lower_bound();
f_t obj = compute_user_objective(original_lp_, upper_bound_);
f_t lower = compute_user_objective(original_lp_, lower_bound);
settings_.log.printf(
"%s%10d %10lu %+13.6e %+10.6e %6d %7.1e %s %9.2f\n",
feasible_solution_symbol(thread_type),
nodes_explored,
nodes_unexplored,
obj,
lower,
leaf_depth,
nodes_explored > 0 ? exploration_stats_.total_lp_iters / nodes_explored : 0,
user_mip_gap<f_t>(obj, lower).c_str(),
toc(exploration_stats_.start_time));

upper_bound_ = leaf_objective;
report(feasible_solution_symbol(thread_type), leaf_objective, get_lower_bound(), leaf_depth);
send_solution = true;
}

Expand Down Expand Up @@ -611,18 +612,20 @@ node_solve_info_t branch_and_bound_t<i_t, f_t>::solve_node(
ss << "simplex-" << std::this_thread::get_id() << ".log";
std::string logname;
ss >> logname;
lp_settings.set_log_filename(logname);
lp_settings.log.enable_log_to_file("a+");
lp_settings.log.set_log_file(logname, "a");
lp_settings.log.log_to_console = false;
lp_settings.log.printf(
"%s node id = %d, branch var = %d, fractional val = %f, variable lower bound = %f, variable "
"upper bound = %f\n",
"%scurrent node: id = %d, depth = %d, branch var = %d, branch dir = %s, fractional val = "
"%f, variable lower bound = %f, variable upper bound = %f, branch vstatus = %d\n\n",
settings_.log.log_prefix.c_str(),
node_ptr->node_id,
node_ptr->depth,
node_ptr->branch_var,
node_ptr->branch_dir == rounding_direction_t::DOWN ? "DOWN" : "UP",
node_ptr->fractional_val,
node_ptr->branch_var_lower,
node_ptr->branch_var_upper);
node_ptr->branch_var_upper,
node_ptr->vstatus[node_ptr->branch_var]);
#endif

// Reset the bound_changed markers
Expand Down Expand Up @@ -685,6 +688,10 @@ node_solve_info_t branch_and_bound_t<i_t, f_t>::solve_node(
}
}

#ifdef LOG_NODE_SIMPLEX
lp_settings.log.printf("\nLP status: %d\n\n", lp_status);
#endif

if (lp_status == dual::status_t::DUAL_UNBOUNDED) {
// Node was infeasible. Do not branch
node_ptr->lower_bound = inf;
Expand Down Expand Up @@ -805,23 +812,7 @@ void branch_and_bound_t<i_t, f_t>::exploration_ramp_up(mip_node_t<i_t, f_t>* nod
bool should_report = should_report_.exchange(false);

if (should_report) {
f_t obj = compute_user_objective(original_lp_, upper_bound);
f_t user_lower = compute_user_objective(original_lp_, root_objective_);
std::string gap_user = user_mip_gap<f_t>(obj, user_lower);
i_t nodes_explored = exploration_stats_.nodes_explored;
i_t nodes_unexplored = exploration_stats_.nodes_unexplored;

settings_.log.printf(
" %10d %10lu %+13.6e %+10.6e %6d %7.1e %s %9.2f\n",
nodes_explored,
nodes_unexplored,
obj,
user_lower,
node->depth,
nodes_explored > 0 ? exploration_stats_.total_lp_iters / nodes_explored : 0,
gap_user.c_str(),
now);

report(" ", upper_bound, root_objective_, node->depth);
exploration_stats_.nodes_since_last_log = 0;
exploration_stats_.last_log = tic();
should_report_ = true;
Expand Down Expand Up @@ -936,22 +927,7 @@ void branch_and_bound_t<i_t, f_t>::explore_subtree(i_t task_id,
abs_gap < 10 * settings_.absolute_mip_gap_tol) &&
time_since_last_log >= 1) ||
(time_since_last_log > 30) || now > settings_.time_limit) {
f_t obj = compute_user_objective(original_lp_, upper_bound);
f_t user_lower = compute_user_objective(original_lp_, get_lower_bound());
std::string gap_user = user_mip_gap<f_t>(obj, user_lower);
i_t nodes_explored = exploration_stats_.nodes_explored;
i_t nodes_unexplored = exploration_stats_.nodes_unexplored;

settings_.log.printf(
" %10d %10lu %+13.6e %+10.6e %6d %7.1e %s %9.2f\n",
nodes_explored,
nodes_unexplored,
obj,
user_lower,
node_ptr->depth,
nodes_explored > 0 ? exploration_stats_.total_lp_iters / nodes_explored : 0,
gap_user.c_str(),
now);
report(" ", upper_bound, get_lower_bound(), node_ptr->depth);
exploration_stats_.last_log = tic();
exploration_stats_.nodes_since_last_log = 0;
}
Expand Down Expand Up @@ -1447,10 +1423,10 @@ mip_status_t branch_and_bound_t<i_t, f_t>::solve(mip_solution_t<i_t, f_t>& solut
settings_.num_diving_threads);

settings_.log.printf(
" | Explored | Unexplored | Objective | Bound | Depth | Iter/Node | Gap "
" | Explored | Unexplored | Objective | Bound | Depth | Iter/Node | Gap "
"| Time |\n");

exploration_stats_.nodes_explored = 0;
exploration_stats_.nodes_explored = 1;
exploration_stats_.nodes_unexplored = 2;
exploration_stats_.nodes_since_last_log = 0;
exploration_stats_.last_log = tic();
Expand Down
5 changes: 4 additions & 1 deletion cpp/src/dual_simplex/branch_and_bound.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* clang-format off */
/*
* SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
/* clang-format on */
Expand Down Expand Up @@ -199,6 +199,9 @@ class branch_and_bound_t {
// its blocks the progression of the lower bound.
omp_atomic_t<f_t> lower_bound_ceiling_;

void report_heuristic(f_t obj);
void report(std::string symbol, f_t obj, f_t lower_bound, i_t node_depth);

// Set the final solution.
mip_status_t set_final_solution(mip_solution_t<i_t, f_t>& solution, f_t lower_bound);

Expand Down
12 changes: 7 additions & 5 deletions cpp/src/dual_simplex/logger.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* clang-format off */
/*
* SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
/* clang-format on */
Expand Down Expand Up @@ -30,22 +30,24 @@ class logger_t {
{
}

void enable_log_to_file(std::string mode = "w")
void enable_log_to_file(const char* mode = "w")
{
if (log_file != nullptr) { std::fclose(log_file); }
log_file = std::fopen(log_filename.c_str(), mode.c_str());
log_file = std::fopen(log_filename.c_str(), mode);
log_to_file = true;
}

void set_log_file(const std::string& filename)
void set_log_file(const std::string& filename, const char* mode = "w")
{
log_filename = filename;
enable_log_to_file();
enable_log_to_file(mode);
}

void close_log_file()
{
if (log_file != nullptr) { std::fclose(log_file); }
log_file = nullptr;
log_to_file = false;
}

void printf(const char* fmt, ...)
Expand Down
Loading