容量限制

电容式车辆路线问题 (CVRP) 是指 VRP, 携带能力有限的用户需要在不同地点自提或配送物品。 商品有数量(例如重量或体积)且车辆有 容量上限。问题在于自提还是送货上门 以最低的成本购买这些物品,但绝不会超过车辆的容量。

在以下示例中,我们假设所有项都被选取了。 如果所有商品都在配送状态中,可解决该问题的程序也同样可行: 在这种情况下,您可以想象如 停满车辆。但容量限制 在这两种情况下的实现方式相同

CVRP 示例

接下来,我们介绍一个存在容量限制的 VRP 的示例。示例 扩展了前面的 VRP 示例,并将 以下要求。每个位置都有与以下项相对应的需求: 要自提的商品的数量。此外,每辆车 容量为 15(我们不指定需求或容量的单位)。

下面的网格以蓝色显示要到访的地点,以蓝色显示公司地点 黑色。需求会显示在每个营业地点的右下角。请参阅 VRP 中的位置坐标 部分,详细了解如何定义位置。

问题在于,要找出距离最短的车辆的路线分配, 这样,车辆的总行驶距离便不会 超出其容量。

使用 OR 工具解 CVRP 示例

以下部分介绍了如何使用 OR 工具求解 CVRP 示例。

创建数据

本示例的数据包含 VRP 示例,并添加了以下内容 需求和车辆容量:

data["demands"] = [0, 1, 1, 2, 4, 2, 4, 8, 8, 1, 2, 1, 2, 4, 4, 8, 8]
data
["vehicle_capacities"] = [15, 15, 15, 15]
const std::vector<int64_t> demands{
   
0, 1, 1, 2, 4, 2, 4, 8, 8, 1, 2, 1, 2, 4, 4, 8, 8,
};
const std::vector<int64_t> vehicle_capacities{15, 15, 15, 15};
public final long[] demands = {0, 1, 1, 2, 4, 2, 4, 8, 8, 1, 2, 1, 2, 4, 4, 8, 8};
public final long[] vehicleCapacities = {15, 15, 15, 15};
public long[] Demands = { 0, 1, 1, 2, 4, 2, 4, 8, 8, 1, 2, 1, 2, 4, 4, 8, 8 };
public long[] VehicleCapacities = { 15, 15, 15, 15 };

数据中的新项包括:

  • 需求:每个地理位置都有与数量相对应的需求, 要自提的商品的重量或体积)。
  • 容量:每辆车都有容量: 当车辆沿其路线行驶时, 且携带的物品不得超过其容量。

添加距离回调

距离回调函数 - 此函数会返回任意地点之间的距离。 两个位置的定义方式与前面的 VRP 示例

添加需求回调和容量限制

除了距离回调之外,求解器还需要一个需求回调 ,返回每个地理位置的需求以及容量维度 限制条件。以下代码会创建这些对象。

def demand_callback(from_index):
   
"""Returns the demand of the node."""
   
# Convert from routing variable Index to demands NodeIndex.
    from_node
= manager.IndexToNode(from_index)
   
return data["demands"][from_node]

demand_callback_index
= routing.RegisterUnaryTransitCallback(demand_callback)
routing
.AddDimensionWithVehicleCapacity(
    demand_callback_index
,
   
0,  # null capacity slack
    data
["vehicle_capacities"],  # vehicle maximum capacities
   
True,  # start cumul to zero
   
"Capacity",
)
const int demand_callback_index = routing.RegisterUnaryTransitCallback(
   
[&data, &manager](const int64_t from_index) -> int64_t {
     
// Convert from routing variable Index to demand NodeIndex.
     
const int from_node = manager.IndexToNode(from_index).value();
     
return data.demands[from_node];
   
});
routing
.AddDimensionWithVehicleCapacity(
    demand_callback_index
,    // transit callback index
    int64_t
{0},               // null capacity slack
    data
.vehicle_capacities,  // vehicle maximum capacities
   
true,                     // start cumul to zero
   
"Capacity");
final int demandCallbackIndex = routing.registerUnaryTransitCallback((long fromIndex) -> {
 
// Convert from routing variable Index to user NodeIndex.
 
int fromNode = manager.indexToNode(fromIndex);
 
return data.demands[fromNode];
});
routing
.addDimensionWithVehicleCapacity(demandCallbackIndex, 0, // null capacity slack
    data
.vehicleCapacities, // vehicle maximum capacities
   
true, // start cumul to zero
   
"Capacity");
int demandCallbackIndex = routing.RegisterUnaryTransitCallback((long fromIndex) =>
                                                               
{
                                                                   
// Convert from routing variable Index to
                                                                   
// demand NodeIndex.
                                                                   
var fromNode =
                                                                       manager
.IndexToNode(fromIndex);
                                                                   
return data.Demands[fromNode];
                                                               
});
routing
.AddDimensionWithVehicleCapacity(demandCallbackIndex, 0, // null capacity slack
                                        data
.VehicleCapacities, // vehicle maximum capacities
                                       
true,                   // start cumul to zero
                                       
"Capacity");

