R1Cs: Separation¶
Overview¶
The files packages/rank1_cuts/separation/include/rank1_separation_macro.hpp
and packages/rank1_cuts/separation/include/rank1_separation_controller.hpp
define the macro definitions, type aliases, and the controller class used for the separation of Rank-1 cuts within the RouteOpt software package. These files provide the parameters for tolerances, memory settings, and generator parameters, as well as the implementation of the Rank-1 separation controller which integrates cut generation, memory finding, and cut selection modules.
License¶
This software is licensed under GPL-3.0.
File Contents¶
The separation module is composed of two main components:
Macro Definitions and Type Aliases
The file
rank1_separation_macro.hpp
defines constants, type aliases, and helper macros used in the Rank-1 separation process.Separation Controller
The file
rank1_separation_controller.hpp
defines theRank1SeparationController
class, which manages the separation process for Rank-1 cuts.
Macro Definitions and Type Aliases¶
The file rank1_separation_macro.hpp
provides several key definitions:
RANK1_SEPARATION_VERBOSE
Verbosity flag for debugging the separation process.
SOL_X_RANK1_TOLERANCE
Tolerance for solution x values in Rank-1 separation.
MAX_CUT_MEM_FACTOR
Factor to control the maximum memory size for cut generation.
INITIAL_RANK_1_MULTI_LABEL_POOL_SIZE
Initial size for the pool of Rank-1 multi-labels.
FIND_MEM_USE_ENUMERATION_OR_MIP
Threshold to decide whether to use enumeration or MIP (Mixed-Integer Programming) for memory finding.
MAX_NUMBER_SEGMENT_FOR_ONE_COLUMN
Maximum number of segments allowed for a single column in the separation process.
MAX_UNDETERMINED_ARC_NUMBER
Maximum number of undetermined arcs.
MAX_LABELS
Maximum number of labels allowed.
MAX_HEURISTIC_SEP_ROW_RANK1
Maximum number of neighbor customers for separating Rank-1 cuts.
MIN_CUTS_ADDED_PER_ROUND
Minimum number of cuts to be added per separation round.
TIME_LIMIT_FOR_MIP_FIND_MEM
Time limit (in seconds) for MIP-based memory finding.
CUT_VIO_FACTOR
Violation factor for cuts.
RANK1_ROW_DENSITY
Estimated density for Rank-1 cuts.
SOL_KEEPER_LIMIT
Limit for keeping solution routes in memory.
MemoryType
Enumeration specifying the memory mode used in separation:
NO_MEMORY
No memory is used.
NODE_MEMORY
Node-based memory is applied.
ARC_MEMORY
Arc-based memory is applied.
VectorHashInRank1
Custom hash functor for a vector of integers, used in unordered containers.
arcBit
A bitset type representing undetermined arcs.
cutLong
Alias for the custom long bitset type used in cut representations.
sparseRowMatrixXI and sparseRowVectorXI
Type aliases for row-major sparse matrices and vectors using the Eigen library.
RouteInfo
Structure containing route information, including:
frac_x
The fractional value associated with the route (e.g., LP solution value).
col_seq
The sequence of customer indices representing the route.
forward_concatenate_pos
The position used for forward concatenation during route merging.
RANK1_VERBOSE_EXEC
Macro for conditionally executing a statement based on the verbosity flag.
Separation Controller: Rank1SeparationController¶
The file rank1_separation_controller.hpp
defines the Rank1SeparationController
class, which manages the separation of Rank-1 cuts. Key functionalities include:
Constructor¶
rank1CutsDataShared
Input: A reference to a shared Rank-1 cuts data object of type
Rank1CutsDataShared
.Meaning: Provides precomputed multipliers and metadata required for generating Rank-1 cuts.
max_row_rank1
Input: An integer representing the maximum row dimension for Rank-1 cuts.
Meaning: Limits the maximum size (or depth) of cuts based on the number of customers (\(|C|\)).
max_num_r1c3_per_round
Input: An integer specifying the maximum number of R1C cuts with dimension 3 to be generated per separation round.
Meaning: Restricts the number of specific types of cuts (dimension 3).
max_num_r1c_per_round
Input: An integer specifying the maximum number of Rank-1 cuts (with dimensions other than 3) to be generated per round.
Meaning: Ensures that the total number of generated cuts remains within a manageable limit.
solver
Input: A reference to the IP solver object.
Meaning: Provides access to the underlying solver to integrate new cuts into the LP/MIP model.
cost_mat4_vertex
Input: A two-dimensional vector of doubles representing the cost matrix for vertices.
Meaning: Used to evaluate the cost associated with transitions between vertices in the routing problem.
updateInfo()¶
limited_memory_type
Input: A value of the enumeration
MemoryType
.Meaning: Determines the memory mode to apply during separation (e.g., NO_MEMORY, NODE_MEMORY, ARC_MEMORY).
pricing_hard_level
Input: A value of the enumeration
PRICING_HARD_LEVEL
.Meaning: Indicates the complexity of the pricing subproblem, guiding the cut generation strategy.
if_collect_sol
Input: A boolean flag.
Meaning: Specifies whether the current solution routes should be collected for further processing or analysis.
sol
Input: A vector of
RouteInfo
structures.Meaning: Represents the current solution routes obtained from the LP/MIP, each containing route details such as fractional value and customer sequence.
old_cuts
Input: A vector of existing Rank-1 cuts (
R1c
structures).Meaning: Contains previously generated cuts that may be used for comparison or updating during the separation process.
separateRank1Cuts()¶
cuts
Input/Output: A vector of
R1c
structures.Meaning: On input, may contain existing cuts; on output, will include newly generated or updated Rank-1 cuts.
if_mem
Input: A boolean flag.
Meaning: Indicates whether memory-based search strategies should be applied during the separation process.
Note
When call this function in
enumeration
state,if_mem
should be set tofalse
to avoid redundant memory search.if_select_cuts
Input: A boolean flag.
Meaning: Determines whether cut selection strategies should be executed to filter or prioritize generated cuts.
Note
When call this function
enumeration
state,if_select_cuts
should be set tofalse
to avoid giving up cuts that are bad for labeling but have no impact on the pricing problem inenumeration
state, i.e., inspection algorithm.
convert2ArcMemory()¶
existing_cuts
Input/Output: A vector of
R1c
structures.Meaning: Contains cuts in a node-memory representation which will be converted to an arc-memory representation for improved pricing performance.
getIfOnceUseNoSymmetryMem()¶
Return Value
Output: A boolean flag.
Meaning: Indicates whether the “no symmetry memory” approach has been applied at least once. This flag is useful for diagnostic purposes.
Note
When performing bi-directional labeling in non-symmetric problems, it is crucial to decide whether to change your resource meet point.
If you have already employed the “no symmetry memory” (i.e., arc memory) and then change your resource meet point, an arc (e.g., 2→3) may be interpreted as 3→2 in your solution vector.
Since 2→3 and 3→2 represent different memory arcs, this change will alter the column coefficients of the cut, typically resulting in a weaker cut compared to the original.
Nevertheless, the cut remains valid.
Header Codes¶
Below is an excerpt from rank1_separation_macro.hpp
that illustrates key macro definitions and type aliases:
/*
* Copyright (c) 2025 Zhengzhong (Ricky) You.
* All rights reserved.
* Software: RouteOpt
* License: GPL-3.0
*/
/*
* @file rank1_separation_macro.hpp
* @brief Macro definitions and type aliases for Rank-1 separation in RouteOpt.
*
* This header defines various constants, type aliases, and helper macros that are used
* in the Rank-1 separation process.
*/
#ifndef ROUTE_OPT_RANK1_SEPARATION_MACRO_HPP
#define ROUTE_OPT_RANK1_SEPARATION_MACRO_HPP
#include <bitset>
#include <Eigen/Sparse>
#include <vector>
#include "route_opt_macro.hpp"
namespace RouteOpt::Rank1Cuts::Separation {
constexpr bool RANK1_SEPARATION_VERBOSE = false;
constexpr double SOL_X_RANK1_TOLERANCE = 1e-3;
constexpr double MAX_CUT_MEM_FACTOR = 0.15;
constexpr int INITIAL_RANK_1_MULTI_LABEL_POOL_SIZE = 2048;
constexpr int FIND_MEM_USE_ENUMERATION_OR_MIP = 1000;
constexpr int MAX_NUMBER_SEGMENT_FOR_ONE_COLUMN = 16;
constexpr int MAX_UNDETERMINED_ARC_NUMBER = 128;
constexpr int MAX_LABELS = 1024;
constexpr int MAX_HEURISTIC_SEP_ROW_RANK1 = 16;
constexpr int MIN_CUTS_ADDED_PER_ROUND = 50;
constexpr double TIME_LIMIT_FOR_MIP_FIND_MEM = 0.2;
constexpr double CUT_VIO_FACTOR = 0.1;
constexpr double RANK1_ROW_DENSITY = 0.1;
constexpr int SOL_KEEPER_LIMIT = 20;
enum class MemoryType {
NO_MEMORY = 0,
NODE_MEMORY = 1,
ARC_MEMORY = 2
};
struct RouteInfo {
double frac_x{};
std::vector<int> col_seq{};
int forward_concatenate_pos{};
};
struct VectorHashInRank1 {
size_t operator()(const std::vector<int> &v) const {
std::size_t hash = 0;
for (const int num: v) {
hash ^= std::hash<int>{}(num) + 0x9e3779b9 + (hash << 6) + (hash >> 2);
}
return hash;
}
};
using arcBit = std::bitset<MAX_UNDETERMINED_ARC_NUMBER>;
using cutLong = routeOptLong;
using sparseRowMatrixXI = Eigen::SparseMatrix<int, Eigen::RowMajor>;
using sparseRowVectorXI = Eigen::SparseVector<int, Eigen::RowMajor>;
#define RANK1_VERBOSE_EXEC(stmt) do { \
if (RANK1_SEPARATION_VERBOSE) { \
stmt; \
} \
} while (0);
} // namespace RouteOpt::Rank1Cuts::Separation
#endif // ROUTE_OPT_RANK1_SEPARATION_MACRO_HPP
Below is an excerpt from rank1_separation_controller.hpp
that demonstrates the interface of the separation controller:
/*
* Copyright (c) 2025 Zhengzhong (Ricky) You.
* All rights reserved.
* Software: RouteOpt
* License: GPL-3.0
*
* @file rank1_separation_controller.hpp
* @brief Rank-1 Separation Controller for handling the generation, updating, and processing of Rank-1 cuts.
*
* This header defines the Rank1SeparationController class which coordinates the separation process for Rank-1 cuts.
* It integrates shared Rank-1 cuts data, cut generation, memory finding, and cut selection modules,
* and interfaces with the underlying solver and vertex cost matrix.
*/
#ifndef ROUTE_OPT_RANK1_SEPARATION_CONTROLLER_HPP
#define ROUTE_OPT_RANK1_SEPARATION_CONTROLLER_HPP
#include "rank1_data_shared.hpp"
#include "rank1_cuts_generator.hpp"
#include "rank1_memory_finder.hpp"
#include "rank1_cuts_selector.hpp"
#include "solver.hpp"
namespace RouteOpt::Rank1Cuts::Separation {
class Rank1SeparationController {
public:
Rank1SeparationController(const Rank1CutsDataShared &rank1CutsDataShared,
int max_row_rank1,
int max_num_r1c3_per_round,
int max_num_r1c_per_round,
Solver &solver,
const std::vector<std::vector<double>> &cost_mat4_vertex);
void updateInfo(MemoryType limited_memory_type,
PRICING_HARD_LEVEL pricing_hard_level,
bool if_collect_sol,
const std::vector<RouteInfo> &sol,
const std::vector<R1c> &old_cuts);
void separateRank1Cuts(std::vector<R1c> &cuts,
bool if_mem = true,
bool if_select_cuts = true);
void convert2ArcMemory(std::vector<R1c> &existing_cuts);
[[nodiscard]] auto getIfOnceUseNoSymmetryMem() const {
return if_once_use_no_symmetry_mem;
}
Rank1SeparationController() = delete;
~Rank1SeparationController() = default;
private:
std::vector<std::vector<RouteInfo>> sol_vec{};
std::reference_wrapper<const Rank1CutsDataShared> rank1CutsDataShared_ref;
DataShared sharedData;
CutGenerator cutGen;
MemGenerator memGen;
CutSelector cutSelector;
bool if_once_use_no_symmetry_mem{false};
void cleanData() {
sharedData.cleanData();
cutGen.cleanData();
memGen.cleanData();
cutSelector.cleanData();
}
void setRhs(std::vector<R1c> &cuts);
};
} // namespace RouteOpt::Rank1Cuts::Separation
#endif // ROUTE_OPT_RANK1_SEPARATION_CONTROLLER_HPP
Usage Example¶
Below is an example demonstrating how to use the Rank-1 separation module in RouteOpt. This example shows how to initialize the shared data, create a solver instance, define a cost matrix, and then use the Rank1SeparationController to update information and generate new Rank-1 cuts.
#include <vector>
#include <iostream>
#include "rank1_data_shared.hpp"
#include "rank1_separation_controller.hpp"
#include "solver.hpp"
using namespace RouteOpt::Rank1Cuts;
using namespace RouteOpt::Rank1Cuts::Separation;
int main() {
// Initialize shared Rank-1 cuts data with an instance dimension (e.g., 4)
Rank1CutsDataShared sharedData(4);
// Create an instance of the IP solver
Solver solver{};
// Define a sample cost matrix for vertices (e.g., for 4 vertices)
std::vector<std::vector<double>> costMatrix = {
{0.0, 10.0, 20.0, 30.0},
{10.0, 0.0, 15.0, 25.0},
{20.0, 15.0, 0.0, 35.0},
{30.0, 25.0, 35.0, 0.0}
};
// Instantiate the Rank1SeparationController with parameters:
// - max_row_rank1: Maximum Rank-1 row dimension (e.g., 4)
// - max_num_r1c3_per_round: Maximum number of R1C3 cuts per separation round (e.g., 50)
// - max_num_r1c_per_round: Maximum number of R1C cuts (dimension != 3) per round (e.g., 100)
// This is one time call
Rank1SeparationController separationController(
sharedData,
4,
50,
100,
solver,
costMatrix
);
// Prepare dummy solution routes (RouteInfo) for the update
std::vector<RouteInfo> solutionRoutes;
RouteInfo route;
route.frac_x = 0.75;
route.col_seq = {1, 2};
route.forward_concatenate_pos = 1;
solutionRoutes.push_back(route);
// Add other routes as needed...
// Prepare the cut vector for old cuts (if not have, give empty vector)
std::vector<R1c> oldCuts;
// Update the controller with the current solution and existing cuts.
// You need to call this function each cutting round
separationController.updateInfo(
MemoryType::NODE_MEMORY, // Using node-based memory
PRICING_HARD_LEVEL::EASY, // Pricing difficulty level: EASY
true, // Collect current solution
solutionRoutes,
oldCuts
);
// Container to hold the new generated Rank-1 cuts
std::vector<R1c> newCuts;
// Perform the separation process to generate new cuts
// You need to call this function each cutting round
separationController.separateRank1Cuts(newCuts, true, true);
// Optionally, convert node-memory-based cuts to arc-memory-based cuts
separationController.convert2ArcMemory(newCuts);
// Output the number of new cuts generated (for demonstration)
std::cout << "Number of new Rank-1 cuts generated: " << newCuts.size() << std::endl;
return 0;
}
Note
The new cuts obtained may contain some of the old cuts, but definitely with larger memory set, and thus, make the these old cuts no longer tight. Therefore, it is recommended to identify the duplicated cuts in old cuts set, and remove them from the old cuts set before/after you add new cuts to the LP model.
Note
The Rank-1 separation module offers a myriad of interesting and useful features that are not fully covered in this document.
For a deeper understanding, I recommend exploring the function usages directly in the source code located under packages/application/cvrp/src
using an IDE such as CLion.
Reviewing the source code of the CVRP application related to the Rank-1 separation module will provide valuable insights into how to use these features effectively.
If you have any questions, please feel free to contact me at you.z@ufl.edu or send your question in Group Chat.
Conclusion¶
The Rank-1 separation module in RouteOpt provides a robust framework for generating, updating, and managing Rank-1 cuts. By leveraging detailed macro definitions for parameter settings and a dedicated controller for the separation process, users can effectively integrate advanced cut separation strategies into their routing solvers.