Branching

This is probably the most sophisticated module in the whole project. Do not worry—we will go step by step and see what is going on. By the end of this section, you will understand the key components of the branching module and be able to build your solver with the branching module integrated.

For more information on the branching module details, please refer to the following sections. I highly recommend you to read the overview first, then the example, and finally the explanation in this page. Then go back to the details of the following branching module.

Overview

The Branching module handles the branch-and-bound procedures within RouteOpt. It manages the exploration of nodes, candidate selection via LP testing, heuristic testing, exact testing by default, and the integration of BKF [You et al., 2023] for achieving best trade off between testing time and candidate quality. The default branching strategy is 3PB, where:

  • Phase 0: Initial screening

  • Phase 1: LP testing

  • Phase 2: Heuristic testing

  • Phase 3: Exact testing

For more details, please refer to [You et al., 2023, Pessoa et al., 2020].

Example: CVRP Main.cpp Review

The main entry point of the CVRP application is located at: packages/application/cvrp/src/main.cpp.

Below is the code for main.cpp:

/*
 * Copyright (c) 2025 Zhengzhong (Ricky) You.
 * All rights reserved.
 * Software: RouteOpt
 * License: GPL-3.0
 *
 * @file main.cpp
 * @brief Main entry point for the CVRP application.
 *
 * This file contains the main function that initializes and runs the CVRP, or VRPTW solver.
 * It sets up the branch-and-bound tree controller and handles the overall solving process.
 */

#include "cvrp.hpp"                // CVRP solver header.
#include "vrptw.hpp"               // VRPTW solver header.
#include "cvrp_macro.hpp"          // Macros specific to CVRP.
#include "bbt_controller.hpp"      // Branch-and-bound tree controller.
using namespace std;
using namespace RouteOpt;
using namespace Application::CVRP;

int main(int argc, char *argv[]) {
    // Create an instance of the solver based on the application type.
    // If the application type is CVRP, create a CVRPSolver; otherwise, create a VRPTW solver.
    CVRPSolver *cvrp = app_type == APPLICATION_TYPE::CVRP
                           ? new CVRPSolver(argc, argv)
                           : new VRPTW(argc, argv);

    // Create a new branch-and-bound node (BbNode) as the root node.
    auto node = new BbNode();

    // Define a candidate selection function pointer for machine learning based candidate selection.
    // Initialize to nullptr by default.
    CandidateSelectionFuncType ml_candidate_selection = nullptr;
    // If machine learning is enabled (ml_type is not ML_NO_USE), set the candidate selection function.
    if constexpr (ml_type != ML_TYPE::ML_NO_USE) {
        ml_candidate_selection = [cvrp](auto arg1, auto &arg2, auto &arg3, auto &arg4) -> std::pair<int, int> {
            return cvrp->callMLCandidateSelection(arg1, arg2, arg3, arg4);
        };
    }

    // Define an output function pointer for writing nodes.
    OutNodeFuncType node_out_func = nullptr;
    // If the configuration enables writing node outputs, set the function accordingly.
    if constexpr (IF_WRITE_NODE_OUT) {
        node_out_func = [cvrp](auto arg1, auto &arg2, auto &arg3) -> void {
            cvrp->callWriteNodeOut(arg1, arg2, arg3);
        };
    }

    // Define a function for reading node information.
    auto node_in_func = [cvrp](auto arg1, auto &arg2, auto &arg3) -> void {
        cvrp->callReadNodeIn(arg1, arg2, arg3);
    };

    // Create an instance of the Branch-and-Bound Tree (BBT) Controller.
    // The controller is templated with:
    //   - BbNode: the type for branch-and-bound nodes.
    //   - std::pair<int, int>: the type for branching candidates.
    //   - PairHasher: hash function for branching candidates.
    Branching::BBT::BBTController<BbNode, std::pair<int, int>, PairHasher> bbt_controller{
        cvrp->getDim(), // Problem/Instance dimension.
        cvrp->refUB(), // Reference to the current upper bound.
        static_cast<int>(NUM_TESTING::PHASE0), // Number of output candidates for phase 0.
        static_cast<int>(NUM_TESTING::PHASE1), // Number of output candidates for phase 1.
        static_cast<int>(NUM_TESTING::PHASE2), // Number of output candidates for phase 2.
        static_cast<int>(NUM_TESTING::PHASE3), // Number of output candidates for phase 3.
        IF_BKF
            ? std::vector<std::pair<int, int> >{
                {static_cast<int>(BKF_TYPE::M_LP), static_cast<int>(BKF_TYPE::N_LP)},
                {static_cast<int>(BKF_TYPE::M_HEUR), static_cast<int>(BKF_TYPE::N_HEUR)}
            }
            : std::vector<std::pair<int, int> >{}, // BKF parameters if BKF is enabled.
        BbNode::defineBetterNode, // Function to compare nodes.
        BbNode::getNodeValue, // Function to extract node value.
        BbNode::getNodeIdx, // Function to extract node index.
        BbNode::getNodeIfInEnumState, // Function to check node enumeration state.
        BbNode::getLastBrc, // Function to get the last branching candidate.
        BbNode::refNodeBrIncreaseVal, // Function to reference node branching increase value.
        BbNode::getNodeIfTerminate, // Function to check if node is terminated.
        BbNode::obtainSolEdgeMap, // Function to obtain solution edge map.
        // LP testing function for processing LP testing.
        [cvrp](auto arg1, auto &arg2, auto &arg3, auto &arg4) -> decltype(auto) {
            return cvrp->processLPTesting(arg1, arg2, arg3, arg4);
        },
        // Heuristic testing function.
        [cvrp](auto arg1, auto &arg2, auto &arg3, auto &arg4) -> decltype(auto) {
            return cvrp->processCGTesting<false>(arg1, arg2, arg3, arg4);
        },
        // Exact testing function.
        [cvrp](auto arg1, auto &arg2, auto &arg3, auto &arg4) -> decltype(auto) {
            return cvrp->processCGTesting<true>(arg1, arg2, arg3, arg4);
        },
        // Function to perform pricing at the beginning of processing a node.
        [cvrp](auto arg1) -> decltype(auto) {
            return cvrp->callPricingAtBeg(arg1);
        },
        // Function to perform cutting on a node.
        [cvrp](auto arg1) -> decltype(auto) {
            return cvrp->callCutting(arg1);
        },
        // Function to impose a branching decision on a node, generating child nodes.
        [cvrp](auto arg1, auto arg2, auto &arg3) -> decltype(auto) {
            return cvrp->imposeBranching(arg1, arg2, arg3);
        },
        // Self-Defined Branching Selection Function (e.g., 2LBB).
        ml_candidate_selection,
        // Node output function for writing nodes to external storage.
        node_out_func,
        // Node input function for reading node information.
        node_in_func
    };

    // Solve the branch-and-bound tree starting from the root node.
    bbt_controller.solve(node);

    // After solving, print the optimal solution along with statistics:
    // number of nodes explored and the lower bound of the solution.
    cvrp->printOptSol(std::cout, bbt_controller.getNumNodesExplored(), bbt_controller.getLowerBound());

    // Clean up: delete the solver instance.
    delete cvrp;

    return 0;
}

