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.

bnb structures

Our structures of the branch and bound tree.

bnb functions

Our functions operate the branch and bound tree.

Boscia.initializeMethod
initialize(; kwargs...)

Initialize the branch and bound framework with the the following arguments. Later it can be dispatched on BnBTree{Node, Root, Solution} for various methods.

Keyword arguments

  • traverse_strategy [BestFirstSearch] currently the only supported traverse strategy is BestFirstSearch. Should be an AbstractTraverseStrategy
  • branch_strategy [FIRST] currently the only supported branching strategies are FIRST and MOST_INFEASIBLE. Should be an AbstractBranchStrategy
  • atol [1e-6] the absolute tolerance to check whether a value is discrete
  • rtol [1e-6] the relative tolerance to check whether a value is discrete
  • Node DefaultNode can be special structure which is used to store all information about a node.
    • needs to have AbstractNode as the super type
    • needs to have std :: BnBNodeInfo as a field (see BnBNodeInfo)
  • Solution DefaultSolution stores the node and several other information about a solution
  • root [nothing] the information about the root problem. The type can be used for dispatching on types
  • sense [:Min] can be :Min or :Max depending on the objective sense
  • Value [Vector{Float64}] the type of a solution

Return a BnBTree object which is the input for optimize!.

source
Boscia.sort_solutions!Method
sort_solutions!(solutions::Vector{<:AbstractSolution})

Sort the solutions vector by objective value such that the best solution is at index 1.

source

Node Evaluation

Evaluation of the nodes and handling of branching.

Boscia.AbstractFrankWolfeNodeType
AbtractFrankWolfeNode <: AbstractNode 
NodeInfo

Holds the necessary information of every node. This needs to be added by every AbstractNode as std::NodeInfo

This variant is more flexible than Bonobo.BnBNodeInfo.

source
Boscia.FrankWolfeNodeType
FrankWolfeNode <: AbstractFrankWolfeNode

A node in the branch-and-bound tree storing information for a Frank-Wolfe subproblem.

std stores the id, lower, upper bound and Depth 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 'precomputedset' stores specifically the extreme points computed in DICG for warm-start. 'parentlowerboundbase' contains lower bound value of the parent node. Needed for updating pseudocosts. 'branchedon' contains the index of the parent. Required for updating pseudocosts. 'branchedright' Boolean value specifying if node resulted from a left or right branch. Needed for updating pseudocosts. 'distancetoint' Stores information on the rounding amount at branching. Required for correct scaling of pseudocosts.

source
Boscia.add_node!Method
add_node!(tree::BnBTree{Node}, parent::Union{AbstractNode, Nothing}, node_info::NamedTuple)

Add a new node to the tree using the node_info. For information on that see set_root!.

source
Boscia.bound!Method
bound!(tree::BnBTree, current_node_id::Int)

Close all nodes which have a lower bound higher or equal to the incumbent

source
Boscia.close_node!Method
close_node!(tree::BnBTree, node::AbstractNode)

Delete the node from the nodes dictionary and the priority queue.

source
Boscia.create_nodeMethod
create_node(Node, node_id::Int, parent::Union{AbstractNode, Nothing}, node_info::NamedTuple)

Creates a node of type Node with id node_id and the named tuple node_info. For information on that see set_root!.

source
Boscia.set_node_bound!Method
set_node_bound!(objective_sense::Symbol, node::AbstractNode, lb, ub)

Set the bounds of the node object to the lower and upper bound given. Internally everything is stored as a minimization problem. Therefore the objective_sense :Min/:Max is needed.

source
Boscia.set_root!Method
set_root!(tree::BnBTree, node_info::NamedTuple)

Set the root node information based on the node_info which needs to include the same fields as the Node struct given to the initialize method. (Besides the std field which is set by Bonobo automatically)

Example

If your node structure is the following:

mutable struct MIPNode <: AbstractNode
    std :: BnBNodeInfo
    lbs :: Vector{Float64}
    ubs :: Vector{Float64}
    status :: MOI.TerminationStatusCode
end

then you can call the function with this syntax:

set_root!(tree, (
    lbs = fill(-Inf, length(x)),
    ubs = fill(Inf, length(x)),
    status = MOI.OPTIMIZE_NOT_CALLED
))
source

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_callbackMethod

Frank-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.

source
Boscia.build_bnb_callbackMethod

Branch-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

source

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.prune_childrenMethod

Use 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.

source

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.HybridStrongBranchingType

Hybrid between partial strong branching and another strategy. perform_strong_branch(tree, node) -> Bool decides whether to perform strong branching or not.

source
Boscia.get_branching_variableMethod

Get branching variable using strong branching. Create all possible subproblems, solve them and pick the one with the most progress.

source
Boscia.strong_up_to_depthFunction

strongupto_depth performs strong branching on nodes up to a predetermined depth, and the falls back to another rule

source