与接受一对位置作为输入的距离回调不同, 需求回调仅取决于广告投放的位置 (from_node)。

由于载客量限制涉及车辆的重量 即运输过程中累积的物品数量,因此我们需要 为容量创建维度, 与上一文本中的距离维度 VRP 示例

在本示例中,我们使用 AddDimensionWithVehicleCapacity 方法,该方法接受容量矢量。

由于此示例中的所有车辆容量都相同,因此您可以使用 AddDimension 方法,该方法对所有车辆数量都采用一个上限。但是 AddDimensionWithVehicleCapacity 会处理更常见的情况, 不同车辆的容量各异

与多种货物类型和容量相关的问题

在更复杂的 CVRP 中,每辆车可能搭载几种不同类型的货物 ,每种类型都有最大容量。 例如,一辆燃油卡车可能搭载多种类型的燃料, 多个容量不同的水箱。要处理此类问题 为每种货物类型创建不同的容量回调和尺寸 请务必为其指定唯一名称)。

添加解决方案打印机

解决方案打印机显示每辆车的路线及其 累计加载大小:车辆停靠在另一个车站上的总负荷 路由。

def print_solution(data, manager, routing, solution):
   
"""Prints solution on console."""
   
print(f"Objective: {solution.ObjectiveValue()}")
    total_distance
= 0
    total_load
= 0
   
for vehicle_id in range(data["num_vehicles"]):
        index
= routing.Start(vehicle_id)
        plan_output
= f"Route for vehicle {vehicle_id}:\n"
        route_distance
= 0
        route_load
= 0
       
while not routing.IsEnd(index):
            node_index
= manager.IndexToNode(index)
            route_load
+= data["demands"][node_index]
            plan_output
+= f" {node_index} Load({route_load}) -> "
            previous_index
= index
            index
= solution.Value(routing.NextVar(index))
            route_distance
+= routing.GetArcCostForVehicle(
                previous_index
, index, vehicle_id
           
)
        plan_output
+= f" {manager.IndexToNode(index)} Load({route_load})\n"
        plan_output
+= f"Distance of the route: {route_distance}m\n"
        plan_output
+= f"Load of the route: {route_load}\n"
       
print(plan_output)
        total_distance
+= route_distance
        total_load
+= route_load
   
print(f"Total distance of all routes: {total_distance}m")
   
