Branch-and-Bound tree functionality
The functionality of the Branch-and-Bound implementation extended from Bonobo.jl and some extended features like strong branching and the callbacks.
Problem
The main problem structure as stored in the Branch-and-Bound tree.
Boscia.SimpleOptimizationProblem
— TypeRepresents an optimization problem of the form:
min_x f(x)
s.t. x ∈ X (given by the LMO)
x_j ∈ Z ∀ j in integer_variables
Boscia.Solve_Stage
— TypeEnum for the solving stage
Bonobo.get_branching_indices
— MethodReturns the indices of the discrete variables for the branching in Bonobo.BnBTree
Boscia._trivial_domain
— MethodTrivial domain function.
Boscia._trivial_domain_point
— MethodTrivial domain point function.
Boscia.indicator_present
— MethodAre indicator constraints present
Boscia.is_integer_feasible
— MethodChecks if a given vector is valid integral solution. Specifically for mixed problems.
Boscia.is_linear_feasible
— MethodChecks if x is valid for all linear and variable bound constraints
Customized Bonobo structures and functions
Our adaptations to the functions in Bonobo.jl
.
Bonobo.optimize!
— Methodoptimize!(tree::BnBTree; callback=(args...; kwargs...)->())
Optimize the problem using a branch and bound approach. The steps, repeated until terminated is true, are the following:
# 1. get the next open node depending on the traverse strategy
node = get_next_node(tree, tree.options.traverse_strategy)
# 2. evaluate the current node and return the lower and upper bound
# if the problem is infeasible both values should be set to NaN
lb, ub = evaluate_node!(tree, node)
# 3. update the upper and lower bound of the node struct
set_node_bound!(tree.sense, node, lb, ub)
# 4. update the best solution
updated = update_best_solution!(tree, node)
updated && bound!(tree, node.id)
# 5. remove the current node
close_node!(tree, node)
# 6. compute the node children and adds them to the tree
# internally calls get_branching_variable and branch_on_variable!
branch!(tree, node)
A callback
function can be provided which will be called whenever a node is closed. It always has the arguments tree
and node
and is called after the node
is closed. Additionally the callback function must accept additional keyword arguments (kwargs
) which are set in the following ways:
- If the node is infeasible the kwarg
node_infeasible
is set totrue
. - If the node has a higher lower bound than the incumbent the kwarg
worse_than_incumbent
is set totrue
.
Node Evaluation
Evaluation of the nodes and handling of branching.
Boscia.AbstractFrankWolfeNode
— TypeAbtractFrankWolfeNode <: Bonobo.AbstractNode
Boscia.FrankWolfeNode
— TypeFrankWolfeNode <: AbstractFrankWolfeNode
A node in the branch-and-bound tree storing information for a Frank-Wolfe subproblem.
std
stores the id, lower and upper bound of the node. active_set
store the active set structure. local_bounds
instead of storing the complete LMO, it just stores the bounds specific to THIS node. All other integer bounds are stored in the root. 'level' stores the level in the tree 'fwdualgaplimit' set the tolerance for the dual gap in the FW algorithms 'precomputed_set' stores specifically the extreme points computed in DICG for warm-start.
Boscia.NodeInfo
— TypeNodeInfo
Holds the necessary information of every node. This needs to be added by every AbstractNode
as std::NodeInfo
This variant is more flexibel than Bonobo.BnBNodeInfo.
Bonobo.evaluate_node!
— MethodComputes the relaxation at that node
Bonobo.get_branching_nodes_info
— MethodCreate the information of the new branching nodes based on their parent and the index of the branching variable
Bonobo.get_relaxed_values
— MethodReturns the solution vector of the relaxed problem at the node
Callbacks
There are two callbacks. One for the Branch-and-Bound tree that records progress data, checks the time limit and prints the logs. The other is a callback for the Frank-Wolfe runs that runs some checks in each iteration. Additionally, the computed vertices are added to the solution pool. Lastly, the Frank-Wolfe solve can be interrupted if either the dual bound has reached the current incumbent or there are enough more promising nodes open.
Boscia.build_FW_callback
— MethodFrank-Wolfe Callback.
Is called in every Frank-Wolfe iteration. Node evaluation can be dynamically stopped here. Time limit is checked. If the vertex is providing a better incumbent, it is added as solution.
Boscia.build_bnb_callback
— MethodBranch-and-Bound Callback. Collects statistics and prints logs if verbose is turned on.
Output of Boscia: iter : current iteration of Boscia node id : current node id lower bound : treelb(tree) incumbent : tree.incumbent gap : tree.incumbent-treelb(tree) rel. gap : dualgap/tree.incumbent time : total time of Boscia time/nodes : average time per node FW time : time spent in FW LMO time : time used by LMO LMO calls : number of computeextreme_point calls in FW FW iterations : number of iterations in FW
Tightenings
Tightenings are performed on node level and can be used either just for the node in question or globally. If the obejctive is strongly convex and/or sharp, this can also be used to tighten the lower bound at the current node.
Boscia.dual_tightening
— MethodTightening of the bounds at node level. Children node inherit the updated bounds.
Boscia.global_tightening
— MethodUse the gradient of the root node to tighten the global bounds.
Boscia.prune_children
— MethodUse strong convexity and/or sharpness to potentially remove one of the children nodes. If both sharpness and strong convexity parameters are provided, strong convexity is preferred.
Boscia.store_data_global_tightening
— MethodSave the gradient of the root solution (i.e. the relaxed solution) and the corresponding lower and upper bounds.
Boscia.tightening_lowerbound
— MethodTighten the lower bound using strong convexity and/or sharpness of the objective.
Strong and Hybrid Branching
We provide a strong branching strategy consisting of running Frank-Wolfe for only a few iterations to get an estimate of the bound increase. Due to the cost of strong branching, it is usually not advisable to run strong branching through the whole tree. Hence, we provide a hybrid branching which performs strong branching until a user specified depth and then switches to most-infeasible branching.
Boscia.HybridStrongBranching
— TypeHybrid between partial strong branching and another strategy. perform_strong_branch(tree, node) -> Bool
decides whether to perform strong branching or not.
Bonobo.get_branching_variable
— MethodGet branching variable using strong branching. Create all possible subproblems, solve them and pick the one with the most progress.
Boscia.strong_up_to_depth
— Functionstrongupto_depth performs strong branching on nodes up to a predetermined depth, and the falls back to another rule