Branch & Bound Tree¶
At the center of the branch & bound algorithm there are the Tree
and Node
classes. The Tree
class can be customized to use user-provided branching
and node selection strategies.
The Tree
class is initialized with a root node that contains the original instance
of the problem (with the original problem bounds). After solving the problem at
the root node, the algorithm branches on one or more variables at one or more
points to create multiple children nodes. The non-root nodes don’t store the
entire problem, instead they only store the variables bounds at the node and
a pointer to the parent problem.
When nodes are first created, they are marked as not visited and added to the queue of nodes to be visited. At each iteration of the branch & bound loop, the algorithm pops the most promising node from the queue and solves the node problem. The solution at the node is used to update the node state (global lower and upper bounds).
The tree upper bound is the best (lowest) feasible solution found so far, the function to update the tree upper bound is as follows:
def update_tree_upper_bound(tree, candidate_solution):
if not candidate_solution.is_feasible:
return
tree.upper_bound = min(
candidate_solution.objective_value,
tree.upper_bound,
)
The tree lower bound is the lowest of the open nodes lower bounds, that is the solution of the linear relaxation of the problem. Since open nodes have not been solved (by definition), then we consider their parent lower bound as their lower bound. If there are no open nodes, then we consider the lower bounds of phatomed nodes.
def update_tree_lower_bound(tree, candidate_solution):
if not candidate_solution.is_feasible:
return
best_lower_bound = infinity
for node in tree.open_nodes:
node_lower_bound = node.parent.lower_bound
if node_lower_bound < best_lower_bound:
best_lower_bound = node_lower_bound
if best_lower_bound != infinity:
# The lower bound was updated from open nodes. Finished.
tree.lower_bound = best_lower_bound
return
for node in tree.phatomed_nodes:
node_lower_bound = node.parent.lower_bound
if node_lower_bound < best_lower_bound:
best_lower_bound = node_lower_bound
# Update lower bound.
tree.lower_bound = best_lower_bound