Node selectors are used to decide which of the leaves in the current branching tree is selected as next subproblem to be processed. The ordering relation of the tree's leaves for storing them in the leaf priority queue is also defined by the node selectors.
A complete list of all node selectors contained in this release can be found here.
We now explain how users can add their own node selectors. Take the node selector for depth first search (src/scip/nodesel_dfs.c) as an example. As all other default plugins, it is written in C. C++ users can easily adapt the code by using the scip::ObjNodesel wrapper base class and implement the scip_...() virtual methods instead of the SCIP_DECL_NODESEL... callback methods.
Additional documentation for the callback methods of a node selector can be found in the file type_nodesel.h.
Here is what you have to do to implement a node selector:
At the top of the new file "nodesel_mynodeselector.c" you can find the node selector properties. These are given as compiler defines. In the C++ wrapper class, you have to provide the node selector properties by calling the constructor of the abstract base class scip::ObjNodesel from within your constructor. The properties you have to set have the following meaning:
Below the header "Data structures" you can find a struct which is called "struct SCIP_NodeselData". In this data structure, you can store the data of your node selector. For example, you should store the adjustable parameters of the node selector in this data structure. If you are using C++, you can add node selector data as usual as object variables to your class.
Defining node selector data is optional. You can leave the struct empty.
At the bottom of "nodesel_mynodeselector.c", you can find the interface method SCIPincludeNodeselMynodeselector(), which also appears in "nodesel_mynodeselector.h" SCIPincludeNodeselMynodeselector() is called by the user, if (s)he wants to include the node selector, i.e., if (s)he wants to use the node selector in his/her application.
This method only has to be adjusted slightly. It is responsible for notifying SCIP of the presence of the node selector. For this, you can either call SCIPincludeNodesel(), or SCIPincludeNodeselBasic() since SCIP version 3.0. In the latter variant, additional callbacks must be added via setter functions as, e.g., SCIPsetNodeselCopy(). We recommend this latter variant because it is more stable towards future SCIP versions which might have more callbacks, whereas source code using the first variant must be manually adjusted with every SCIP release containing new callbacks for node selectors in order to compile.
If you are using node selector data, you have to allocate the memory for the data at this point. You can do this by calling:
You also have to initialize the fields in struct SCIP_NodeselData afterwards.
The fundamental callback methods of the plugins are the ones that have to be implemented in order to obtain an operational algorithm. They are passed together with the node selector itself to SCIP using SCIPincludeNodesel() or SCIPincludeNodeselBasic(), see Interface Methods.
Node selector plugins have two fundamental callback methods, namely the NODESELSELECT method and the NODESELCOMP method. These methods have to be implemented for every node selector; the other callback methods are optional. They implement the two requirements every node selector has to fulfill: Selecting a node from the queue to be processed next and, given two nodes, deciding which of both is favored by the node selector's selection rule. The first task is implemented in the NODESELSELECT callback, the second one in the NODESELCOMP callback. In the C++ wrapper class scip::ObjNodesel, the scip_select() method and the scip_comp() method (which correspond to the NODESELSELECT callback and the NODESELCOMP callback, respectively) are virtual abstract member functions. You have to implement them in order to be able to construct an object of your node selector class.
Additional documentation for the callback methods can be found in type_nodesel.h.
The NODESELSELECT callback is the first method called in each iteration in the main solving loop. It should decide which of the leaves in the current branching tree is selected as the next subproblem to be processed. It can arbitrarily decide between all leaves stored in the tree, but for performance reasons, the current node's children and siblings are often treated different from the remaining leaves. This is mainly due to the warm start capabilities of the simplex algorithm and the expectation that the bases of neighboring vertices in the branching tree very similar. The node selector's choice of the next node to process can have a large impact on the solver's performance, because it influences the finding of feasible solutions and the development of the global dual bound.
Besides the ranking of the node selector, every node gets assigned a node selection priority by the branching rule that created the node. See the BRANCHEXECLP and BRANCHEXECPS callbacks of the branching rules for details. For example, the node where the branching went in the same way as the deviation from the branching variable's root solution could be assigned a higher priority than the node where the branching went in the opposite direction.
The following methods provide access to the various types of leaf nodes:
The NODESELCOMP callback is called to compare two leaves of the current branching tree (say node 1 and node 2) regarding their ordering relation.
The NODESELCOMP should return the following values:
The additional callback methods do not need to be implemented in every case. However, some of them have to be implemented for most applications, they can be used, for example, to initialize and free private data. Additional callbacks can either be passed directly with SCIPincludeNodesel() to SCIP or via specific setter functions after a call of SCIPincludeNodeselBasic(), see also Interface Methods.
If you are using node selector data, you have to implement this method in order to free the node selector data. This can be done by the following procedure:
If you have allocated memory for fields in your node selector data, remember to free this memory before freeing the node selector data itself. If you are using the C++ wrapper class, this method is not available. Instead, just use the destructor of your class to free the member variables of your class.
The NODESELINIT callback is executed after the problem is transformed. The node selector may, e.g., use this call to initialize its node selector data.
The NODESELCOPY callback is executed when a SCIP instance is copied, e.g. to solve a sub-SCIP. By defining this callback as
NULL the user disables the execution of the specified node selector for all copied SCIP instances. This may deteriorate the performance of primal heuristics using sub-SCIPs.
The NODESELEXIT callback is executed before the transformed problem is freed. In this method, the node selector should free all resources that have been allocated for the solving process in NODESELINIT.
The NODESELINITSOL callback is executed when the presolving is finished and the branch-and-bound process is about to begin. The node selector may use this call to initialize its branch-and-bound specific data.
The NODESELEXITSOL callback is executed before the branch-and-bound process is freed. The node selector should use this call to clean up its branch-and-bound data.