print(f"Total load of all routes: {total_load}")
//! @brief Print the solution.
//! @param[in] data Data of the problem.
//! @param[in] manager Index manager used.
//! @param[in] routing Routing solver used.
//! @param[in] solution Solution found by the solver.
void PrintSolution(const DataModel& data, const RoutingIndexManager& manager,
                   
const RoutingModel& routing, const Assignment& solution) {
  int64_t total_distance
= 0;
  int64_t total_load
= 0;
 
for (int vehicle_id = 0; vehicle_id < data.num_vehicles; ++vehicle_id) {
    int64_t index
= routing.Start(vehicle_id);
    LOG
(INFO) << "Route for Vehicle " << vehicle_id << ":";
    int64_t route_distance
= 0;
    int64_t route_load
= 0;
    std
::stringstream route;
   
while (!routing.IsEnd(index)) {
     
const int node_index = manager.IndexToNode(index).value();
      route_load
+= data.demands[node_index];
      route
<< node_index << " Load(" << route_load << ") -> ";
     
const int64_t previous_index = index;
      index
= solution.Value(routing.NextVar(index));
      route_distance
+= routing.GetArcCostForVehicle(previous_index, index,
                                                     int64_t
{vehicle_id});
   
}
    LOG
(INFO) << route.str() << manager.IndexToNode(index).value();
    LOG
(INFO) << "Distance of the route: " << route_distance << "m";
    LOG
(INFO) << "Load of the route: " << route_load;
    total_distance
+= route_distance;
    total_load
+= route_load;
 
}
  LOG
(INFO) << "Total distance of all routes: " << total_distance << "m";
  LOG
(INFO) << "Total load of all routes: " << total_load;
  LOG
(INFO) << "";
  LOG
(INFO) << "Advanced usage:";
  LOG
(INFO) << "Problem solved in " << routing.solver()->wall_time() << "ms";
}
/// @brief Print the solution.
static void printSolution(
   
DataModel data, RoutingModel routing, RoutingIndexManager manager, Assignment solution) {
 
// Solution cost.
  logger
.info("Objective: " + solution.objectiveValue());
 
// Inspect solution.
 
long totalDistance = 0;
 
long totalLoad = 0;
 
for (int i = 0; i < data.vehicleNumber; ++i) {
   
long index = routing.start(i);
    logger
.info("Route for Vehicle " + i + ":");
   
long routeDistance = 0;
   
long routeLoad = 0;
   
String route = "";
   
while (!routing.isEnd(index)) {
     
long nodeIndex = manager.indexToNode(index);
      routeLoad
+= data.demands[(int) nodeIndex];
      route
+= nodeIndex + " Load(" + routeLoad + ") -> ";
     
long previousIndex = index;
      index
= solution.value(routing.nextVar(index));
      routeDistance
+= routing.getArcCostForVehicle(previousIndex, index, i);
   
}
    route
+= manager.indexToNode(routing.end(i));
    logger
.info(route);
    logger
.info("Distance of the route: " + routeDistance + "m");
    totalDistance
+= routeDistance;
    totalLoad
+= routeLoad;
 
}
  logger
.info("Total distance of all routes: " + totalDistance + "m");
  logger
.info("Total load of all routes: " + totalLoad);
}
/// <summary>
///   Print the solution.
/// </summary>
static void PrintSolution(in DataModel data, in RoutingModel routing, in RoutingIndexManager manager,
                         
in Assignment solution)
{
   
Console.WriteLine($"Objective {solution.ObjectiveValue()}:");

   
// Inspect solution.
   
long totalDistance = 0;
   
long totalLoad = 0;
   
for (int i = 0; i < data.VehicleNumber; ++i)
   
{
       
Console.WriteLine("Route for Vehicle {0}:", i);
       
long routeDistance = 0;
       
long routeLoad = 0;
       
var index = routing.Start(i);
       
while (routing.IsEnd(index) == false)
       
{
           
long nodeIndex = manager.IndexToNode(index);
            routeLoad
+= data.Demands[nodeIndex];
           
Console.Write("{0} Load({1}) -> ", nodeIndex, routeLoad);
           
var previousIndex = index;
            index
= solution.Value(routing.NextVar(index));
            routeDistance
+= routing.GetArcCostForVehicle(previousIndex, index, 0);
       
}
       
Console.WriteLine("{0}", manager.IndexToNode((int)index));
       
Console.WriteLine("Distance of the route: {0}m", routeDistance);
        totalDistance
+= routeDistance;
        totalLoad
+= routeLoad;
   
}
   
Console.WriteLine("Total distance of all routes: {0}m", totalDistance);
   
Console.WriteLine("Total load of all routes: {0}m", totalLoad);
}

主函数

本例中的 main 函数与 TSP 示例,还会添加 上述需求和容量维度

运行程序

完整的程序将在下一部分中显示。 运行该程序后,它会显示以下输出:

Objective: 6208
Route for vehicle 0:
 0 Load(0) ->  4 Load(0) ->  3 Load(4) ->  1 Load(6) ->  7 Load(7) ->  0 Load(15)
Distance of the route: 1552m
Load of the route: 15

Route for vehicle 1:
 0 Load(0) ->  14 Load(0) ->  16 Load(4) ->  10 Load(12) ->  9 Load(14) ->  0 Load(15)
Distance of the route: 1552m
Load of the route: 15

Route for vehicle 2:
 0 Load(0) ->  12 Load(0) ->  11 Load(2) ->  15 Load(3) ->  13 Load(11) ->  0 Load(15)
Distance of the route: 1552m
Load of the route: 15

Route for vehicle 3:
 0 Load(0) ->  8 Load(0) ->  2 Load(8) ->  6 Load(9) ->  5 Load(13) ->  0 Load(15)
Distance of the route: 1552m
Load of the route: 15

Total Distance of all routes: 6208m
Total Load of all routes: 60

对于路线上的每个位置,输出结果会显示:

  • 营业地点的索引。
  • 车辆离开相应位置时承载的总负载。

  • 路由如下所示。

完成计划

下面显示了用于解决电容式车辆路线问题的完整程序。

"""Capacited Vehicles Routing Problem (CVRP)."""

