You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Conception/drake-master/solvers/branch_and_bound.h

695 lines
27 KiB

#pragma once
#include <list>
#include <map>
#include <memory>
#include <unordered_map>
#include <utility>
#include "drake/solvers/mathematical_program.h"
#include "drake/solvers/mathematical_program_result.h"
namespace drake {
namespace solvers {
/**
* A node in the branch-and-bound (bnb) tree.
* The whole branch-and-bound tree solves the mixed-integer problem
* min f(x) (1)
* s.t g(x) ≤ 0
* z ∈ {0, 1}
* where the binary variables z are a subset of the decision variables x.
* In this node, we will fix some binary variables to either 0 and 1, and relax
* the rest of the binary variables to continuous variables between 0 and 1.
* Namely we will solve the following problem with all variables being
* continuous
* min f(x) (2)
* s.t g(x) ≤ 0
* z_fixed = b_fixed
* 0 ≤ z_relaxed ≤ 1
* where z_fixed, z_relaxed is a partition of the original binary variables z.
* z_fixed is the fixed binary variables, z_relaxed is the relaxed binary
* variables. b_fixed is a vector containing the assigned values of the fixed
* binary variables z_fixed, b_fixed only contains value either 0 or 1.
*
* Each node is created from its parent node, by fixing one binary variable to
* either 0 or 1.
*/
class MixedIntegerBranchAndBoundNode {
public:
DRAKE_NO_COPY_NO_MOVE_NO_ASSIGN(MixedIntegerBranchAndBoundNode)
/** Construct the root node from an optimization program.
* For the mixed-integer optimization program
* min f(x) (1)
* s.t g(x) ≤ 0
* z ∈ {0, 1}
* we will construct a root node for this mixed-integer program. In the root
* node, it enforces all the costs and constraints in the original program,
* except the binary constraint z ∈ {0, 1}. Instead, it enforces the relaxed
* constraint to 0 ≤ z ≤ 1. So the root node contains the program
* min f(x) (2)
* s.t g(x) ≤ 0
* 0 ≤ z ≤ 1
* This optimization program is solved during the node construction.
* @param prog The mixed-integer optimization program (1) in the
* documentation above.
* @param solver_id The ID of the solver for the optimization program.
* @retval (node, map_old_vars_to_new_vars) node is the root node of the tree,
* that contains the optimization program (2) in the documentation above. This
* root node has no parent. We also need to recreate new decision variables in
* the root node, from the original optimization program (1), since the binary
* variables will be converted to continuous variables in (2). We thus return
* the map from the old variables to the new variables.
* @pre prog should contain binary variables.
* @pre solver_id can be either Gurobi or Scs.
* @throws std::exception if the preconditions are not met.
*/
static std::pair<
std::unique_ptr<MixedIntegerBranchAndBoundNode>,
std::unordered_map<symbolic::Variable::Id, symbolic::Variable>>
ConstructRootNode(const MathematicalProgram& prog, const SolverId& solver_id);
/**
* Branches on @p binary_variable, and creates two child nodes. In the left
* child node, the binary variable is fixed to 0. In the right node, the
* binary variable is fixed to 1. Solves the optimization program in each
* child node.
* @param binary_variable This binary variable is fixed to either 0 or 1 in
* the child node.
* @pre binary_variable is in remaining_binary_variables_;
* @throws std::exception if the preconditions are not met.
*/
void Branch(const symbolic::Variable& binary_variable);
/** Returns true if a node is the root.
* A root node has no parent.
*/
[[nodiscard]] bool IsRoot() const;
/** Determine if a node is a leaf or not.
* A leaf node has no child nodes.
*/
bool IsLeaf() const { return !left_child_ && !right_child_; }
/**
* Getter for the mathematical program.
*/
const MathematicalProgram* prog() const { return prog_.get(); }
/**
* Getter for the mathematical program result.
*/
const MathematicalProgramResult* prog_result() const {
return prog_result_.get();
}
/** Getter for the left child. */
const MixedIntegerBranchAndBoundNode* left_child() const {
return left_child_.get();
}
/** Getter for the mutable left child. */
MixedIntegerBranchAndBoundNode* mutable_left_child() {
return left_child_.get();
}
/** Getter for the right child. */
const MixedIntegerBranchAndBoundNode* right_child() const {
return right_child_.get();
}
/** Getter for the mutable right child. */
MixedIntegerBranchAndBoundNode* mutable_right_child() {
return right_child_.get();
}
/** Getter for the parent node. */
const MixedIntegerBranchAndBoundNode* parent() const { return parent_; }
/** Getter for the mutable parent node. */
MixedIntegerBranchAndBoundNode* mutable_parent() { return parent_; }
/**
* Getter for the binary variable, whose value was not fixed in
* the parent node, but is fixed to either 0 or 1 in this node.
*/
const symbolic::Variable& fixed_binary_variable() const {
return fixed_binary_variable_;
}
/**
* Getter for the value of the binary variable, which was not fixed in the
* parent node, but is fixed to either 0 or 1 in this node.
*/
int fixed_binary_value() const { return fixed_binary_value_; }
/**
* Getter for the remaining binary variables in this node.
*/
const std::list<symbolic::Variable>& remaining_binary_variables() const {
return remaining_binary_variables_;
}
/** Getter for the solution result when solving the optimization program. */
SolutionResult solution_result() const { return solution_result_; }
/**
* Getter for optimal_solution_is_integral.
* @pre The optimization problem is solved successfully.
* @throws std::exception if the precondition is not satisfied.
*/
[[nodiscard]] bool optimal_solution_is_integral() const;
/** Getter for solver id. */
const SolverId& solver_id() const { return solver_id_; }
private:
/**
* If the solution to a binary variable is either less than integral_tol or
* larger than 1 - integral_tol, then we regard the solution to be binary.
* This method set this tolerance.
*/
void set_integral_tolerance(double integral_tol) {
integral_tol_ = integral_tol;
}
private:
// Constructs an empty node. Clone the input mathematical program to this
// node. The child and the parent nodes are all nullptr.
// @param prog The optimization program whose binary variable constraints are
// all relaxed to 0 ≤ z ≤ 1.
// @param binary_variables The list of binary variables in the mixed-integer
// problem.
MixedIntegerBranchAndBoundNode(
const MathematicalProgram& prog,
const std::list<symbolic::Variable>& binary_variables,
const SolverId& solver_id);
// Fix a binary variable to a binary value. Add a constraint z = 0 or z = 1 to
// the optimization program. Remove this binary variable from the
// remaining_binary_variables_ list; set the binary_var_ and
// binary_var_value_.
void FixBinaryVariable(const symbolic::Variable& binary_variable,
bool binary_value);
// Check if the optimal solution to the program in this node satisfies all
// integral constraints.
// Only call this function AFTER the program is solved.
void CheckOptimalSolutionIsIntegral();
enum class OptimalSolutionIsIntegral {
kTrue, ///< The program in this node has been solved, and the solution to
/// all binary variables satisfies the integral constraints.
kFalse, ///< The program in this node has been solved, and the solution to
/// some binary variables does not satisfy the integral constraints.
kUnknown, ///< Either the program in this node has not been solved, or we
/// have not checked if the solution satisfy the integral
/// constraints yet.
};
// Stores the optimization program in this node.
std::unique_ptr<MathematicalProgram> prog_;
std::unique_ptr<MathematicalProgramResult> prog_result_;
std::unique_ptr<MixedIntegerBranchAndBoundNode> left_child_;
std::unique_ptr<MixedIntegerBranchAndBoundNode> right_child_;
MixedIntegerBranchAndBoundNode* parent_;
// The newly fixed binary variable z, in the decision variables x.
// The value of z was not fixed in the parent node, but is fixed in this
// node.
symbolic::Variable fixed_binary_variable_;
// The value of the newly fixed binary variable z, in the decision variables
// x. The value of z was not fixed in the parent node, but is fixed in this
// node.
int fixed_binary_value_;
// The variables that were binary in the original mixed-integer optimization
// problem, but whose value has not been fixed to either 0 or 1 yet.
std::list<symbolic::Variable> remaining_binary_variables_;
// The solution result of the optimization program.
SolutionResult solution_result_;
// Whether the optimal solution in this node satisfies all integral
// constraints.
OptimalSolutionIsIntegral optimal_solution_is_integral_;
SolverId solver_id_;
// If the solution to a binary variable is either less than integral_tol or
// larger than 1 - integral_tol, then we regard the solution to be binary.
double integral_tol_{1E-5};
};
/**
* Given a mixed-integer optimization problem (MIP) (or more accurately, mixed
* binary problem), solve this problem through branch-and-bound process. We will
* first replace all the binary variables with continuous variables, and relax
* the integral constraint on the binary variables z ∈ {0, 1} with continuous
* constraints 0 ≤ z ≤ 1. In the subsequent steps, at each node of the tree,
* we will fix some binary variables to either 0 or 1, and solve the rest of
* the variables.
* Notice that we will create a new set of variables in the branch-and-bound
* process, since we need to replace the binary variables with continuous
* variables.
*/
class MixedIntegerBranchAndBound {
public:
/**
* Different methods to pick a branching variable.
*/
enum class VariableSelectionMethod {
kUserDefined, ///< User defined.
kLeastAmbivalent, ///< Pick the variable whose value is closest to 0 or 1.
kMostAmbivalent, ///< Pick the variable whose value is closest to 0.5
};
/**
* Different methods to pick a branching node.
*/
enum class NodeSelectionMethod {
kUserDefined, ///< User defined.
kDepthFirst, ///< Pick the node with the most binary variables fixed.
kMinLowerBound, ///< Pick the node with the smallest optimal cost.
};
/**
* The function signature for the user defined method to pick a branching node
* or a branching variable.
*/
using NodeSelectFun = std::function<MixedIntegerBranchAndBoundNode*(
const MixedIntegerBranchAndBound&)>;
using VariableSelectFun = std::function<const symbolic::Variable*(
const MixedIntegerBranchAndBoundNode&)>;
/** The function signature for user defined node callback function. */
using NodeCallbackFun = std::function<void(
const MixedIntegerBranchAndBoundNode&, MixedIntegerBranchAndBound* bnb)>;
/**
* Construct a branch-and-bound tree from a mixed-integer optimization
* program.
* @param prog A mixed-integer optimization program.
* @param solver_id The ID of the solver for the optimization.
*/
explicit MixedIntegerBranchAndBound(const MathematicalProgram& prog,
const SolverId& solver_id);
/**
* Solve the mixed-integer problem (MIP) through a branch and bound process.
* @retval solution_result If solution_result=SolutionResult::kSolutionFound,
* then the best solutions are stored inside solutions(). The user
* can access the value of each variable(s) through GetSolution(...).
* If solution_result=SolutionResult::kInfeasibleConstraints, then the
* mixed-integer problem is primal infeasible.
* If solution_result=SolutionResult::kUnbounded, then the mixed-integer
* problem is primal unbounded.
*/
SolutionResult Solve();
/** Get the optimal cost. */
[[nodiscard]] double GetOptimalCost() const;
/**
* Get the n'th sub-optimal cost.
* The costs are sorted in the ascending order. The sub-optimal costs do not
* include the optimal cost.
* @param nth_suboptimal_cost The n'th sub-optimal cost.
* @pre `nth_suboptimal_cost` is between 0 and solutions().size() - 1.
* @throws std::exception if the precondition is not satisfied.
*/
[[nodiscard]] double GetSubOptimalCost(int nth_suboptimal_cost) const;
/**
* Get the n'th best integral solution for a variable.
* The best solutions are sorted in the ascending order based on their costs.
* Each solution is found in a separate node in the branch-and-bound tree, so
* the values of the binary variables are different in each solution.
* @param mip_var A variable in the original MIP.
* @param nth_best_solution. The index of the best integral solution.
* @pre `mip_var` is a variable in the original MIP.
* @pre `nth_best_solution` is between 0 and solutions().size().
* @throws std::exception if the preconditions are not satisfied.
*/
[[nodiscard]] double GetSolution(const symbolic::Variable& mip_var,
int nth_best_solution = 0) const;
/**
* Get the n'th best integral solution for some variables.
* The best solutions are sorted in the ascending order based on their costs.
* Each solution is found in a separate node in the branch-and-bound tree, so
* @param mip_vars Variables in the original MIP.
* @param nth_best_solution. The index of the best integral solution.
* @pre `mip_vars` are variables in the original MIP.
* @pre `nth_best_solution` is between 0 and solutions().size().
* @throws std::exception if the preconditions are not satisfied.
*/
template <typename Derived>
[[nodiscard]] typename std::enable_if_t<
std::is_same_v<typename Derived::Scalar, symbolic::Variable>,
MatrixLikewise<double, Derived>>
GetSolution(const Eigen::MatrixBase<Derived>& mip_vars,
int nth_best_solution = 0) const {
MatrixLikewise<double, Derived> value(mip_vars.rows(), mip_vars.cols());
for (int i = 0; i < mip_vars.rows(); ++i) {
for (int j = 0; j < mip_vars.cols(); ++j) {
value(i, j) = GetSolution(mip_vars(i, j), nth_best_solution);
}
}
return value;
}
/**
* Given an old variable in the original mixed-integer program, return the
* corresponding new variable in the branch-and-bound process.
* @param old_variable A variable in the original mixed-integer program.
* @retval new_variable The corresponding variable in the branch-and-bound
* procedure.
* @pre old_variable is a variable in the mixed-integer program, passed in the
* constructor of this MixedIntegerBranchAndBound.
* @throws std::exception if the pre-condition fails.
*/
[[nodiscard]] const symbolic::Variable& GetNewVariable(
const symbolic::Variable& old_variable) const;
/**
* Given a matrix of old variables in the original mixed-integer program,
* return a matrix of corresponding new variables in the branch-and-bound
* process.
* @param old_variables Variables in the original mixed-integer program.
* @retval new_variables The corresponding variables in the branch-and-bound
* procedure.
*/
template <typename Derived>
typename std::enable_if_t<
is_eigen_scalar_same<Derived, symbolic::Variable>::value,
MatrixLikewise<symbolic::Variable, Derived>>
GetNewVariables(const Eigen::MatrixBase<Derived>& old_variables) const {
MatrixLikewise<symbolic::Variable, Derived> new_variables;
new_variables.resize(old_variables.rows(), old_variables.cols());
for (int i = 0; i < old_variables.rows(); ++i) {
for (int j = 0; j < old_variables.cols(); ++j) {
new_variables(i, j) = GetNewVariable(old_variables(i, j));
}
}
return new_variables;
}
/**
* The user can choose the method to pick a node for branching. We provide
* options such as "depth first" or "min lower bound".
* @param node_selection_method The option to pick a node. If the option is
* NodeSelectionMethod::kUserDefined, then the user should also provide the
* method to pick a node through SetUserDefinedNodeSelectionFunction.
*/
void SetNodeSelectionMethod(NodeSelectionMethod node_selection_method) {
node_selection_method_ = node_selection_method;
}
/**
* Set the user-defined method to pick the branching node. This method is
* used if the user calls
* SetNodeSelectionMethod(NodeSelectionMethod::kUserDefined).
*
* For example, if the user has defined a function LeftMostNode that would
* return the left-most unfathomed node in the tree, then the user could do
* \code{.cc}
* MixedIntegerBranchAndBoundNode* LeftMostNodeInSubTree(
* const MixedIntegerBranchAndBound& branch_and_bound,
* const MixedIntegerBranchAndBoundNode& subtree_root) {
* // Starting from the subtree root, find the left most leaf node that is
* not fathomed.
* blah
* }
*
* MixedIntegerBranchAndBound bnb(...);
* bnb.SetNodeSelectionMethod(
* MixedIntegerBranchAndBound::NodeSelectionMethod::kUserDefined);
* // Use a lambda function as the NodeSelectionFun
* bnb->SetUserDefinedNodeSelectionFunction([](
* const MixedIntegerBranchAndBound& branch_and_bound) {
* return LeftMostNodeInSubTree(branch_and_bound,
* *(branch_and_bound.root()));
* \endcode
* A more detailed example can be found in
* solvers/test/branch_and_bound_test.cc
* in TestSetUserDefinedNodeSelectionFunction.
* @note The user defined function should pick an un-fathomed leaf node for
* branching.
* @throws std::exception if the node is not a leaf node, or it is
* fathomed.
*/
void SetUserDefinedNodeSelectionFunction(NodeSelectFun fun) {
node_selection_userfun_ = fun;
}
/**
* The user can choose the method to pick a variable for branching in each
* node. We provide options such as "most ambivalent" or "least ambivalent".
* @param variable_selection_method The option to pick a variable. If the
* option is VariableSelectionMethod::kUserDefined, then the user should also
* provide the method to pick a variable through
* SetUserDefinedVariableSelectionFunction(...).
*/
void SetVariableSelectionMethod(
VariableSelectionMethod variable_selection_method) {
variable_selection_method_ = variable_selection_method;
}
/**
* Set the user-defined method to pick the branching variable. This method is
* used if the user calls
* SetVariableSelectionMethod(VariableSelectionMethod::kUserDefined).
*
* For example, if the user has defined a function FirstVariable, that would
* return the first un-fixed binary variable in this branch as
* \code{.cc}
* SymbolicVariable* FirstVariable(const MixedIntegerBranchAndBoundNode& node)
* {
* return node.remaining_binary_variables().begin();
* }
* \endcode
* The user can then set the branch-and-bound to use this function to select
* the branching variable as
* \code{.cc}
* MixedIntegerBranchAndBound bnb(...);
* bnb.SetVariableSelectionMethod(
* MixedIntegerBranchAndBound:VariableSelectionMethod::kUserDefined);
* // Set VariableSelectFun by using a function pointer.
* bnb.SetUserDefinedVariableSelectionFunction(FirstVariable);
* \endcode
*/
void SetUserDefinedVariableSelectionFunction(VariableSelectFun fun) {
variable_selection_userfun_ = fun;
}
/** Set the flag to true if the user wants to search an integral solution
* in each node, after the optimization problem in that node is solved.
* The program can search for an integral solution based on the solution to
* the optimization program in the node, by rounding the binary variables
* to the nearest integer value, and solve for the continuous variables.
* If a solution is obtained in this new program, then this solution is
* an integral solution to the mixed-integer program.
*/
void SetSearchIntegralSolutionByRounding(bool flag) {
search_integral_solution_by_rounding_ = flag;
}
/**
* The user can set a defined callback function in each node. This function is
* called after the optimization is solved in each node.
*/
void SetUserDefinedNodeCallbackFunction(NodeCallbackFun fun) {
node_callback_userfun_ = fun;
}
/**
* If a leaf node is fathomed, then there is no need to branch on this node
* any more. A leaf node is fathomed is any of the following conditions are
* satisfied:
*
* 1. The optimization problem in the node is infeasible.
* 2. The optimal cost of the node is larger than the best upper bound.
* 3. The optimal solution to the node satisfies all the integral constraints.
* 4. All binary variables are fixed to either 0 or 1 in this node.
*
* @param leaf_node A leaf node to check if it is fathomed.
* @pre The node should be a leaf node.
* @throws std::exception if the precondition is not satisfied.
*/
[[nodiscard]] bool IsLeafNodeFathomed(
const MixedIntegerBranchAndBoundNode& leaf_node) const;
/**
* Getter for the root node. Note that this is aliased for the lifetime of
* this object.
*/
[[nodiscard]] const MixedIntegerBranchAndBoundNode* root() const {
return root_.get();
}
/** Getter for the best upper bound. */
[[nodiscard]] double best_upper_bound() const { return best_upper_bound_; }
/** Getter for the best lower bound. */
[[nodiscard]] double best_lower_bound() const { return best_lower_bound_; }
/**
* Getter for the solutions.
* Returns a list of solutions, together with the costs evaluated at the
* solutions. The solutions are sorted in the ascending order based on the
* cost.
*/
[[nodiscard]] const std::multimap<double, Eigen::VectorXd>& solutions()
const {
return solutions_;
}
/** Setter for the absolute gap tolerance.
* The branch-and-bound will terminate if its difference between its best
* upper bound and best lower bound is below this gap tolerance.
*/
void set_absolute_gap_tol(double tol) { absolute_gap_tol_ = tol; }
/** Getter for the absolute gap tolerance. */
[[nodiscard]] double absolute_gap_tol() const { return absolute_gap_tol_; }
/** Setter for the relative gap tolerance.
* The branch-and-bound will terminate if
* (best_upper_bound() - best_lower_bound()) / abs(best_lower_bound())
* is smaller than this tolerance.
*/
void set_relative_gap_tol(double tol) { relative_gap_tol_ = tol; }
/** Geeter for the relative gap tolerance. */
[[nodiscard]] double relative_gap_tol() const { return relative_gap_tol_; }
private:
// Forward declaration the tester class.
friend class MixedIntegerBranchAndBoundTester;
/**
* Pick one node to branch.
*/
[[nodiscard]] MixedIntegerBranchAndBoundNode* PickBranchingNode() const;
/**
* Pick the node with the minimal lower bound.
*/
[[nodiscard]] MixedIntegerBranchAndBoundNode* PickMinLowerBoundNode() const;
/**
* Pick the node with the most binary variables fixed.
*/
[[nodiscard]] MixedIntegerBranchAndBoundNode* PickDepthFirstNode() const;
/**
* Pick the branching variable in a node.
*/
[[nodiscard]] const symbolic::Variable* PickBranchingVariable(
const MixedIntegerBranchAndBoundNode& node) const;
/**
* Branch on a node, solves the optimization, and update the best lower and
* upper bounds.
* @param node. The node to be branched.
* @param branching_variable. Branch on this variable in the node.
*/
void BranchAndUpdate(MixedIntegerBranchAndBoundNode* node,
const symbolic::Variable& branching_variable);
/**
* Update the solutions (solutions_) and the best upper bound, with an
* integral solution and its cost.
* @param solution. The integral solution.
* @param cost. The cost evaluated at this integral solution.
*/
void UpdateIntegralSolution(const Eigen::Ref<const Eigen::VectorXd>& solution,
double cost);
/**
* The branch-and-bound has converged if the gap between the best upper bound
* and the best lower bound is less than the tolerance.
*/
[[nodiscard]] bool HasConverged() const;
/** Call the callback function in each node. */
void NodeCallback(const MixedIntegerBranchAndBoundNode& node);
/**
* Search for an integral solution satisfying all the constraints in this
* node, together with the integral constraints in the original mixed-integer
* program. It will construct a new optimization program, same as the one
* in this node, but the remaining binary variables are all rounded to
* the binary value that is closest to the solution of the optimization
* program in this node.
* @note this function is only called if the following conditions are
* satisfied:
* 1. The optimization problem in this node is feasible.
* 2. The optimal solution to the problem in this node is not integral.
* 3. The user called SetSearchIntegralSolutionByRounding(true);
*
* @note This method will change the data field such as solutions_ and/or
* best_upper_bound_, if an integral solution is found.
*/
void SearchIntegralSolutionByRounding(
const MixedIntegerBranchAndBoundNode& node);
// The root node of the tree.
std::unique_ptr<MixedIntegerBranchAndBoundNode> root_;
// We re-created the decision variables in the optimization program in the
// branch-and-bound. All nodes uses the same new set of decision variables,
// which is different from the variables in the original mixed-integer program
// (the one passed in the constructor of MixedIntegerBranchAndBound). This map
// is used to find the corresponding new variable from the old variable in the
// mixed-integer program.
std::unordered_map<symbolic::Variable::Id, symbolic::Variable>
map_old_vars_to_new_vars_;
// The best upper bound of the mixed-integer optimization optimal cost. An
// upper bound is obtained by evaluating the cost at a solution satisfying all
// the constraints (including the integral constraints) in the mixed-integer
// problem.
double best_upper_bound_;
// The best lower bound of the mixed-integer optimization optimal cost. This
// best lower bound is obtained by taking the minimal of the optimal cost in
// each leaf node.
double best_lower_bound_;
// Solutions found so far. Each entry in this list contains both the
// cost and the decision variable values. This list is sorted in the
// ascending order based on the cost, and it contains at most
// max_num_solutions_ elements.
std::multimap<double, Eigen::VectorXd> solutions_;
int max_num_solutions_{10};
// The branch and bound process will terminate when the best upper bound is
// sufficiently close to the best lower bound, that is, when either of the
// following conditions is satisfied:
// 1. (best_upper_bound_ - best_lower_bound_) / abs(best_lower_bound_) <
// relative_gap_tol
// 2. best_upper_bound_ - best_lower_bound_ < absolute_gap_tol_;
double absolute_gap_tol_ = 1E-2;
double relative_gap_tol_ = 1E-2;
VariableSelectionMethod variable_selection_method_ =
VariableSelectionMethod::kMostAmbivalent;
NodeSelectionMethod node_selection_method_ =
NodeSelectionMethod::kMinLowerBound;
bool search_integral_solution_by_rounding_ = false;
// The user defined function to pick a branching variable. Default is null.
VariableSelectFun variable_selection_userfun_ = nullptr;
// The user defined function to pick a branching node. Default is null.
NodeSelectFun node_selection_userfun_ = nullptr;
// The user defined callback function in each node. Default is null.
NodeCallbackFun node_callback_userfun_ = nullptr;
};
} // namespace solvers
} // namespace drake