Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
45 changes: 30 additions & 15 deletions cpp/src/dual_simplex/branch_and_bound.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -697,17 +697,17 @@ void branch_and_bound_t<i_t, f_t>::exploration_ramp_up(search_tree_t<i_t, f_t>*
// to repair the heuristic solution.
repair_heuristic_solutions();

f_t lower_bound = node->lower_bound;
f_t upper_bound = get_upper_bound();
f_t rel_gap = user_relative_gap(original_lp_, upper_bound, lower_bound);
f_t abs_gap = upper_bound - lower_bound;
i_t nodes_explored = (++stats_.nodes_explored);
i_t nodes_unexplored = (--stats_.nodes_unexplored);
stats_.nodes_since_last_log++;
f_t lower_bound = node->lower_bound;
f_t upper_bound = get_upper_bound();
f_t rel_gap = user_relative_gap(original_lp_, upper_bound, lower_bound);
f_t abs_gap = upper_bound - lower_bound;

if (lower_bound > upper_bound || rel_gap < settings_.relative_mip_gap_tol) {
search_tree->graphviz_node(settings_.log, node, "cutoff", node->lower_bound);
search_tree->update_tree(node, node_status_t::FATHOMED);
++stats_.nodes_explored;
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a bit strange. I think of nodes explored as the number of nodes we have solved, or perhaps proved infeasible through node presolve. If we are fathoming the node here I would not count it as explored.

--stats_.nodes_unexplored;
++stats_.nodes_since_last_log;
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here. The purpose of nodes since last log is for us to print every so often in a deterministic way. Here we did no work, so I don't think we want to increase this counter.

return;
}

Expand All @@ -717,15 +717,14 @@ void branch_and_bound_t<i_t, f_t>::exploration_ramp_up(search_tree_t<i_t, f_t>*
if (((stats_.nodes_since_last_log >= 10 || abs_gap < 10 * settings_.absolute_mip_gap_tol) &&
(time_since_last_log >= 1)) ||
(time_since_last_log > 30) || now > settings_.time_limit) {
// Check if no new node was explored until now. If this is the case,
// only the last thread should report the progress
if (stats_.nodes_explored.load() == nodes_explored) {
stats_.nodes_since_last_log = 0;
stats_.last_log = tic();
bool should_report = should_report_.exchange(false);
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need to have member variable to decide if we should log? I think this is increasing the complexity of the code for little return.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since this piece of code is running by multiple threads, the atomic here prevent multiple reports from happening (potentially out-of-order). But I agree that it is an imperfect solution. I will probably change when the ramp-up phase is reworked or the report is moved to a separated thread.


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 = stats_.nodes_explored;
i_t nodes_unexplored = stats_.nodes_unexplored;

settings_.log.printf(" %10d %10lu %+13.6e %+10.6e %6d %7.1e %s %9.2f\n",
nodes_explored,
Expand All @@ -736,6 +735,10 @@ void branch_and_bound_t<i_t, f_t>::exploration_ramp_up(search_tree_t<i_t, f_t>*
nodes_explored > 0 ? stats_.total_lp_iters / nodes_explored : 0,
gap_user.c_str(),
now);

stats_.nodes_since_last_log = 0;
stats_.last_log = tic();
should_report_ = true;
}
}

Expand Down Expand Up @@ -775,6 +778,10 @@ void branch_and_bound_t<i_t, f_t>::exploration_ramp_up(search_tree_t<i_t, f_t>*
mutex_heap_.unlock();
}
}

++stats_.nodes_explored;
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: is there a reason why all of these are ++x instead x++?

Copy link
Contributor

Choose a reason for hiding this comment

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

Why are we incrementing the nodes explored here at all?

--stats_.nodes_unexplored;
++stats_.nodes_since_last_log;
}

template <typename i_t, typename f_t>
Expand Down Expand Up @@ -806,13 +813,13 @@ void branch_and_bound_t<i_t, f_t>::explore_subtree(i_t task_id,
// - The lower bound of the parent is lower or equal to its children
assert(task_id < local_lower_bounds_.size());
local_lower_bounds_[task_id] = lower_bound;
i_t nodes_explored = (++stats_.nodes_explored);
i_t nodes_unexplored = (--stats_.nodes_unexplored);
stats_.nodes_since_last_log++;

if (lower_bound > upper_bound || rel_gap < settings_.relative_mip_gap_tol) {
search_tree.graphviz_node(settings_.log, node_ptr, "cutoff", node_ptr->lower_bound);
search_tree.update_tree(node_ptr, node_status_t::FATHOMED);
++stats_.nodes_explored;
--stats_.nodes_unexplored;
++stats_.nodes_since_last_log;
continue;
}

Expand All @@ -827,6 +834,9 @@ void branch_and_bound_t<i_t, f_t>::explore_subtree(i_t task_id,
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 = stats_.nodes_explored;
i_t nodes_unexplored = stats_.nodes_unexplored;

settings_.log.printf(" %10d %10lu %+13.6e %+10.6e %6d %7.1e %s %9.2f\n",
nodes_explored,
nodes_unexplored,
Expand Down Expand Up @@ -889,6 +899,10 @@ void branch_and_bound_t<i_t, f_t>::explore_subtree(i_t task_id,
stack.push_front(second);
stack.push_front(first);
}

++stats_.nodes_explored;
--stats_.nodes_unexplored;
++stats_.nodes_since_last_log;
}
}

Expand Down Expand Up @@ -1174,6 +1188,7 @@ mip_status_t branch_and_bound_t<i_t, f_t>::solve(mip_solution_t<i_t, f_t>& solut
min_diving_queue_size_ = 4 * settings_.num_diving_threads;
status_ = mip_exploration_status_t::RUNNING;
lower_bound_ceiling_ = inf;
should_report_ = true;

#pragma omp parallel num_threads(settings_.num_threads)
{
Expand Down
2 changes: 2 additions & 0 deletions cpp/src/dual_simplex/branch_and_bound.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,8 @@ class branch_and_bound_t {
// Global status of the solver.
omp_atomic_t<mip_exploration_status_t> status_;

omp_atomic_t<bool> should_report_;

// In case, a best-first thread encounters a numerical issue when solving a node,
// its blocks the progression of the lower bound.
omp_atomic_t<f_t> lower_bound_ceiling_;
Expand Down