from ortools.constraint_solver import routing_enums_pb2
from ortools.constraint_solver import pywrapcp


def create_data_model():
   
"""Stores the data for the problem."""
    data
= {}
    data
["distance_matrix"] = [
       
# fmt: off
     
[0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354, 468, 776, 662],
     
[548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674, 1016, 868, 1210],
     
[776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164, 1130, 788, 1552, 754],
     
[696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822, 1164, 560, 1358],
     
[582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708, 1050, 674, 1244],
     
[274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628, 514, 1050, 708],
     
[502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856, 514, 1278, 480],
     
[194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320, 662, 742, 856],
     
[308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662, 320, 1084, 514],
     
[194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388, 274, 810, 468],
     
[536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764, 730, 388, 1152, 354],
     
[502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114, 308, 650, 274, 844],
     
[388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194, 536, 388, 730],
     
[354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0, 342, 422, 536],
     
[468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536, 342, 0, 764, 194],
     
[776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274, 388, 422, 764, 0, 798],
     
[662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730, 536, 194, 798, 0],
       
# fmt: on
   
]
    data
["demands"] = [0, 1, 1, 2, 4, 2, 4, 8, 8, 1, 2, 1, 2, 4, 4, 8, 8]
    data
["vehicle_capacities"] = [15, 15, 15, 15]
    data
["num_vehicles"] = 4
    data
["depot"] = 0
   
return data


def print_solution(data, manager, routing, solution):
   
"""Prints solution on console."""
   
print(f"Objective: {solution.ObjectiveValue()}")
    total_distance
= 0
    total_load
= 0
   
for vehicle_id in range(data["num_vehicles"]):
        index
= routing.Start(vehicle_id)
        plan_output
= f"Route for vehicle {vehicle_id}:\n"
        route_distance
= 0
        route_load
= 0
       
while not routing.IsEnd(index):
            node_index
= manager.IndexToNode(index)
            route_load
+= data["demands"][node_index]
            plan_output
+= f" {node_index} Load({route_load}) -> "
            previous_index
= index
            index
= solution.Value(routing.NextVar(index))
            route_distance
+= routing.GetArcCostForVehicle(
                previous_index
, index, vehicle_id
           
)
        plan_output
+= f" {manager.IndexToNode(index)} Load({route_load})\n"
        plan_output
+= f"Distance of the route: {route_distance}m\n"
        plan_output
+= f"Load of the route: {route_load}\n"
       
print(plan_output)
        total_distance
+= route_distance
        total_load
+= route_load
   
print(f"Total distance of all routes: {total_distance}m")
   
print(f"Total load of all routes: {total_load}")


def main():
   
"""Solve the CVRP problem."""
   
# Instantiate the data problem.
    data
= create_data_model()

   
# Create the routing index manager.
    manager
= pywrapcp.RoutingIndexManager(
        len
(data["distance_matrix"]), data["num_vehicles"], data["depot"]
   
)

   
# Create Routing Model.
    routing
= pywrapcp.RoutingModel(manager)

   
# Create and register a transit callback.
   
def distance_callback(from_index, to_index):
       
"""Returns the distance between the two nodes."""
       
# Convert from routing variable Index to distance matrix NodeIndex.
        from_node
= manager.IndexToNode(from_index)
        to_node
= manager.IndexToNode(to_index)
       
return data["distance_matrix"][from_node][to_node]

    transit_callback_index
= routing.RegisterTransitCallback(distance_callback)

   
# Define cost of each arc.
    routing
.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)

   
# Add Capacity constraint.
   
def demand_callback(from_index):
       
"""Returns the demand of the node."""
       
# Convert from routing variable Index to demands NodeIndex.
        from_node
= manager.IndexToNode(from_index)
       
return data["demands"][from_node]

    demand_callback_index
= routing.RegisterUnaryTransitCallback(demand_callback)
    routing
.AddDimensionWithVehicleCapacity(
        demand_callback_index
,
       
0,  # null capacity slack
        data
["vehicle_capacities"],  # vehicle maximum capacities
       
True,  # start cumul to zero
       
"Capacity",
   
)

   
# Setting first solution heuristic.
    search_parameters
= pywrapcp.DefaultRoutingSearchParameters()
    search_parameters
.first_solution_strategy = (
        routing_enums_pb2
.FirstSolutionStrategy.PATH_CHEAPEST_ARC
   
)
    search_parameters
.local_search_metaheuristic = (
        routing_enums_pb2
.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH
   
)
    search_parameters
.time_limit.FromSeconds(1)

   
# Solve the problem.
    solution
