Skip to content

best dive lower bound#58

Merged
Josh Robbins (jrobbins11) merged 3 commits intomainfrom
bugfix/best-dive-lower-bound
Apr 13, 2026
Merged

best dive lower bound#58
Josh Robbins (jrobbins11) merged 3 commits intomainfrom
bugfix/best-dive-lower-bound

Conversation

@jrobbins11
Copy link
Copy Markdown
Contributor

  • added bugfix for computing lower bound during best dive in B&B
  • fixed pruning bug

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors and fixes branch-and-bound bound/pruning behavior, specifically to compute a correct global lower bound during “best dive” search and to improve queue pruning correctness.

Changes:

  • Replaces inline lower-bound computation with a dedicated BranchAndBound::get_lower_bound() helper.
  • Implements best-dive lower-bound computation by taking the minimum bound over all queued nodes (not just the queue top).
  • Updates PriorityQueuePrunable::prune() to remove all prunable elements and rebuild the heap; adds an accessor for the underlying container to support best-dive lower-bound scanning.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
src/BranchAndBound.cpp Introduces get_lower_bound() and uses it in the main convergence loop; adds best-dive LB logic.
include/zonoopt/BranchAndBound.hpp Declares the new get_lower_bound() helper.
include/zonoopt/BnbDataStructures.hpp Fixes pruning to remove multiple elements and re-heapifies; adds get_container() accessor.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/BranchAndBound.cpp Outdated
Comment on lines +803 to +812
else if (this->data.admm_data->settings.search_mode == 0) // best first
{
J_min = std::min(this->node_queue.top()->solution.J, min_val_pair.second);
}
else if (this->data.admm_data->settings.search_mode == 1) // best dive
{
J_min = min_val_pair.second;
for (const auto& n : this->node_queue.get_container())
J_min = std::min(n->solution.J, J_min);
}
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

In best-first / best-dive branches, min_val_pair.second is used even when valid is false (i.e., no active thread tags). ThreadSafeSet::get_min() returns a default-constructed value when empty, so this can produce an incorrect lower bound (and potentially premature convergence) when J_threads is empty but node_queue is not. Consider handling !valid by computing the bound from node_queue only (or treating the thread bound as +inf) before taking mins.

Copilot uses AI. Check for mistakes.
Comment on lines 299 to 310
// prune queue
void prune(const T& t)
{
auto it_prune = std::find_if(this->c.begin(), this->c.end(), [&](const T& item)
auto it_prune = std::remove_if(this->c.begin(), this->c.end(), [&](const T& item)
{
return this->comp(item, t);
});
if (it_prune != this->c.end())
{
this->c.erase(it_prune, this->c.end());
std::make_heap(this->c.begin(), this->c.end(), this->comp);
}
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

PriorityQueuePrunable::prune() removes items using this->comp(item, t). With NodeCompare, any item with is_priority()==true will never satisfy comp(item, t) when t is non-priority, so priority nodes will not be pruned even if their objective is worse than the incumbent. If pruning is intended to discard all nodes whose lower bound cannot beat the incumbent (regardless of priority), the prune predicate should compare the objective bound directly (or use a comparator that ignores the priority flag for pruning).

Copilot uses AI. Check for mistakes.
Comment thread src/BranchAndBound.cpp Outdated
Comment on lines +807 to +812
else if (this->data.admm_data->settings.search_mode == 1) // best dive
{
J_min = min_val_pair.second;
for (const auto& n : this->node_queue.get_container())
J_min = std::min(n->solution.J, J_min);
}
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

get_lower_bound() computes the best-dive lower bound by scanning the entire underlying priority queue container while holding pq_mtx. Since this runs in the main monitoring loop, it can block worker threads from pushing/popping and can become a bottleneck as the queue grows. Consider maintaining a separate min-bound (updated on push/pop/prune) or copying out the needed J values under lock and computing the min outside the critical section.

Copilot uses AI. Check for mistakes.
@jrobbins11 Josh Robbins (jrobbins11) merged commit 6285847 into main Apr 13, 2026
12 checks passed
@jrobbins11 Josh Robbins (jrobbins11) deleted the bugfix/best-dive-lower-bound branch April 13, 2026 13:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants