ortools系列:路由问题1

1. 路由问题

运筹学中最有趣的领域之一是路由,其目标是找到通过复杂网络传输物品的有效路径。网络通常用如下图所示的图来表示。

每个节点表示一个位置,路由是通过一组节点的路径。大多数实际的路由问题都涉及到为车辆(汽车、火车、飞机等等)寻找有效的路径,因此它们通常被称为车辆路由问题。

路由问题可以分为两种主要类型:节点路由问题和弧线路由问题,这取决于目标是以访问节点(位置)还是弧线(连接它们的边)的形式表示的。我们将给出每个例子。

首先,这里有一个弧线路由问题,如果你看过谷歌地图街景,你可能想知道谷歌是如何在全世界数百万个地址获得街道级别的图像的。答案很简单:谷歌的一个团队驾驶着一队装备有自动拍摄每个地址照片的摄像头的车队,持续行驶在世界各地的道路上。谷歌的问题是为每辆在指定区域内穿越每条街道的车辆构造最短的路线。如果你用一个图来表示这个问题,其中弧线代表街道,而节点是街道的交叉点,那么弧线路由问题就是找出穿过图中每个弧线的最短路径。谷歌每天使用ortools中提供的技术来解决这个问题。

节点路由问题的一个例子是车辆路由。假设一个公司需要使用车队将包裹运送到不同的地点。在这个问题的图中,节点表示位置,弧线表示它们之间的路由。每条弧线都有一个重量,对应于该路线的运行成本。问题:在图中找到一组路径(对应于每辆车的送货路线),其中包含每个目的地,同时最小化总成本。这与弧路由问题不同,因为路径不必遍历每个弧,只需包含每个节点。

OR-Tools包括一个专门的路由库来解决许多类型的节点路由问题:Traveling Salesman Problems (TSP),旅行商问题,这个问题非常著名,也是编程中一个基本问题,在编程领域通常用动态规划解决,现在很多用智能算法比如遗传算法等求解。当然如果你把它看成是一个运筹学的路由问题,就用运筹学的思路解决。

Vehicle Routing Problems (VRP), 这个问题类似收快递或送快递,比如说,有100个快递需要送到100个客户手中,但是一辆车只能送30个快递,需要怎么安排成本最小或最快的问题。

Capacitated Vehicle Routing Problems (CVRP),有约束的VRP问题,比如每辆车都有载重限制等。

Vehicle Routing Problems with Time Windows (VRPTW), 时间限制,车辆必须在一定时间内开始或完成任务。

Vehicle Routing Problems with Resource Constraints, 仓库可能有车辆限制,或车辆需要补充燃料。

Vehicle Routing Problems with Pickup and Delivery (VRPPD),车辆在交付客户前必须先取货。

我们看到,这些都是同一类问题,但是有不同的约束条件,比如车辆载重限制,时间限制,或者仓库限制等,针对不同的场景分别建模,效果会好得多,ortools也是这样做的。

注意:需要说明的是,还有其他的解决方案,如Concorde,致力于解决非常大的tsp优化,这已经超过了ortools的能力范围。然而,ortools提供了一个更好的平台来解决包含纯TSP之外的约束的更一般的路由问题。

2. TSP问题1

旅行商问题是计算机科学中最著名的问题之一。在下面的内容中,我们讲解如何解决TSP问题。

在推销员挨家挨户兜售真空吸尘器和百科全书的年代,他们不得不计划自己的路线,从一家到另一家,从一个城市到另一个城市。路线越短越好。查找访问一组地点的最短路径是一个指数难度的问题:查找20个城市的最短路径的难度是查找10个城市的两倍多。

对所有可能的路径进行彻底的搜索将确保找到最短的路径,但是对于除小的位置集之外的所有路径,这在计算上都是棘手的。对于较大的问题,需要使用优化技术来智能搜索解空间,找到近似最优解。

在数学上,旅行商问题可以表示为一个图,其中的位置是节点,而边(或弧)表示节点之间的直接路由。每条边的权值是节点之间的距离。目标是找到权值之和最短的路径。下面,我们看到一个简单的四节点图,以及访问每个节点的最短周期:

OR-Tools除了为经典的旅行商问题寻找解决方案外,还提供了针对更一般类型的tsp的方法,包括:传统的TSP是对称的:不对称成本问题从A点到B点的距离等于从A点B点的距离不过,航运物品的成本从A点到B点的成本可能不等于把A点B点或工具也可以处理成本不对称的问题。