= routing.SolveWithParameters(search_parameters)

   
# Print solution on console.
   
if solution:
        print_solution
(data, manager, routing, solution)


if __name__ == "__main__":
    main
()
#include <cstdint>
#include <sstream>
#include <vector>

#include "google/protobuf/duration.pb.h"
#include "ortools/constraint_solver/routing.h"
#include "ortools/constraint_solver/routing_enums.pb.h"
#include "ortools/constraint_solver/routing_index_manager.h"
#include "ortools/constraint_solver/routing_parameters.h"

namespace operations_research {
struct DataModel {
 
const std::vector<std::vector<int64_t>> distance_matrix{
     
{0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354, 468,
       
776, 662},
     
{548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674,
       
1016, 868, 1210},
     
{776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164, 1130,
       
788, 1552, 754},
     
{696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822,
       
1164, 560, 1358},
     
{582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708,
       
1050, 674, 1244},
     
{274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628, 514,
       
1050, 708},
     
{502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856, 514,
       
1278, 480},
     
{194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320, 662,
       
742, 856},
     
{308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662, 320,
       
1084, 514},
     
{194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388, 274,
       
810, 468},
     
{536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764, 730,
       
388, 1152, 354},
     
{502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114, 308,
       
650, 274, 844},
     
{388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194, 536,
       
388, 730},
     
{354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0, 342,
       
422, 536},
     
{468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536, 342,
       
0, 764, 194},
     
{776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274, 388,
       
422, 764, 0, 798},
     
{662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730, 536,
       
194, 798, 0},
 
};
 
const std::vector<int64_t> demands{
     
0, 1, 1, 2, 4, 2, 4, 8, 8, 1, 2, 1, 2, 4, 4, 8, 8,
 
};
 
const std::vector<int64_t> vehicle_capacities{15, 15, 15, 15};
 
const int num_vehicles = 4;
 
const RoutingIndexManager::NodeIndex depot{0};
};

//! @brief Print the solution.
//! @param[in] data Data of the problem.
//! @param[in] manager Index manager used.
//! @param[in] routing Routing solver used.
//! @param[in] solution Solution found by the solver.
void PrintSolution(const DataModel& data, const RoutingIndexManager& manager,
                   
const RoutingModel& routing, const Assignment& solution) {
  int64_t total_distance
= 0;
  int64_t total_load
= 0;
 
for (int vehicle_id = 0; vehicle_id < data.num_vehicles; ++vehicle_id) {
    int64_t index
= routing.Start(vehicle_id);
    LOG
(INFO) << "Route for Vehicle " << vehicle_id << ":";
    int64_t route_distance
= 0;
    int64_t route_load
= 0;
    std
::stringstream route;
   
while (!routing.IsEnd(index)) {
     
const int node_index = manager.IndexToNode(index).value();
      route_load
+= data.demands[node_index];
      route
<< node_index << " Load(" << route_load << ") -> ";
     
const int64_t previous_index = index;
      index
= solution.Value(routing.NextVar(index));
      route_distance
+= routing.GetArcCostForVehicle(previous_index, index,
                                                     int64_t
{vehicle_id});
   
}
    LOG
(INFO) << route.str() << manager.IndexToNode(index).value();
    LOG
(INFO) << "Distance of the route: " << route_distance << "m";
    LOG
(INFO) << "Load of the route: " << route_load;
    total_distance
+= route_distance;
    total_load
+= route_load;
 
}
  LOG
(INFO) << "Total distance of all routes: " << total_distance << "m";
  LOG
(INFO) << "Total load of all routes: " << total_load;
  LOG
(INFO) << "";
  LOG
(INFO) << "Advanced usage:";
  LOG
(INFO) << "Problem solved in " << routing.solver()->wall_time() << "ms";
}

void VrpCapacity() {
 
// Instantiate the data problem.
 
DataModel data;

 
// Create Routing Index Manager
 
RoutingIndexManager manager(data.distance_matrix.size(), data.num_vehicles,
                              data
.depot);

 
// Create Routing Model.
 
RoutingModel routing(manager);

 
// Create and register a transit callback.
 
const int transit_callback_index = routing.RegisterTransitCallback(
     
[&data, &manager](const int64_t from_index,
                       
const int64_t to_index) -> int64_t {
       
// Convert from routing variable Index to distance matrix NodeIndex.
       
const int from_node = manager.IndexToNode(from_index).value();
       
const int to_node = manager.IndexToNode(to_index).value();
       
return data.distance_matrix[from_node][to_node];
     
});

 
// Define cost of each arc.
  routing
.SetArcCostEvaluatorOfAllVehicles(transit_callback_index);

 
// Add Capacity constraint.
 
const int demand_callback_index = routing.RegisterUnaryTransitCallback(
     
[&data, &manager](const int64_t from_index) -> int64_t {
       
// Convert from routing variable Index to demand NodeIndex.
       
const int from_node = manager.IndexToNode(from_index).value();
       
return data.demands[from_node];
     
});
  routing
.AddDimensionWithVehicleCapacity(
      demand_callback_index
,    // transit callback index
      int64_t
{0},               // null capacity slack
      data
.vehicle_capacities,  // vehicle maximum capacities
     
true,                     // start cumul to zero
     
"Capacity");

 
// Setting first solution heuristic.
 
RoutingSearchParameters search_parameters = DefaultRoutingSearchParameters();
  search_parameters
.set_first_solution_strategy(
     
FirstSolutionStrategy::PATH_CHEAPEST_ARC);
  search_parameters
.set_local_search_metaheuristic(
     
LocalSearchMetaheuristic::GUIDED_LOCAL_SEARCH);
  search_parameters
.mutable_time_limit()->set_seconds(1);

 
// Solve the problem.
 
const Assignment* solution = routing.SolveWithParameters(search_parameters);

 
// Print solution on console.
 
PrintSolution(data, manager, routing, *solution);
}
}  // namespace operations_research

