Ortools是开源求解器,与gurobi的一个区别在于ortools给出了实际的场景,使用不同的模型和接口可以直接调用来解决特定场景的类型,比如
线性优化
整数优化
约束优化
分配问题
路径问题
装箱问题
网络流
排班问题
车辆路径规划
车辆路径规划是为了从一系列地点中找到最优路径;通常,最优意味着最少的距离和成本
最经典的车辆路径问题是旅行商问题(TSP),即一个销售员为了拜访客户从起点无重复的拜访一系列的地点最后返回原点的问题
在TSP问题中,通常是NP hard问题,因此在计算此类问题时,如果过于追求最优解,往往会耗费几个小时甚至几十个小时去搜索最优解,故为了平衡时间和最优解,除了在solver上对求解方法进行优化,还将设置求解的时间,以便在有限的时间内,找到一个相对较优解
旅行商问题本质上属于车辆路径问题,但区别在于旅行商问题只关系到一个车从起点出发,并拜访各个点后最后回到原点;而车辆路径问题通常会包含多辆车在不同约束条件下进行串线
在车辆路径问题上,除了ortools可以进行求解外,还有其他求解器,如Concorde在求解车辆路径问题上有更好的求解效果
Ortools求解车辆路径规划的场景类型
旅行商问题
车辆路径问题
有载重约束的车辆路径问题
有时间窗的车辆路径问题
有资源约束的车辆路径问题
有放弃参观点的车辆约束问题
例:旅行商问题
def create_data_model():
"""Stores the data for the problem."""
data = {}
data['distance_matrix'] = [
[0, 2451, 713, 1018, 1631, 1374, 2408, 213, 2571, 875, 1420, 2145, 1972],
[2451, 0, 1745, 1524, 831, 1240, 959, 2596, 403, 1589, 1374, 357, 579],
[713, 1745, 0, 355, 920, 803, 1737, 851, 1858, 262, 940, 1453, 1260],
[1018, 1524, 355, 0, 700, 862, 1395, 1123, 1584, 466, 1056, 1280, 987],
[1631, 831, 920, 700, 0, 663, 1021, 1769, 949, 796, 879, 586, 371],
[1374, 1240, 803, 862, 663, 0, 1681, 1551, 1765, 547, 225, 887, 999],
[2408, 959, 1737, 1395, 1021, 1681, 0, 2493, 678, 1724, 1891, 1114, 701],
[213, 2596, 851, 1123, 1769, 1551, 2493, 0, 2699, 1038, 1605, 2300, 2099],
[2571, 403, 1858, 1584, 949, 1765, 678, 2699, 0, 1744, 1645, 653, 600],
[875, 1589, 262, 466, 796, 547, 1724, 1038, 1744, 0, 679, 1272, 1162],
[1420, 1374, 940, 1056, 879, 225, 1891, 1605, 1645, 679, 0, 1017, 1200],
[2145, 357, 1453, 1280, 586, 887, 1114, 2300, 653, 1272, 1017, 0, 504],
[1972, 579, 1260, 987, 371, 999, 701, 2099, 600, 1162, 1200, 504, 0],
] # yapf: disable
data['num_vehicles'] = 1
data['depot'] = 0
return data
旅行商问题通常需要创建一个距离矩阵(distance matrix),其中(i,j)分别代表该矩阵对应的index pairs;i通常表示各个起始点的index,j表示目标点的index,而(i,j)的index pair所对应的值通常表示从i点出发到j点的距离。
注:距离矩阵中各个起始点的顺序可以是任意的
另外,在初始数据中,如果限制车辆数为1,则是旅行商问题,如果多于1,则是车辆路径优化问题。
Depot:表示车辆的起始点和终点,如果depot是0,表示距离矩阵中的起始索引i=0所对应的位置点
创建线路模型
这里会创建index manager和routing模型,其中manager.IndexToNode会将求解器内部的index对应到各个位置
data = create_data_model()
manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),
data['num_vehicles'], data['depot'])
routing = pywrapcp.RoutingModel(manager)
输入项RoutingIndexManager包含
距离矩阵的行数,表示位置的数量,包含depot
问题中所包含的车辆数
起始点所对应的index
创建距离调用函数
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)
该调用函数接受两个index,from_index和to_index,并且返回距离矩阵中对应的值
设置旅行成本项
由于目标是计算旅行成本最小,且该成本和每条线路上的arc最短,故这里会将其转换为arc成本求解器,且该旅行成本项默认是求解最小化问题
routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)
在该例子中,arc成本求解器是求解transit_callback_index最小,即求解器求解距离矩阵调用函数最短。另外,通常情况下,这个成本项也可以包含其他成本因素使得总成本最小。
最后,我们也可以用routing.SetArcCostEvaluatorOfVehicle()定义多个arc成本求解器。
设置参数
search_parameters = pywrapcp.DefaultRoutingSearchParameters()
search_parameters.first_solution_strategy = (
routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)
上述代码可以设置求解器的参数,通过修改不同的参数项来改变求解器求解问题的方式和方法;如上述的PATH_CHEAPEST_ARC表示通过在第一个edge中在不会走重复的edge下进而不断嵌入最小权重的edge来找到该问题的初始路径,之后算法基于这个初始路径再不断的进行优化和迭代。
注:这些参数方法都可以进行调整,常用的参数定义主要包含
GUIDED_LOCAL_SEARCH:在求解可能陷入局部最优时,跳出局部最优解继续搜索全局最优
PARALLEL_CHEAPEST_INSERTION:在最便宜的节点中插入最便宜的位置来构建解决方案,该插入成本基于arc成本函数,且比BEST_INSERTION插入速度要快
定义方案的结果打印
def print_solution(manager, routing, solution):
"""Prints solution on console."""
print('Objective: {} miles'.format(solution.ObjectiveValue()))
index = routing.Start(0)
plan_output = 'Route for vehicle 0:\n'
route_distance = 0
while not routing.IsEnd(index):
plan_output += ' {} ->'.format(manager.IndexToNode(index))
previous_index = index
index = solution.Value(routing.NextVar(index))
route_distance += routing.GetArcCostForVehicle(previous_index, index, 0)
plan_output += ' {}\n'.format(manager.IndexToNode(index))
print(plan_output)
plan_output += 'Route distance: {}miles\n'.format(route_distance)
求解并打印出求解方案
solution = routing.SolveWithParameters(search_parameters)
if solution:
print_solution(manager, routing, solution)
其他补充:
由于旅行商问题是车辆路径最简单的问题,在更复杂的问题中,我们可以通过创建dimensions来定制化的构建约束条件
在例子中depot构建的是单辆车起始点和终点位置,我们也可以通过构建depot列表来定制化创建多辆车的不同起始点和终点的位置
上述例子中创建的是不同点的距离矩阵,我们也可以通过各点的坐标并结合不同的距离方法,如欧几里得距离等来计算出来该距离矩阵
在VRP问题中,我们还可以通过创建一个虚拟的起始点和虚拟的终点,来计算从虚拟起始点出发到虚拟终点结束的多辆车串点线路,最后剔除该虚拟起始点和虚拟终点,这样计算出来的最优线路就默认是从任意起点出发且到任意终点结束的最优路径规划
如前所述,ortools定义了多种线路规划的场景并且有很多的code范例,详细内容参见https://developers.google.com/optimization/routing/penalties