Prize-collecting TSP:从访问节点中获益。

带有时间窗口的TSP。

我们来看这么一个例子,在美国的13个城市,不同城市之间的距离已经知道,现在需要找到一条路径,每个城市只访问一次,需要访问所有城市,该怎么走,才能使这条路径最短呢?

OK,让我们来撸代码:

from ortools.constraint_solver import pywrapcp

from ortools.constraint_solver import routing_enums_pb2

# 距离的回调函数,用于计算两个节点之间的距离

# 在后面的例子中我们也会看到类似的回调函数,只是计算节点间距离方法不一样而已

def create_distance_callback(dist_matrix):

# Create a callback to calculate distances between cities.

def distance_callback(from_node, to_node):

return int(dist_matrix[from_node][to_node])

return distance_callback

def main():

# 一共13个城市,以及城市之间的距离

city_names = ["New York", "Los Angeles", "Chicago", "Minneapolis",

"Denver", "Dallas", "Seattle", "Boston", "San Francisco",

"St. Louis", "Houston", "Phoenix", "Salt Lake City"]

# Distance matrix

dist_matrix = [

[0, 2451, 713, 1018, 1631, 1374, 2408, 213, 2571, 875, 1420, 2145, 1972], # New York

[2451, 0, 1745, 1524, 831, 1240, 959, 2596, 403, 1589, 1374, 357, 579], # Los Angeles

[713, 1745, 0, 355, 920, 803, 1737, 851, 1858, 262, 940, 1453, 1260], # Chicago

[1018, 1524, 355, 0, 700, 862, 1395, 1123, 1584, 466, 1056, 1280, 987], # Minneapolis

[1631, 831, 920, 700, 0, 663, 1021, 1769, 949, 796, 879, 586, 371], # Denver

[1374, 1240, 803, 862, 663, 0, 1681, 1551, 1765, 547, 225, 887, 999], # Dallas

[2408, 959, 1737, 1395, 1021, 1681, 0, 2493, 678, 1724, 1891, 1114, 701], # Seattle

[213, 2596, 851, 1123, 1769, 1551, 2493, 0, 2699, 1038, 1605, 2300, 2099], # Boston

[2571, 403, 1858, 1584, 949, 1765, 678, 2699, 0, 1744, 1645, 653, 600], # San Francisco

[875, 1589, 262, 466, 796, 547, 1724, 1038, 1744, 0, 679, 1272, 1162], # St. Louis

[1420, 1374, 940, 1056, 879, 225, 1891, 1605, 1645, 679, 0, 1017, 1200], # Houston

[2145, 357, 1453, 1280, 586, 887, 1114, 2300, 653, 1272, 1017, 0, 504], # Phoenix

[1972, 579, 1260, 987, 371, 999, 701, 2099, 600, 1162, 1200, 504, 0]] # Salt Lake City

# 定义TSP问题的参数

tsp_size = len(city_names) # 网络中节点的数量

num_routes = 1 # 定义路由数,TSP为1

depot = 0 # 路由的开始和结束节点,0表示从New York出发,最后要回到New York

# 创建路由模型,需要指定路由的参数

routing = pywrapcp.RoutingModel(tsp_size, num_routes, depot)

search_parameters = pywrapcp.RoutingModel.DefaultSearchParameters()

# 创建距离回调函数,用于计算两个节点之间的距离

dist_callback = create_distance_callback(dist_matrix)

routing.SetArcCostEvaluatorOfAllVehicles(dist_callback)

# Solve the problem.

# 求解并打印结果

assignment = routing.SolveWithParameters(search_parameters)

if assignment:

# 最小距离

print("Total distance: " + str(assignment.ObjectiveValue()) + " miles\n")

# 规划路径结果

# Only one route here; otherwise iterate from 0 to routing.vehicles() - 1

route_number = 0

index = routing.Start(route_number) # Index of the variable for the starting node.

route = ''

while not routing.IsEnd(index):

# Convert variable indices to node indices in the displayed route.

# IndexToNode 求当前节点的对应的编号

# NextVar 当前节点的下一个节点,当然还要再外层套上assignment.Value

route += str(city_names[routing.IndexToNode(index)]) + ' -> '

index = assignment.Value(routing.NextVar(index))

route += str(city_names[routing.IndexToNode(index)])

print("Route:\n\n" + route)

else:

print('No solution found.')

if __name__ == '__main__':

main()

# 结果

Total distance: 7293 miles

Route:

New York -> Boston -> Chicago -> Minneapolis -> Denver

-> Salt Lake City -> Seattle -> San Francisco

-> Los Angeles -> Phoenix -> Houston -> Dallas

-> St. Louis -> New York

注意,因为路由解决程序使用整数进行所有计算,所以距离回调需要将距离回调返回的值转换为整数。在上面的例子中,转换由Python int()函数完成。虽然在这种情况下不需要进行转换,因为所有的距离都是整数,但是建议始终将回调的返回值转换为整数。

3. TSP问题2-钻线路

这个例子是用自动钻孔机在电路板上钻孔,问题是为了钻完所有需要的孔,找到钻板的最短路径。该示例取自TSP问题库TSPLIB。

见下图,和TSP是一个问题,只是看起来更复杂而已,不过距离函数计算不同,在前面的问题中,距离直接给出来了,而这里需要自己计算欧拉距离。

下面是python代码,就不注释了。

import math

from ortools.constraint_solver import pywrapcp

from ortools.constraint_solver import routing_enums_pb2

def euclid_distance(x1, y1, x2, y2):

# 欧式距离计算

dist = math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2)

return dist

def create_distance_matrix(locations):

# Create the distance matrix.

size = len(locations)

dist_matrix = {}

for from_node in range(size):

dist_matrix[from_node] = {}

for to_node in range(size):

x1 = locations[from_node][0]

y1 = locations[from_node][1]

x2 = locations[to_node][0]

y2 = locations[to_node][1]

dist_matrix[from_node][to_node] = euclid_distance(x1, y1, x2, y2)

return dist_matrix

def create_distance_callback(dist_matrix):

# Create the distance callback.

def distance_callback(from_node, to_node):

return int(dist_matrix[from_node][to_node])

return distance_callback

def main():

# Create the data.

locations = create_data_array()

dist_matrix = create_distance_matrix(locations)

dist_callback = create_distance_callback(dist_matrix)

tsp_size = len(locations)

num_routes = 1

depot = 0

# Create routing model.

if tsp_size > 0:

routing = pywrapcp.RoutingModel(tsp_size, num_routes, depot)

search_parameters = pywrapcp.RoutingModel.DefaultSearchParameters()

routing.SetArcCostEvaluatorOfAllVehicles(dist_callback)

# Solve the problem.

assignment = routing.SolveWithParameters(search_parameters)

if assignment:

# Solution cost.

print("Total distance: " + str(assignment.ObjectiveValue()) + "\n")

# Inspect solution.

# Only one route here; otherwise iterate from 0 to routing.vehicles() - 1.

route_number = 0

node = routing.Start(route_number)

start_node = node

route = ''

while not routing.IsEnd(node):

route += str(node) + ' -> '

node = assignment.Value(routing.NextVar(node))

route += '0'

print("Route:\n\n" + route)

else:

print('No solution found.')

else:

print('Specify an instance greater than 0.')

def create_data_array():