int main(int /*argc*/, char* /*argv*/[]) {
  operations_research
::VrpCapacity();
 
return EXIT_SUCCESS;
}
package com.google.ortools.constraintsolver.samples;
import com.google.ortools.Loader;
import com.google.ortools.constraintsolver.Assignment;
import com.google.ortools.constraintsolver.FirstSolutionStrategy;
import com.google.ortools.constraintsolver.LocalSearchMetaheuristic;
import com.google.ortools.constraintsolver.RoutingIndexManager;
import com.google.ortools.constraintsolver.RoutingModel;
import com.google.ortools.constraintsolver.RoutingSearchParameters;
import com.google.ortools.constraintsolver.main;
import com.google.protobuf.Duration;
import java.util.logging.Logger;

/** Minimal VRP. */
public final class VrpCapacity {
 
private static final Logger logger = Logger.getLogger(VrpCapacity.class.getName());

 
static class DataModel {
   
public final long[][] distanceMatrix = {
       
{0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354, 468, 776, 662},
       
{548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674, 1016, 868, 1210},
       
{776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164, 1130, 788, 1552, 754},
       
{696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822, 1164, 560, 1358},
       
{582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708, 1050, 674, 1244},
       
{274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628, 514, 1050, 708},
       
{502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856, 514, 1278, 480},
       
{194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320, 662, 742, 856},
       
{308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662, 320, 1084, 514},
       
{194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388, 274, 810, 468},
       
{536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764, 730, 388, 1152, 354},
       
{502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114, 308, 650, 274, 844},
       
{388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194, 536, 388, 730},
       
{354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0, 342, 422, 536},
       
{468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536, 342, 0, 764, 194},
       
{776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274, 388, 422, 764, 0, 798},
       
{662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730, 536, 194, 798, 0},
   
};
   
public final long[] demands = {0, 1, 1, 2, 4, 2, 4, 8, 8, 1, 2, 1, 2, 4, 4, 8, 8};
   
public final long[] vehicleCapacities = {15, 15, 15, 15};
   
public final int vehicleNumber = 4;
   
public final int depot = 0;
 
}

 
/// @brief Print the solution.
 
static void printSolution(
     
DataModel data, RoutingModel routing, RoutingIndexManager manager, Assignment solution) {
   
// Solution cost.
    logger
.info("Objective: " + solution.objectiveValue());
   
// Inspect solution.
   
long totalDistance = 0;
   
long totalLoad = 0;
   
for (int i = 0; i < data.vehicleNumber; ++i) {
     
long index = routing.start(i);
      logger
.info("Route for Vehicle " + i + ":");
     
long routeDistance = 0;
     
long routeLoad = 0;
     
String route = "";
     
while (!routing.isEnd(index)) {
       
long nodeIndex = manager.indexToNode(index);
        routeLoad
+= data.demands[(int) nodeIndex];
        route
+= nodeIndex + " Load(" + routeLoad + ") -> ";
       
long previousIndex = index;
        index
= solution.value(routing.nextVar(index));
        routeDistance
+= routing.getArcCostForVehicle(previousIndex, index, i);
     
}
      route
+= manager.indexToNode(routing.end(i));
      logger
.info(route);
      logger
.info("Distance of the route: " + routeDistance + "m");
      totalDistance
+= routeDistance;
      totalLoad
+= routeLoad;
   
}
    logger
.info("Total distance of all routes: " + totalDistance + "m");
    logger
.info("Total load of all routes: " + totalLoad);
 
}

 
public static void main(String[] args) throws Exception {
   
Loader.loadNativeLibraries();
   
// Instantiate the data problem.
   
final DataModel data = new DataModel();

   
// Create Routing Index Manager
   
RoutingIndexManager manager =
       
new RoutingIndexManager(data.distanceMatrix.length, data.vehicleNumber, data.depot);

   
// Create Routing Model.
   
RoutingModel routing = new RoutingModel(manager);

   
// Create and register a transit callback.
   
final int transitCallbackIndex =
        routing
.registerTransitCallback((long fromIndex, long toIndex) -> {
         
// Convert from routing variable Index to user NodeIndex.
         
int fromNode = manager.indexToNode(fromIndex);
         
int toNode = manager.indexToNode(toIndex);
         
return data.distanceMatrix[fromNode][toNode];
       
});

   
// Define cost of each arc.
    routing
.setArcCostEvaluatorOfAllVehicles(transitCallbackIndex);

   
// Add Capacity constraint.
   
final int demandCallbackIndex = routing.registerUnaryTransitCallback((long fromIndex) -> {
     
// Convert from routing variable Index to user NodeIndex.
     
int fromNode = manager.indexToNode(fromIndex);
     
return data.demands[fromNode];
   
});
    routing
.addDimensionWithVehicleCapacity(demandCallbackIndex, 0, // null capacity slack
        data
.vehicleCapacities, // vehicle maximum capacities
       
true, // start cumul to zero
       
"Capacity");

   
// Setting first solution heuristic.
   
RoutingSearchParameters searchParameters =
        main
.defaultRoutingSearchParameters()
           
.toBuilder()
           
.setFirstSolutionStrategy(FirstSolutionStrategy.Value.PATH_CHEAPEST_ARC)
           
.setLocalSearchMetaheuristic(LocalSearchMetaheuristic.Value.GUIDED_LOCAL_SEARCH)
           
.setTimeLimit(Duration.newBuilder().setSeconds(1).build())
           
.build();

   
// Solve the problem.
   
Assignment solution = routing.solveWithParameters(searchParameters);

   
// Print solution on console.
    printSolution
(data, routing, manager, solution);
 
}

 
private VrpCapacity() {}
}
using System;
using System.Collections.Generic;
using Google.OrTools.ConstraintSolver;
using Google.Protobuf.WellKnownTypes; // Duration