Explanation

  1. BBTController Initialization:

    • Template Parameters: The controller is instantiated with the following template parameters:

      • BbNode: Node type for branch-and-bound.

      • std::pair<int, int>: Branching candidate type.

      • PairHasher: Hash function for the candidate type.

    • Phase Parameters: The parameters:

      • static_cast<int>(NUM_TESTING::PHASE0)

      • static_cast<int>(NUM_TESTING::PHASE1)

      • static_cast<int>(NUM_TESTING::PHASE2)

      • static_cast<int>(NUM_TESTING::PHASE3)

      represent the number of output candidates for each phase. In the default 3PB strategy:

      • Phase 0: Initial screening.

      • Phase 1: LP testing.

      • Phase 2: Heuristic testing.

      • Phase 3: Exact testing.

  2. BKF Parameters:

    If BKF (Branching Kernel Functions) are enabled (IF_BKF is true), a vector of pairs is passed:

    • Each pair represents BKF parameters: the first integer is the M value and the second is the N value.

    • An empty vector indicates that BKF is not used.

  3. Node Functions:

    A set of static functions from BbNode are provided:

    • defineBetterNode: Sorts nodes based on a criterion (e.g., its local lower bound).

    • getNodeValue: Returns the node’s value (e.g., its local lower bound).

    • getNodeIdx: Returns a unique node identifier.

    • getNodeIfInEnumState: Checks if the node is in an enumeration state [Baldacci et al., 2008] (i.e., all necessary columns are enumerated).

    • getLastBrc: Retrieves the last branching candidate for improvement calculation.

    • refNodeBrIncreaseVal: References the node’s branching increase value.

    • getNodeIfTerminate: Determines if the node is terminated (infeasible or optimal).

    • obtainSolEdgeMap: Gets the solution candidate (edge in this case) map for fractional candidate generation.

  4. Testing Functions:

    The controller is also provided with functions for processing testing:

    • LP Testing Function: Invokes processLPTesting on the solver.

    • Heuristic Testing Function: Invokes processCGTesting<false> on the solver.

    • Exact Testing Function: Invokes processCGTesting<true> on the solver.

    These functions take a node and candidate as inputs and output the improvement in objective value for the left and right LPs relative to the current node.

  5. Pricing, Cutting, and Branching:

    Additional functions provided include:

    • Pricing Function: Called at the beginning of node processing.

    • Cutting Function: Applies cutting on a node.

    • Impose Branching Function: Generates child nodes by applying a branching decision.

  6. Self-Defined Branching Selection and I/O Functions:

    • Self-Defined Branching Selection Function: Allows for custom branching strategies (e.g., 2LBB).

    • Node Output Function: For writing node information to external storage.

    • Node Input Function: For reading node information from external storage.

  7. Execution Flow:

    • The solve function is called to process the branch-and-bound tree starting from the root node.

    • After solving, the optimal solution and statistics (nodes explored, lower bound) are printed.

    • Finally, the solver instance is deleted.

Further Reading

For more detailed information, please refer to:

[BCM08]

Roberto Baldacci, Nicos Christofides, and Aristide Mingozzi. An exact algorithm for the vehicle routing problem based on the set partitioning formulation with additional cuts. Mathematical Programming, 115:351–385, 2008.

[PSUV20]

Artur Pessoa, Ruslan Sadykov, Eduardo Uchoa, and François Vanderbeck. A generic exact solver for vehicle routing and related problems. Mathematical Programming, 183:483–523, 2020.

[YYWY23] (1,2)

Zhengzhong You, Yu Yang, Xinshang Wang, and Wotao Yin. Two-stage learning to branch in branch-price-and-cut algorithms for solving vehicle routing problems exactly. Available at SSRN 4630549, 2023.