locations = [[288, 149], [288, 129], [270, 133], [256, 141], [256, 157], [246, 157], [236, 169],

[228, 169], [228, 161], [220, 169], [212, 169], [204, 169], [196, 169], [188, 169], [196, 161],

[188, 145], [172, 145], [164, 145], [156, 145], [148, 145], [140, 145], [148, 169], [164, 169],

[172, 169], [156, 169], [140, 169], [132, 169], [124, 169], [116, 161], [104, 153], [104, 161],

[104, 169], [90, 165], [80, 157], [64, 157], [64, 165], [56, 169], [56, 161], [56, 153], [56, 145],

[56, 137], [56, 129], [56, 121], [40, 121], [40, 129], [40, 137], [40, 145], [40, 153], [40, 161],

[40, 169], [32, 169], [32, 161], [32, 153], [32, 145], [32, 137], [32, 129], [32, 121], [32, 113],

[40, 113], [56, 113], [56, 105], [48, 99], [40, 99], [32, 97], [32, 89], [24, 89], [16, 97],

[16, 109], [8, 109], [8, 97], [8, 89], [8, 81], [8, 73], [8, 65], [8, 57], [16, 57], [8, 49],

[8, 41], [24, 45], [32, 41], [32, 49], [32, 57], [32, 65], [32, 73], [32, 81], [40, 83], [40, 73],

[40, 63], [40, 51], [44, 43], [44, 35], [44, 27], [32, 25], [24, 25], [16, 25], [16, 17], [24, 17],

[32, 17], [44, 11], [56, 9], [56, 17], [56, 25], [56, 33], [56, 41], [64, 41], [72, 41], [72, 49],

[56, 49], [48, 51], [56, 57], [56, 65], [48, 63], [48, 73], [56, 73], [56, 81], [48, 83], [56, 89],

[56, 97], [104, 97], [104, 105], [104, 113], [104, 121], [104, 129], [104, 137], [104, 145],

[116, 145], [124, 145], [132, 145], [132, 137], [140, 137], [148, 137], [156, 137], [164, 137],

[172, 125], [172, 117], [172, 109], [172, 101], [172, 93], [172, 85], [180, 85], [180, 77],

[180, 69], [180, 61], [180, 53], [172, 53], [172, 61], [172, 69], [172, 77], [164, 81], [148, 85],

[124, 85], [124, 93], [124, 109], [124, 125], [124, 117], [124, 101], [104, 89], [104, 81],

[104, 73], [104, 65], [104, 49], [104, 41], [104, 33], [104, 25], [104, 17], [92, 9], [80, 9],

[72, 9], [64, 21], [72, 25], [80, 25], [80, 25], [80, 41], [88, 49], [104, 57], [124, 69],

[124, 77], [132, 81], [140, 65], [132, 61], [124, 61], [124, 53], [124, 45], [124, 37], [124, 29],

[132, 21], [124, 21], [120, 9], [128, 9], [136, 9], [148, 9], [162, 9], [156, 25], [172, 21],

[180, 21], [180, 29], [172, 29], [172, 37], [172, 45], [180, 45], [180, 37], [188, 41], [196, 49],

[204, 57], [212, 65], [220, 73], [228, 69], [228, 77], [236, 77], [236, 69], [236, 61], [228, 61],

[228, 53], [236, 53], [236, 45], [228, 45], [228, 37], [236, 37], [236, 29], [228, 29], [228, 21],

[236, 21], [252, 21], [260, 29], [260, 37], [260, 45], [260, 53], [260, 61], [260, 69], [260, 77],

[276, 77], [276, 69], [276, 61], [276, 53], [284, 53], [284, 61], [284, 69], [284, 77], [284, 85],

[284, 93], [284, 101], [288, 109], [280, 109], [276, 101], [276, 93], [276, 85], [268, 97],

[260, 109], [252, 101], [260, 93], [260, 85], [236, 85], [228, 85], [228, 93], [236, 93],

[236, 101], [228, 101], [228, 109], [228, 117], [228, 125], [220, 125], [212, 117], [204, 109],

[196, 101], [188, 93], [180, 93], [180, 101], [180, 109], [180, 117], [180, 125], [196, 145],

[204, 145], [212, 145], [220, 145], [228, 145], [236, 145], [246, 141], [252, 125], [260, 129],

[280, 133]]

return locations

if __name__ == '__main__':

main()

# 结果

Total distance: 2790

Route:

0 -> 1 -> 279 -> 2 -> 278 -> 277 -> 248 -> 247 -> 243 -> 242 -> 241

-> 240 -> 239 -> 238 -> 245 -> 244 -> 246 -> 249 -> 250 -> 229 -> 228

……

-> 276 -> 3 -> 4 -> 0

那么长的代码不用细看,看几个关键点就好了。上面两个例子只是练手,其实没多大用处,接下来将的VRP问题就用处就比较直接了,当然,现实业务可不这么简单,总是有各种各样的状况要考虑,不管是简化模型也好,增加各种约束也好,开开心心干活是不存在的。

敬请期待。

4. 参考

大家看完记得关注点赞.

master苏.