/// <summary>
///   Minimal TSP using distance matrix.
/// </summary>
public class VrpCapacity
{
   
class DataModel
   
{
       
public long[,] DistanceMatrix = {
           
{ 0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354, 468, 776, 662 },
           
{ 548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674, 1016, 868, 1210 },
           
{ 776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164, 1130, 788, 1552, 754 },
           
{ 696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822, 1164, 560, 1358 },
           
{ 582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708, 1050, 674, 1244 },
           
{ 274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628, 514, 1050, 708 },
           
{ 502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856, 514, 1278, 480 },
           
{ 194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320, 662, 742, 856 },
           
{ 308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662, 320, 1084, 514 },
           
{ 194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388, 274, 810, 468 },
           
{ 536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764, 730, 388, 1152, 354 },
           
{ 502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114, 308, 650, 274, 844 },
           
{ 388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194, 536, 388, 730 },
           
{ 354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0, 342, 422, 536 },
           
{ 468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536, 342, 0, 764, 194 },
           
{ 776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274, 388, 422, 764, 0, 798 },
           
{ 662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730, 536, 194, 798, 0 }
       
};
       
public long[] Demands = { 0, 1, 1, 2, 4, 2, 4, 8, 8, 1, 2, 1, 2, 4, 4, 8, 8 };
       
public long[] VehicleCapacities = { 15, 15, 15, 15 };
       
public int VehicleNumber = 4;
       
public int Depot = 0;
   
};

   
/// <summary>
   
///   Print the solution.
   
/// </summary>
   
static void PrintSolution(in DataModel data, in RoutingModel routing, in RoutingIndexManager manager,
                             
in Assignment solution)
   
{
       
Console.WriteLine($"Objective {solution.ObjectiveValue()}:");

       
// Inspect solution.
       
long totalDistance = 0;
       
long totalLoad = 0;
       
for (int i = 0; i < data.VehicleNumber; ++i)
       
{
           
Console.WriteLine("Route for Vehicle {0}:", i);
           
long routeDistance = 0;
           
long routeLoad = 0;
           
var index = routing.Start(i);
           
while (routing.IsEnd(index) == false)
           
{
               
long nodeIndex = manager.IndexToNode(index);
                routeLoad
+= data.Demands[nodeIndex];
               
Console.Write("{0} Load({1}) -> ", nodeIndex, routeLoad);
               
var previousIndex = index;
                index
= solution.Value(routing.NextVar(index));
                routeDistance
+= routing.GetArcCostForVehicle(previousIndex, index, 0);
           
}
           
Console.WriteLine("{0}", manager.IndexToNode((int)index));
           
Console.WriteLine("Distance of the route: {0}m", routeDistance);
            totalDistance
+= routeDistance;
            totalLoad
+= routeLoad;
       
}
       
Console.WriteLine("Total distance of all routes: {0}m", totalDistance);
       
Console.WriteLine("Total load of all routes: {0}m", totalLoad);
   
}

   
public static void Main(String[] args)
   
{
       
// Instantiate the data problem.
       
DataModel data = new DataModel();

       
// Create Routing Index Manager
       
RoutingIndexManager manager =
           
new RoutingIndexManager(data.DistanceMatrix.GetLength(0), data.VehicleNumber, data.Depot);

       
// Create Routing Model.
       
RoutingModel routing = new RoutingModel(manager);

       
// Create and register a transit callback.
       
int transitCallbackIndex = routing.RegisterTransitCallback((long fromIndex, long toIndex) =>
                                                                   
{
                                                                       
// Convert from routing variable Index to
                                                                       
// distance matrix NodeIndex.
                                                                       
var fromNode = manager.IndexToNode(fromIndex);
                                                                       
var toNode = manager.IndexToNode(toIndex);
                                                                       
return data.DistanceMatrix[fromNode, toNode];
                                                                   
});

       
// Define cost of each arc.
        routing
.SetArcCostEvaluatorOfAllVehicles(transitCallbackIndex);

       
// Add Capacity constraint.
       
int demandCallbackIndex = routing.RegisterUnaryTransitCallback((long fromIndex) =>
                                                                       
{
                                                                           
// Convert from routing variable Index to
                                                                           
// demand NodeIndex.
                                                                           
var fromNode =
                                                                               manager
.IndexToNode(fromIndex);
                                                                           
return data.Demands[fromNode];
                                                                       
});
        routing
.AddDimensionWithVehicleCapacity(demandCallbackIndex, 0, // null capacity slack
                                                data
.VehicleCapacities, // vehicle maximum capacities
                                               
true,                   // start cumul to zero
                                               
"Capacity");

       
// Setting first solution heuristic.
       
RoutingSearchParameters searchParameters =
            operations_research_constraint_solver
.DefaultRoutingSearchParameters();
        searchParameters
.FirstSolutionStrategy = FirstSolutionStrategy.Types.Value.PathCheapestArc;
        searchParameters
.LocalSearchMetaheuristic = LocalSearchMetaheuristic.Types.Value.GuidedLocalSearch;
        searchParameters
.TimeLimit = new Duration { Seconds = 1 };

       
// Solve the problem.
       
Assignment solution = routing.SolveWithParameters(searchParameters);

       
// Print solution on console.
       
PrintSolution(data, routing, manager, solution);
   
}
}

下面列举了几个具有其他类型的车辆路线问题的 GitHub 上的限制 (请查找名称中包含“vrp”的示例)。

如果一个问题没有解决方案,该怎么办?

存在约束条件的路由问题(例如 CVRP)可能不可行 例如,如果商品的总数量 超过了车辆的总容量。如果您试图解决 求解器可能会执行详尽的搜索,这需要很长时间, 最终您必须放弃并中断程序。

这通常不是问题。不过,您可以通过以下几种方式 在问题无解决方法的情况下,让程序长时间运行:

  • 在以下位置设置时间限制: 程序,这样即使未找到解决方案,也会停止搜索。不过, 请记住,如果问题有个需要漫长搜索的解决方案, 程序就可能在达到时间限制后才找到解决方案。
  • 设置针对丢弃前往营业地点的处罚。如此一来,求解器便可 返回“解决方案”这样就不会访问所有营业地点 不可行。请参阅处罚和删除访问次数

一般来说,很难判断某个给定的问题是否有解决方案。即便是 总需求不超过总容量的 CVRP,用于确定 都能装在车里的所有物品都是 多背包问题