ortools解决tsp_ortools系列:路由问题1相关推荐

  1. ortools解决tsp_ortools系列:后话

    ortools系列:后话 到此,整个ortools系列就写完了,总的来说基本把ortools使用方法和应用场景讲清楚了.同时也注意到还有很多东西没有讲到,比如ortools的体系架构,接口分类等等.对 ...

  2. mysql数据库出现无法登录(ERROR 1045 ),预防和解决及系列问题解决方法。

    mysql数据库出现无法登录(ERROR 1045 ),预防和解决及系列问题解决方法. 参考文章: (1)mysql数据库出现无法登录(ERROR 1045 ),预防和解决及系列问题解决方法. (2) ...

  3. 解决zabbix-4系列监控图形中文乱码问题

    解决zabbix-4系列监控图形中文乱码问题 参考文章: (1)解决zabbix-4系列监控图形中文乱码问题 (2)https://www.cnblogs.com/ywb123/p/12124409. ...

  4. 解决vue2.0路由 TypeError: Cannot read property ‘matched‘ of undefined 的错误问题

    解决vue2.0路由 TypeError: Cannot read property 'matched' of undefined 的错误问题 参考文章: (1)解决vue2.0路由 TypeErro ...

  5. 解决vue项目路由出现message: “Navigating to current location (XXX) is not allowed“的问题

    解决vue项目路由出现message: "Navigating to current location (XXX) is not allowed"的问题 参考文章: (1)解决vu ...

  6. 解决vue项目路由出现message: “Navigating to current location (XXX) is not allowed“的问题(点击多次跳转)

    解决vue项目路由出现message: "Navigating to current location (XXX) is not allowed"的问题(点击多次跳转) 参考文章: ...

  7. 解决VIVO系列热点自动关闭

    解决VIVO系列热点自动关闭 蛋疼的VIVO 十分钟自动关闭热点 用来做路由器不好使,所以写个app解决这个问题. 打开并保持后台运行即可,热点关闭后会在2秒内重启 ap名称:隔壁老王和他的小伙伴 密 ...

  8. 解决Autodesk系列软件卸载不完全导致的再次安装失败问题

    解决Autodesk系列软件卸载不完全导致的再次安装失败问题 网上流传的auto uninstaller需要付费,而最简单的方法就是打开要安装的软件预解包文件路径下的xx.msi文件,比如...... ...

  9. cmd长ping记录日志和时间_Gin 框架系列 — 路由中间件:日志记录

    概述 首先同步下项目概况: 上篇文章分享了,规划项目目录和参数验证,其中参数验证使用的是 validator.v8 版本,现已更新到 validator.v9 版本,最新代码查看 github 即可. ...

最新文章

  1. 单片机与微处理器和微型计算机的关系,微处理器、CPU、微处理机、微机、单片机它们之间有何区别?...
  2. Finding iPhone Memory Leaks: A “Leaks” Tool Tutorial[转]
  3. linux外部命令帮助,Linux的命令帮助
  4. 包概念与__init__注意事项
  5. python中pip已经安装好第三方库,但在pycharm中import时还是标红
  6. FastAPI集成SQLAlchemy实现数据库操作
  7. org.aspectj aspectjweaver 报错
  8. “鬼影”浅析 - 反病毒,信息安全,网络安全,反木马,病毒资讯平台,安全解决方案,电脑使用技巧,杀毒软件交流,anti-virus,民间反病毒联盟
  9. 熊出没电锯机器人哪一集_熊出没伐木机器人第几集 熊出没光头强造伐木机器人是哪一集?...
  10. Xcel Energy与D.E.Shaw签署100MW光伏电站购电协议
  11. 机器算法基础——回归分析
  12. 视频教程-Nodejs极速入门-Node.js
  13. HTML Purifier解决XSS问题
  14. QT起一个线程实时监测某个进程是否正常运行
  15. 特征工程(1)特征工程的简介
  16. (生活篇)职场饭局生存法则
  17. php顺丰bsp订单跟踪,顺丰BSP接口PHP开发注意事项
  18. 使用Open Flash Chart(OFC)制作图表(Struts2处理)
  19. 疫情期间2020应届生找工作经历
  20. 实现Aero特效的基础

热门文章

  1. jQuery-1.9.1源码分析系列(六) 延时对象应用——jQuery.ready
  2. Response.Write()方法响应导致页面内容变形的问题
  3. Ajax实现异步操作实例_针对XML格式的请求数据
  4. 转:[Asp.net]常见数据导入Excel,Excel数据导入数据库解决方案,总有一款适合你!...
  5. 告诉SEO初学者:百度收录并非终极目标
  6. Android网络框架OK3,Android网络框架---OkHttp3
  7. [MySQL]MySQL分区与传统的分库分表(精华)
  8. CSS一个冒号是伪类:用于监控动作、两个冒号是伪元素::用于定位元素
  9. PHP验证码相关函数
  10. Yii的数值唯一性-场景与SQL