【附代码】【6种方法】旅行商问题(TSP)整数规划 VS 启发式——2023.9 持续更新ing

文章目录

    • 相关文献
    • 问题概述
    • 测试电脑配置
    • 测试结果——更新于2023.9
    • 测试数据集
    • 测试代码
      • DFJ_Lazy
      • MTZ
      • LKH
      • Concorde
      • OrTools Routing
      • OrTools Sat
    • 算法原理(待补充。。。敬请期待!!!)

作者:小猪快跑

基础数学&计算数学,从事优化领域5年+,主要研究方向:MIP求解器、整数规划、随机规划、智能优化算法

本文以综述为主,将从常见的多种整数规划建模,如DFJ模型、MTZ模型等,使用Gurobi、Cplex、SCIP、Or-Tools、Cbc等常见求解器,并和常见的启发式LKH等给出性能分析报告,并浅谈其优缺点。

如有错误,欢迎指正。如有更好的算法,也欢迎交流!!!——@小猪快跑

相关文献

  • Traveling Salesperson Problem | OR-Tools | Google for Developers
  • TSP(旅行商问题)_百度百科 (baidu.com)
  • TSPLIB (uni-heidelberg.de)

问题概述

TSP,即旅行商问题,又称TSP问题(Traveling Salesman Problem),是数学领域中著名问题之一。

假设有一个旅行商人要拜访n个城市,他必须选择所要走的路径,路径的限制是每个城市只能拜访一次,而且最后要回到原来出发的城市。路径的选择目标是要求所选的路径路程为所有路径中的最小值。

例如,下图显示 一个只有四个位置的 TSP,标记为 A、B、C 和 D。 任意两个位置之间的距离由边旁边的数字给出 加入他们。
【附代码】【6种方法】旅行商问题(TSP)整数规划 VS 启发式——2023.9 持续更新ing_第1张图片

通过计算所有可能路线的距离,您可以看到 最短路线是 ACDBA,其总距离为 。35 + 30 + 15 + 10 = 90

位置越多,问题就越严重。上面的示例中只有六条路线。但是,如果有 10 个位置(不计算起点),路线数量为 362880。对于 20 个位置,该数量将跳转到 2432902008176640000。 对于较大的问题,往往很难在给定的时间得到最优解,我们一般倾向于得到近乎最佳的解决方案。

测试电脑配置

博主三千元电脑的渣渣配置:

CPU model: AMD Ryzen 7 7840HS w/ Radeon 780M Graphics, instruction set [SSE2|AVX|AVX2|AVX512]
Thread count: 8 physical cores, 16 logical processors, using up to 16 threads

测试结果——更新于2023.9

使用著名的TSPLIB 95测试集(有些case因为电脑内存受限报错没有计入)

怀疑官方给出的某些case的bounds存在一些问题(也或许是数值误差?)。比如说:tsp225,在各种方法中我们都得到了比官方给出bound更优的解,但为了保持和官方一致性,我们暂时不对官方数据进行修正,特此说明。

表格中没有时间的说明内存溢出,电脑配置有限无法进行测试。

Name #cities Type Bounds DFJ_Lazy DFJ_LAZY Gap DFJ_Lazy Run Time(s) LKH LKH Gap LKH Run Time(s) LKH(10 times) LKH(10 times) Gap LKH(10 times) Run Time(s) MTZ MTZ Gap MTZ Run Time(s) Concorde Concorde Gap Concorde Run Time(s) OrTools Routing OrTools Routing Gap OrTools Routing Run Time(s) OrTools Sat OrTools Sat Gap OrTools Sat Run Time(s)
burma14 14 GEO 3323 3323 0.00% 0.004 3323 0.00% 0 3323 0.00% 0 3323 0.00% 0.131 3323 0.00% 0 3323 0.00% 0.003 3323 0.00% 0.019
ulysses16 16 GEO 6859 6859 0.00% 0.004 6859 0.00% 0 6859 0.00% 0 6859 0.00% 0.324 6859 0.00% 0.01 6859 0.00% 0.003 6859 0.00% 0.217
gr17 17 MATRIX 2085 2085 0.00% 0.008 2085 0.00% 0 2085 0.00% 0 2085 0.00% 0.432 2085 0.00% 0 2085 0.00% 0.003 2085 0.00% 0.054
gr21 21 MATRIX 2707 2707 0.00% 0.008 2707 0.00% 0 2707 0.00% 0 2707 0.00% 0.008 2707 0.00% 0 2707 0.00% 0.007 2707 0.00% 0.068
ulysses22 22 GEO 7013 7013 0.00% 0.012 7013 0.00% 0 7013 0.00% 0 7013 0.00% 4.097 7013 0.00% 0.03 7013 0.00% 0.006 7013 0.00% 0.152
gr24 24 MATRIX 1272 1272 0.00% 0.017 1272 0.00% 0 1272 0.00% 0 1272 0.00% 0.057 1272 0.00% 0 1314 3.30% 0.009 1272 0.00% 0.180
fri26 26 MATRIX 937 937 0.00% 0.025 937 0.00% 0 937 0.00% 0 937 0.00% 0.217 937 0.00% 0 953 1.71% 0.005 937 0.00% 0.169
bayg29 29 GEO 1610 1610 0.00% 0.046 1610 0.00% 0 1610 0.00% 0 1610 0.00% 0.280 1610 0.00% 0.01 1748 8.57% 0.011 1610 0.00% 0.214
bays29 29 GEO 2020 2020 0.00% 0.036 2020 0.00% 0 2020 0.00% 0 2020 0.00% 0.303 2020 0.00% 0 2020 0.00% 0.010 2020 0.00% 0.154
dantzig42 42 MATRIX 699 699 0.00% 0.039 699 0.00% 0 699 0.00% 0 699 0.00% 1.931 699 0.00% 0.01 738 5.58% 0.026 699 0.00% 0.339
swiss42 42 MATRIX 1273 1273 0.00% 0.090 1273 0.00% 0 1273 0.00% 0 1273 0.00% 0.392 1273 0.00% 0.01 1368 7.46% 0.015 1273 0.00% 0.359
att48 48 ATT 10628 10628 0.00% 0.109 10628 0.00% 0 10628 0.00% 0 10628 0.00% 20.482 10628 0.00% 0.03 10836 1.96% 0.039 10628 0.00% 0.612
gr48 48 MATRIX 5046 5046 0.00% 0.242 5046 0.00% 0 5046 0.00% 0.01 5046 0.00% 3.960 5046 0.00% 0.03 5097 1.01% 0.024 5046 0.00% 1.308
hk48 48 MATRIX 11461 11461 0.00% 0.104 11461 0.00% 0 11461 0.00% 0 11461 0.00% 0.766 11461 0.00% 0.01 11847 3.37% 0.027 11461 0.00% 0.451
eil51 51 EUC_2D 426 426 0.00% 0.142 426 0.00% 0 426 0.00% 0.01 426 0.00% 0.626 426 0.00% 0.02 439 3.05% 0.030 426 0.00% 1.114
berlin52 52 EUC_2D 7542 7542 0.00% 0.032 7542 0.00% 0 7542 0.00% 0 7542 0.00% 0.331 7542 0.00% 0.01 7944 5.33% 0.030 7542 0.00% 0.247
brazil58 58 MATRIX 25395 25395 0.00% 0.122 25395 0.00% 0 25395 0.00% 0.01 25395 0.00% 101.686 25395 0.00% 0.03 25937 2.13% 0.032 25395 0.00% 0.574
st70 70 EUC_2D 675 675 0.00% 0.267 675 0.00% 0 675 0.00% 0.01 675 0.00% 23.076 675 0.00% 0.04 683 1.19% 0.099 675 0.00% 4.584
eil76 76 EUC_2D 538 538 0.00% 0.231 538 0.00% 0 538 0.00% 0.01 538 0.00% 1.338 538 0.00% 0.02 548 1.86% 0.073 538 0.00% 1.129
pr76 76 EUC_2D 108159 108159 0.00% 3.390 108159 0.00% 0 108159 0.00% 0.04 108159 0.00% 38.697 108159 0.00% 0.12 110948 2.58% 0.072 108159 0.00% 117.067
gr96 96 GEO 55209 55209 0.00% 1.540 55209 0.00% 0 55209 0.00% 0.06 55209 0.00% 156.626 55209 0.00% 0.12 56925 3.11% 0.152 55209 0.00% 13.319
rat99 99 EUC_2D 1211 1211 0.00% 0.648 1211 0.00% 0 1211 0.00% 0.01 1211 0.00% 3.075 1211 0.00% 0.06 1284 6.03% 0.101 1211 0.00% 5.752
kroA100 100 EUC_2D 21282 21282 0.00% 1.586 21282 0.00% 0 21282 0.00% 0.03 21282 0.00% 79.691 21282 0.00% 0.05 21960 3.19% 0.138 21282 0.00% 12.701
kroB100 100 EUC_2D 22141 22141 0.00% 2.607 22141 0.00% 0 22141 0.00% 0.05 22141 0.00% 600 22141 0.00% 0.1 22945 3.63% 0.138 22141 0.00% 16.237
kroC100 100 EUC_2D 20749 20749 0.00% 0.757 20749 0.00% 0 20749 0.00% 0.01 20749 0.00% 58.760 20749 0.00% 0.05 21699 4.58% 0.110 20749 0.00% 7.455
kroD100 100 EUC_2D 21294 21294 0.00% 0.991 21294 0.00% 0 21294 0.00% 0.03 21450 0.73% 600 21294 0.00% 0.07 22439 5.38% 0.122 21294 0.00% 7.964
kroE100 100 EUC_2D 22068 22068 0.00% 1.695 22068 0.00% 0.01 22068 0.00% 0.1 22068 0.00% 600 22068 0.00% 0.11 22551 2.19% 0.182 22068 0.00% 15.506
rd100 100 EUC_2D 7910 7910 0.00% 0.524 7910 0.00% 0 7910 0.00% 0.01 7910 0.00% 600 7910 0.00% 0.04 8221 3.93% 0.140 7910 0.00% 3.946
eil101 101 EUC_2D 629 629 0.00% 0.431 629 0.00% 0 629 0.00% 0.02 629 0.00% 600 629 0.00% 0.04 650 3.34% 0.184 629 0.00% 7.013
lin105 105 EUC_2D 14379 14379 0.00% 0.348 14379 0.00% 0 14379 0.00% 0.01 14379 0.00% 600 14379 0.00% 0.03 15363 6.84% 0.280 14379 0.00% 3.139
pr107 107 EUC_2D 44303 44303 0.00% 0.452 44303 0.00% 0 44303 0.00% 0.03 44474 0.39% 600 44303 0.00% 0.07 44573 0.61% 0.167 44303 0.00% 3.989
gr120 120 MATRIX 6942 6942 0.00% 1.582 6942 0.00% 0 6942 0.00% 0.03 6942 0.00% 600 6942 0.00% 0.08 7116 2.51% 0.382 6942 0.00% 14.897
pr124 124 EUC_2D 59030 59030 0.00% 2.601 59030 0.00% 0 59030 0.00% 0.04 59076 0.08% 600 59030 0.00% 0.84 60413 2.34% 0.142 59030 0.00% 25.276
bier127 127 EUC_2D 118282 118282 0.00% 3.616 118282 0.00% 0 118282 0.00% 0.04 118282 0.00% 600 118282 0.00% 0.1 121729 2.91% 0.320 118282 0.00% 15.329
ch130 130 EUC_2D 6110 6110 0.00% 3.281 6110 0.00% 0 6110 0.00% 0.04 6217 1.75% 600 6110 0.00% 0.12 6329 3.58% 0.253 6110 0.00% 15.927
pr136 136 EUC_2D 96772 96772 0.00% 4.480 96772 0.00% 0.01 96772 0.00% 0.1 96772 0.00% 600 96772 0.00% 0.2 102813 6.24% 0.344 96772 0.00% 52.818
gr137 137 GEO 69853 69853 0.00% 2.404 69853 0.00% 0 69853 0.00% 0.04 69853 0.00% 600 69853 0.00% 0.16 72028 3.11% 0.317 69853 0.00% 27.201
pr144 144 EUC_2D 58537 58537 0.00% 2.101 58537 0.00% 0.01 58537 0.00% 0.07 58763 0.39% 600 58537 0.00% 0.17 59286 1.28% 0.482 58537 0.00% 23.272
ch150 150 EUC_2D 6528 6528 0.00% 6.964 6532 0.06% 0.04 6528 0.00% 0.42 6528 0.00% 600 6528 0.00% 0.25 6733 3.14% 0.454 6528 0.00% 43.855
kroA150 150 EUC_2D 26524 26524 0.00% 5.665 26524 0.00% 0 26524 0.00% 0.05 26525 0.00% 600 26524 0.00% 0.24 27503 3.69% 0.270 26524 0.00% 74.588
kroB150 150 EUC_2D 26130 26130 0.00% 5.698 26131 0.00% 0.06 26130 0.00% 0.6 26188 0.22% 600 26130 0.00% 0.38 26671 2.07% 0.359 26130 0.00% 75.159
pr152 152 EUC_2D 73682 73682 0.00% 12.436 73682 0.00% 0.02 73682 0.00% 0.19 75281 2.17% 600 73682 0.00% 0.48 75832 2.92% 0.570 73682 0.00% 42.787
u159 159 EUC_2D 42080 42080 0.00% 2.733 42080 0.00% 0 42080 0.00% 0.02 42080 0.00% 600 42080 0.00% 0.06 43403 3.14% 0.815 42080 0.00% 13.925
si175 175 MATRIX 21407 21407 0.00% 10.557 21407 0.00% 0.01 21407 0.00% 0.14 21422 0.07% 600 21407 0.00% 1.56 21513 0.50% 0.652 21407 0.00% 126.282
brg180 180 MATRIX 1950 1950 0.00% 1.196 1950 0.00% 0 1950 0.00% 0.04 1950 0.00% 600 1950 0.00% 0.28 1960 0.51% 0.471 1950 0.00% 7.529
rat195 195 EUC_2D 2323 2323 0.00% 39.384 2323 0.00% 0.02 2323 0.00% 0.18 2323 0.00% 600 2323 0.00% 1.24 2375 2.24% 0.812 2323 0.00% 275.161
d198 198 EUC_2D 15780 15780 0.00% 16.663 15780 0.00% 0.06 15780 0.00% 0.57 15974 1.23% 600 15780 0.00% 0.68 16005 1.43% 0.741 15780 0.00% 83.168
kroA200 200 EUC_2D 29368 29368 0.00% 14.420 29368 0.00% 0.01 29368 0.00% 0.16 29382 0.05% 600 29368 0.00% 0.27 29874 1.72% 0.544 29368 0.00% 326.296
kroB200 200 EUC_2D 29437 29437 0.00% 22.398 29437 0.00% 0 29437 0.00% 0.05 29800 1.23% 600 29437 0.00% 0.17 31110 5.68% 0.840 29437 0.00% 143.404
gr202 202 GEO 40160 40160 0.00% 10.337 40160 0.00% 0 40160 0.00% 0.04 40160 0.00% 600 40160 0.00% 0.3 42218 5.12% 0.832 40160 0.00% 35.310
ts225 225 EUC_2D 126643 600 126643 0.00% 0.01 126643 0.00% 0.11 127587 0.75% 600 126643 0.00% 2.11 127763 0.88% 0.766 127229 0.46% 664.010
tsp225 225 EUC_2D 3919 3916 -0.08% 92.804 3916 -0.08% 0.04 3916 -0.08% 0.42 3979 1.53% 600 3916 -0.08% 0.86 4117 5.05% 1.042 3930 0.28% 627.207
pr226 226 EUC_2D 80369 80369 0.00% 14.744 80369 0.00% 0.01 80369 0.00% 0.08 81308 1.17% 600 80369 0.00% 0.25 83113 3.41% 0.857 80369 0.00% 252.200
gr229 229 GEO 134602 134602 0.00% 13.956 134612 0.01% 0.1 134602 0.00% 1 134602 0.00% 600 134602 0.00% 1.84 140014 4.02% 0.791 135101 0.37% 631.349
gil262 262 EUC_2D 2378 2378 0.00% 41.076 2378 0.00% 0.03 2378 0.00% 0.31 2398 0.84% 600 2378 0.00% 0.57 2517 5.85% 1.889 2380 0.08% 628.027
pr264 264 EUC_2D 49135 49135 0.00% 49.310 49135 0.00% 0.02 49135 0.00% 0.28 55289 12.52% 600 49135 0.00% 0.15 51495 4.80% 2.470 49135 0.00% 236.860
a280 280 EUC_2D 2579 2579 0.00% 19.930 2579 0.00% 0 2579 0.00% 0.08 2579 0.00% 600 2579 0.00% 0.31 2742 6.32% 1.544 2579 0.00% 299.732
pr299 299 EUC_2D 48191 48191 0.00% 193.045 48191 0.00% 0.07 48191 0.00% 0.71 48472 0.58% 600 48191 0.00% 1.01 50617 5.03% 1.819 48769 1.20% 662.902
lin318 318 EUC_2D 42029 42029 0.00% 39.310 42075 0.11% 0.17 42029 0.00% 1.79 45273 7.72% 600 42029 0.00% 1.18 43550 3.62% 3.436 42285 0.61% 652.776
rd400 400 EUC_2D 15281 15281 0.00% 281.219 15281 0.00% 0.04 15281 0.00% 0.48 17733 16.05% 600 15281 0.00% 7.24 15820 3.53% 5.347 15431 0.98% 644.397
fl417 417 EUC_2D 11861 600 11871 0.09% 1.33 11861 0.00% 13.37 14396 21.37% 600 11861 0.00% 3.8 12164 2.55% 3.113 11878 0.14% 678.489
gr431 431 GEO 171414 600 171522 0.06% 0.79 171414 0.00% 8.02 221655 29.31% 600 171414 0.00% 10 177315 3.44% 5.722 172399 0.57% 675.936
pr439 439 EUC_2D 107217 600 107221 0.00% 0.12 107217 0.00% 1.3 128992 20.31% 600 107217 0.00% 9.95 117171 9.28% 4.869 109106 1.76% 654.776
pcb442 442 EUC_2D 50778 600 50778 0.00% 0.11 50778 0.00% 1.24 57458 13.16% 600 50778 0.00% 11.97 52390 3.17% 5.990 53941 6.23% 644.089
d493 493 EUC_2D 35002 600 35003 0.00% 0.63 35002 0.00% 6.38 59211 69.16% 600 35002 0.00% 14.03 36737 4.96% 8.003 35968 2.76% 644.176
att532 532 ATT 27686 600 27686 0.00% 0.13 27686 0.00% 1.41 52596 89.97% 600 27686 0.00% 11.99 28785 3.97% 8.977 28035 1.26% 644.428
ali535 535 GEO 202310 600 202339 0.01% 1.97 202339 0.01% 19.81 235193 16.25% 600 202339 0.01% 1.68 218171 7.84% 10.926 205238 1.45% 635.610
si535 535 MATRIX 48450 600 48453 0.01% 1.66 48450 0.00% 16.68 53897 11.24% 600 48450 0.00% 3.41 48843 0.81% 9.206 48893 0.91% 642.263
pa561 561 MATRIX 2763 600 2763 0.00% 0.24 2763 0.00% 2.56 4264 54.33% 600 2763 0.00% 15.74 2924 5.83% 13.069 2963 7.24% 673.489
u574 574 EUC_2D 36905 600 36908 0.01% 0.18 36905 0.00% 1.99 52670 42.72% 600 36905 0.00% 3.16 38717 4.91% 12.583 37718 2.20% 656.241
rat575 575 EUC_2D 6773 600 6773 0.00% 0.12 6773 0.00% 1.3 8034 18.62% 600 6773 0.00% 15.93 7146 5.51% 11.315 7266 7.28% 658.255
p654 654 EUC_2D 34643 600 34643 0.00% 0.75 34643 0.00% 7.7 53930 55.67% 600 34643 0.00% 1.49 34984 0.98% 15.336 43494 25.55% 668.280
d657 657 EUC_2D 48912 600 48913 0.00% 0.86 48913 0.00% 8.77 115563 136.27% 600 48913 0.00% 13.84 51499 5.29% 18.637 54175 10.76% 654.260
gr666 666 GEO 294358 600 294452 0.03% 1.71 294358 0.00% 17.29 698914 137.44% 600 294358 0.00% 4.3 306909 4.26% 16.516 358840 21.91% 659.841
u724 724 EUC_2D 41910 600 41910 0.00% 0.29 41910 0.00% 3.18 90860 116.80% 600 41910 0.00% 10.07 43968 4.91% 22.485 48564 15.88% 659.095
rat783 783 EUC_2D 8806 600 8806 0.00% 0.01 8806 0.00% 0.41 600 8806 0.00% 2.41 9234 4.86% 34.622 10135 15.09% 687.173
dsj1000 1000 CEIL_2D 18659688 600 18662354 0.01% 13.06 18660188 0.00% 131.13 600 18660188 0.00% 37.56 20173763 8.11% 70.175 27290425 46.25% 733.970
pr1002 1002 EUC_2D 259045 600 259045 0.00% 0.23 259045 0.00% 2.92 600 259045 0.00% 2.75 271893 4.96% 60.720 332066 28.19% 755.929
si1032 1032 MATRIX 92650 600 92690 0.04% 0.98 92650 0.00% 10.22 600 92650 0.00% 1.95 92731 0.09% 54.858 144822 56.31% 781.457
u1060 1060 EUC_2D 224094 600 224113 0.01% 11.81 224094 0.00% 118.68 600 224094 0.00% 70.48 236297 5.45% 62.834 254330 13.49% 774.805
vm1084 1084 EUC_2D 239297 600 239335 0.02% 3.11 239297 0.00% 31.72 600 239297 0.00% 64.73 251490 5.10% 67.099 4095605 1611.52% 761.969
pcb1173 1173 EUC_2D 56892 600 56892 0.00% 0.55 56892 0.00% 6.17 600 56892 0.00% 32.66 60764 6.81% 98.028 178312 213.42% 819.574
d1291 1291 EUC_2D 50801 600 50836 0.07% 9.35 50801 0.00% 94.32 600 50801 0.00% 11622.49 53985 6.27% 124.252 1734384 3314.07% 853.088
rl1304 1304 EUC_2D 252948 600 253037 0.04% 1.33 252948 0.00% 14.14 600 252948 0.00% 19.12 278726 10.19% 119.564 5095380 1914.40% 866.635
rl1323 1323 EUC_2D 270199 600 270224 0.01% 4.69 270199 0.00% 47.83 600 270199 0.00% 682.14 284355 5.24% 98.401 3163916 1070.96% 887.869
nrw1379 1379 EUC_2D 56638 600 56639 0.00% 2.06 56638 0.00% 21.48 600 56638 0.00% 26.45 59231 4.58% 125.277 716727 1165.45% 972.965
fl1400 1400 EUC_2D 20127 600 20165 0.19% 62.61 20164 0.18% 626.99 600 20127 0.00% 582.94 21031 4.49% 168.513 1690273 8298.04% 921.967
u1432 1432 EUC_2D 152970 600 152970 0.00% 0.27 152970 0.00% 3.71 600 152970 0.00% 100.29 160767 5.10% 173.341 242149 58.30% 957.349
d1655 1655 EUC_2D 62128 600 62128 0.00% 0.82 62128 0.00% 9.49 600 62128 0.00% 15.94 65346 5.18% 240.322 206258 231.99% 1128.190
vm1748 1748 EUC_2D 336556 600 336593 0.01% 5.72 336556 0.00% 58.72 600 336556 0.00% 283.17 356201 5.84% 215.322 11325958 3265.25% 1281.710

测试数据集

使用著名的TSPLIB 95测试集,这是官方给出的结果:

Name #cities Type Bounds
burma14 14 GEO 3323
ulysses16 16 GEO 6859
gr17 17 MATRIX 2085
gr21 21 MATRIX 2707
ulysses22 22 GEO 7013
gr24 24 MATRIX 1272
fri26 26 MATRIX 937
bayg29 29 GEO 1610
bays29 29 GEO 2020
dantzig42 42 MATRIX 699
swiss42 42 MATRIX 1273
att48 48 ATT 10628
gr48 48 MATRIX 5046
hk48 48 MATRIX 11461
eil51 51 EUC_2D 426
berlin52 52 EUC_2D 7542
brazil58 58 MATRIX 25395
st70 70 EUC_2D 675
eil76 76 EUC_2D 538
pr76 76 EUC_2D 108159
gr96 96 GEO 55209
rat99 99 EUC_2D 1211
kroA100 100 EUC_2D 21282
kroB100 100 EUC_2D 22141
kroC100 100 EUC_2D 20749
kroD100 100 EUC_2D 21294
kroE100 100 EUC_2D 22068
rd100 100 EUC_2D 7910
eil101 101 EUC_2D 629
lin105 105 EUC_2D 14379
pr107 107 EUC_2D 44303
gr120 120 MATRIX 6942
pr124 124 EUC_2D 59030
bier127 127 EUC_2D 118282
ch130 130 EUC_2D 6110
pr136 136 EUC_2D 96772
gr137 137 GEO 69853
pr144 144 EUC_2D 58537
ch150 150 EUC_2D 6528
kroA150 150 EUC_2D 26524
kroB150 150 EUC_2D 26130
pr152 152 EUC_2D 73682
u159 159 EUC_2D 42080
si175 175 MATRIX 21407
brg180 180 MATRIX 1950
rat195 195 EUC_2D 2323
d198 198 EUC_2D 15780
kroA200 200 EUC_2D 29368
kroB200 200 EUC_2D 29437
gr202 202 GEO 40160
ts225 225 EUC_2D 126643
tsp225 225 EUC_2D 3919
pr226 226 EUC_2D 80369
gr229 229 GEO 134602
gil262 262 EUC_2D 2378
pr264 264 EUC_2D 49135
a280 280 EUC_2D 2579
pr299 299 EUC_2D 48191
lin318 318 EUC_2D 42029
linhp318 318 EUC_2D 41345
rd400 400 EUC_2D 15281
fl417 417 EUC_2D 11861
gr431 431 GEO 171414
pr439 439 EUC_2D 107217
pcb442 442 EUC_2D 50778
d493 493 EUC_2D 35002
att532 532 ATT 27686
ali535 535 GEO 202310
si535 535 MATRIX 48450
pa561 561 MATRIX 2763
u574 574 EUC_2D 36905
rat575 575 EUC_2D 6773
p654 654 EUC_2D 34643
d657 657 EUC_2D 48912
gr666 666 GEO 294358
u724 724 EUC_2D 41910
rat783 783 EUC_2D 8806
dsj1000 1000 CEIL_2D 18659688
pr1002 1002 EUC_2D 259045
si1032 1032 MATRIX 92650
u1060 1060 EUC_2D 224094
vm1084 1084 EUC_2D 239297
pcb1173 1173 EUC_2D 56892
d1291 1291 EUC_2D 50801
rl1304 1304 EUC_2D 252948
rl1323 1323 EUC_2D 270199
nrw1379 1379 EUC_2D 56638
fl1400 1400 EUC_2D 20127
u1432 1432 EUC_2D 152970
fl1577 1577 EUC_2D [22204,22249]
d1655 1655 EUC_2D 62128
vm1748 1748 EUC_2D 336556
u1817 1817 EUC_2D 57201
rl1889 1889 EUC_2D 316536
d2103 2103 EUC_2D [79952,80450]
u2152 2152 EUC_2D 64253
u2319 2319 EUC_2D 234256
pr2392 2392 EUC_2D 378032
pcb3038 3038 EUC_2D 137694
fl3795 3795 EUC_2D [28723,28772]
fnl4461 4461 EUC_2D 182566
rl5915 5915 EUC_2D [565040,565530]
rl5934 5934 EUC_2D [554070,556045]
pla7397 7397 CEIL_2D 23260728
rl11849 11849 EUC_2D [920847,923368]
usa13509 13509 EUC_2D [19947008,19982889]
brd14051 14051 EUC_2D [468942,469445]
d15112 15112 EUC_2D [1564590,1573152]
d18512 18512 EUC_2D [644650,645488]
pla33810 33810 CEIL_2D [65913275,66116530]
pla85900 85900 CEIL_2D [141904862,142487006]

测试代码

DFJ_Lazy

import glob
import networkx as nx
import numpy as np
import gurobipy as gp
import pandas as pd
from gurobipy import GRB, quicksum
import tsplib95


class DfjLazy:
    def __init__(self, cost: np.ndarray, problem=None):
        self.problem = problem
        self.cost = cost
        self.dimension = len(cost)
        self.x = {}
        self.m = None

    @classmethod
    def read_tsplib95(cls, path):
        problem = tsplib95.load(path)
        cost = np.zeros([problem.dimension, problem.dimension], dtype=int)
        for i, start_node in enumerate(problem.get_nodes()):
            for j, end_node in enumerate(problem.get_nodes()):
                cost[i, j] = problem.get_weight(start_node, end_node)
        return cls(cost, problem)

    def create_solver(self, params: dict = None):
        self.m = gp.Model("DFJ_Lazy")
        self.m.setParam('lazyConstraints', 1)
        for param_name, new_val in params.items():
            self.m.setParam(param_name, new_val)

    def add_vars(self):
        for i in range(self.dimension):
            for j in range(self.dimension):
                if i == j:
                    continue
                self.x[i, j] = self.m.addVar(vtype=GRB.BINARY, name="x(%s,%s)" % (i, j))

    def set_obj(self):
        self.m.setObjective(quicksum(self.cost[i, j] * self.x[i, j] for (i, j) in self.x), GRB.MINIMIZE)

    def add_conss(self):
        for i in range(self.dimension):
            self.m.addConstr(quicksum(self.x[i, j] for j in range(self.dimension) if j != i) == 1, "Out(%s)" % i)
            self.m.addConstr(quicksum(self.x[j, i] for j in range(self.dimension) if j != i) == 1, "In(%s)" % i)

    def solve(self):
        def subtour_elimination(model, where):
            if where == GRB.Callback.MIPSOL:
                x_sol = model.cbGetSolution(self.x)

                graph = nx.Graph()
                for k, sol in x_sol.items():
                    if sol > 0.5:
                        graph.add_edge(k[0], k[1])
                for c in nx.connected_components(graph):
                    if len(c) < self.dimension:
                        model.cbLazy(quicksum(self.x[i, j] for i in c for j in c if i != j) <= len(c) - 1)

        self.m.optimize(subtour_elimination)
        return self.get_sol()

    def get_status(self):
        match self.m.status:
            case GRB.status.OPTIMAL:
                return 'OPTIMAL'
            case GRB.status.TIME_LIMIT:
                return 'TIME_LIMIT'

    def get_sol(self):
        status = self.get_status()
        gap = self.m.MIPGap
        lb = self.m.ObjBoundC
        ub = self.m.ObjVal
        runtime = self.m.Runtime

        return {'gap': gap, 'status': status, 'lb': lb, 'ub': ub, 'runtime': runtime}

    def make_model(self, params: dict = None):
        self.create_solver(params)
        self.add_vars()
        self.add_conss()
        self.set_obj()


if __name__ == '__main__':
    res_file = 'result.csv'
    df = pd.DataFrame(columns=['Method', 'Name', '#cities', 'LB', 'UB', 'Run Time'])
    df.to_csv(res_file, index=False)
    for path in glob.glob('D:/Data/ALL_tsp/*.tsp'):
        print(path)
        m = DfjLazy.read_tsplib95(path)
        p = m.problem
        m.make_model({'OutputFlag': 1, 'TimeLimit': 600, 'MIPGap': 0})
        sol = m.solve()
        with open(res_file, 'a') as f:
            f.write(f"DFJ_Lazy,{p.name},{p.dimension},{sol['lb']},{sol['ub']},{sol['runtime']}\n")

MTZ

import glob
import networkx as nx
import numpy as np
import gurobipy as gp
import pandas as pd
from gurobipy import GRB, quicksum
import tsplib95


class MtzStrong:
    def __init__(self, cost: np.ndarray, problem=None):
        self.problem = problem
        self.cost = cost
        self.dimension = len(cost)
        self.n = self.dimension
        self.mu = None
        self.x = None
        self.m = None

    @classmethod
    def read_tsplib95(cls, path):
        problem = tsplib95.load(path)
        cost = np.zeros([problem.dimension, problem.dimension], dtype=int)
        for i, start_node in enumerate(problem.get_nodes()):
            for j, end_node in enumerate(problem.get_nodes()):
                cost[i, j] = problem.get_weight(start_node, end_node)
        return cls(cost, problem)

    def create_solver(self, params: dict = None):
        self.m = gp.Model("MTZ - Strong")
        for param_name, new_val in params.items():
            self.m.setParam(param_name, new_val)

    def add_vars(self):
        self.x = {}
        self.mu = {}

        for i in range(self.dimension):
            self.mu[i] = self.m.addVar(lb=0, ub=self.n, vtype=GRB.CONTINUOUS, name="mu(%s)" % i)
            for j in range(self.dimension):
                if i == j:
                    continue
                self.x[i, j] = self.m.addVar(vtype=GRB.BINARY, name="x(%s,%s)" % (i, j))

        self.m.addConstr(self.mu[0] == 0, "mu[0]")

    def set_obj(self):
        self.m.setObjective(quicksum(self.cost[i, j] * self.x[i, j] for (i, j) in self.x), GRB.MINIMIZE)

    def add_in_out_cons(self):
        for i in range(self.dimension):
            self.m.addConstr(quicksum(self.x[i, j] for j in range(self.dimension) if j != i) == 1, "Out(%s)" % i)
            self.m.addConstr(quicksum(self.x[j, i] for j in range(self.dimension) if j != i) == 1, "In(%s)" % i)

    def add_lifted_mtz_cons(self):
        for i in range(self.dimension):
            for j in range(1, self.dimension):
                if i == j:
                    continue
                self.m.addConstr(self.mu[i] - self.mu[j] + (self.n - 1) * self.x[i, j] + (self.n - 3) * self.x[
                    j, i] <= self.n - 2, "LiftedMTZ(%s,%s)" % (i, j))
        for i in range(1, self.dimension):
            self.m.addConstr(-self.x[0, i] - self.mu[i] + (self.n - 3) * self.x[i, 0] <= -2, name="LiftedLB(%s)" % i)
            self.m.addConstr(-self.x[i, 0] + self.mu[i] + (self.n - 3) * self.x[0, i] <= self.n - 2,
                             name="LiftedUB(%s)" % i)

    def add_conss(self):
        self.add_in_out_cons()
        self.add_lifted_mtz_cons()

    def solve(self):
        self.m.optimize()
        return self.get_sol()

    def get_status(self):
        match self.m.status:
            case GRB.status.OPTIMAL:
                return 'OPTIMAL'
            case GRB.status.TIME_LIMIT:
                return 'TIME_LIMIT'

    def get_sol(self):
        status = self.get_status()
        gap = self.m.MIPGap
        lb = self.m.ObjBoundC
        ub = self.m.ObjVal
        runtime = self.m.Runtime

        return {'gap': gap, 'status': status, 'lb': lb, 'ub': ub, 'runtime': runtime}

    def make_model(self, params: dict = None):
        self.create_solver(params)
        self.add_vars()
        self.add_conss()
        self.set_obj()


if __name__ == '__main__':
    res_file = 'result_mtz_strong.csv'
    df = pd.DataFrame(columns=['Method', 'Name', '#cities', 'LB', 'UB', 'Run Time'])
    df.to_csv(res_file, index=False)
    for path in glob.glob('D:/Data/ALL_tsp/*.tsp'):
        print(path)
        m = MtzStrong.read_tsplib95(path)
        p = m.problem
        m.make_model({'OutputFlag': 1, 'TimeLimit': 600, 'MIPGap': 0})
        sol = m.solve()
        with open(res_file, 'a') as f:
            f.write(f"MTZ_STRONG,{p.name},{p.dimension},{sol['lb']},{sol['ub']},{sol['runtime']}\n")

LKH

Taillard & Helsgaun, 2019 : LKH3

首先看下目录结构:

LKH-3.0.9
├── ALL_tsp
│   ├── INSTANCES
│   │   └── Tsplib95
│   │       ├── a280.tsp
│   │       ├── ali535.tsp
│   │       └── vm1748.tsp
│   ├── LOG
│   ├── run_TSP
│   ├── test.txt
│   └── 旅行商问题(TSP)整数规划 VS 启发式.csv
├── LKH
├── Makefile
├── README.txt
└── SRC

run_TSP

#!/bin/bash
# Usage: ./run_TSP class name runs [ optimum ]

if [ -z "$3" ]; then
    echo "./run_TSP class name runs [ optimum ]"
    exit
fi

lkh="../LKH"
class=$1
name=$2
runs=$3
optimum=$4
par=TMP/$name.pid$$.par

mkdir -p TOURS
mkdir -p TOURS/$class
mkdir -p TMP
# mkdir -p SOLUTIONS
# mkdir -p SOLUTIONS/$class

echo "PROBLEM_FILE = INSTANCES/$class/$name.tsp" >> $par

echo "RUNS = $runs" >> $par
# echo "TOUR_FILE = TOURS/$class/$name.$.tour" >> $par
# echo "SINTEF_SOLUTION_FILE = SOLUTIONS/$class/$name.$.sol" >> $par
echo "SEED = 0" >> $par

if [ -n "$optimum" ]; then
    echo "OPTIMUM = $optimum" >> $par
fi

$lkh $par

/bin/rm -f $par

最后我是采用了python调用脚本的方式,个人感觉写起来方便一点,当然也可以用bash直接批量调用,仅供参考:

import os
import glob
import pandas as pd

os.chdir('/home/user/ClionProjects/LKH-3.0.9/ALL_tsp/INSTANCES/Tsplib95')
os.chdir('/home/user/ClionProjects/LKH-3.0.9/ALL_tsp')
os.getcwd()

df = pd.read_csv('旅行商问题(TSP)整数规划 VS 启发式.csv')
for i, row in df.iterrows():
    with os.popen(f'./run_TSP Tsplib95 {row.Name} 10 {row.Bounds}', "r") as p:
        r = p.read()
    with open(f'LOG/{row.Name}.txt', 'w') as f:
        f.write(r)

res = []
for path in glob.glob('LOG/*'):
    with open(path, 'r') as f:
        t = f.readlines()
    name = path[len('LOG/'):-len('.txt')]
    avg_time = float(t[-3].split(',')[1][len(' Time.avg = '):-len(' sec.')])
    total_time = float(t[-2][len('Time.total = '):-len(' sec.\n')])
    avg_cost = float(t[-6].split(',')[1][len(' Cost.avg = '):])
    min_cost = float(t[-6].split(',')[0][len('Cost.min = '):])
    res.append([name, avg_cost, avg_time, min_cost, total_time])
result = pd.DataFrame(res, columns=['name', 'avg_cost', 'avg_time', 'min_cost', 'total_time'])
result.to_csv('result.csv', index=False)

Concorde

目录结构如下,数据主要放在data下:

pyconcorde
├── COPYING
├── MANIFEST.in
├── README.md
├── build
├── concorde
│   ├── tests
│   │   ├── data
│   │   │   ├── a280.tsp
│   │   │   ├── ali535.tsp
│   │   │   └── vm1748.tsp
│   │   └── test_solution.py
│   ├── tsp.py
│   └── util.py
└── tools

最后我是采用了python调用脚本的方式,个人感觉写起来方便一点,仅供参考:

import glob
import os
import pandas as pd

if __name__ == '__main__':
    df = pd.read_csv('旅行商问题(TSP)整数规划 VS 启发式.csv')
    for i, row in df.iterrows():
        test_py = f'''from concorde.tsp import TSPSolver
from concorde.tests.data_utils import get_dataset_path

fname = get_dataset_path("{row.Name}")
solver = TSPSolver.from_tspfile(fname)
solution = solver.solve()'''
        with open(f'test.py', 'w') as f:
            f.write(test_py)
        with os.popen('python test.py', "r") as p:
            r = p.read()
        with open(f'LOG/{row.Name}.txt', 'w') as f:
            f.write(r)

    res = []
    for path in glob.glob('LOG/*'):
        with open(path, 'r') as f:
            t = f.read().split('\n')
        name = path[len('LOG/'):-len('.txt')]
        time = float(t[-2][len('Total Time to solve TSP: '):])
        cost = float(t[-3][len('Optimal tour: '):])
        res.append([name, cost, time])
    result = pd.DataFrame(res, columns=['name', 'cost', 'time'])
    result.to_csv('result.csv', index=False)

OrTools Routing

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

import os
import glob
import time
import numpy as np
import pandas as pd
import tsplib95


class OrToolsRouting:
    def __init__(self, cost: np.ndarray, problem=None):
        self.problem = problem
        self.cost = cost

    @classmethod
    def read_tsplib95(cls, path):
        problem = tsplib95.load(path)
        cost = np.zeros([problem.dimension, problem.dimension], dtype=int)
        for i, start_node in enumerate(problem.get_nodes()):
            for j, end_node in enumerate(problem.get_nodes()):
                cost[i, j] = problem.get_weight(start_node, end_node)
        return cls(cost, problem)

    @staticmethod
    def print_solution(manager, routing, solution):
        """Prints solution on console."""
        print(f"Objective: {solution.ObjectiveValue()} miles")
        index = routing.Start(0)
        plan_output = "Route for vehicle 0:\n"
        route_distance = 0
        while not routing.IsEnd(index):
            plan_output += f" {manager.IndexToNode(index)} ->"
            previous_index = index
            index = solution.Value(routing.NextVar(index))
            route_distance += routing.GetArcCostForVehicle(previous_index, index, 0)
        plan_output += f" {manager.IndexToNode(index)}\n"
        print(plan_output)
        plan_output += f"Route distance: {route_distance}miles\n"

    def run(self):
        # Create the routing index manager.
        manager = pywrapcp.RoutingIndexManager(len(self.cost), 1, 0)

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

        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 self.cost[from_node][to_node]

        transit_callback_index = routing.RegisterTransitCallback(distance_callback)

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

        # Setting first solution heuristic.
        search_parameters = pywrapcp.DefaultRoutingSearchParameters()
        search_parameters.first_solution_strategy = (routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)
        search_parameters.time_limit.seconds = 600

        # Solve the problem.
        s = time.time()
        solution = routing.SolveWithParameters(search_parameters)
        run_time = time.time() - s

        # Print solution on console.
        if solution:
            self.print_solution(manager, routing, solution)
            return solution.ObjectiveValue(), run_time


if __name__ == '__main__':
    res_file = 'result_ortools_routing.csv'
    for path in glob.glob('D:/Data/ALL_tsp/*.tsp'):
        print(path)
        if tsplib95.load(path).dimension > 1748:
            continue
        m = OrToolsRouting.read_tsplib95(path)
        try:
            obj, run_time = m.run()
        except:
            continue
        res = {'obj': obj, 'run_tim': run_time, 'name': m.problem.name, 'dimension': m.problem.dimension}
        if os.path.exists(res_file):
            pd.DataFrame([res]).to_csv(res_file, index=False, header=False, mode='a+')
        else:
            pd.DataFrame([res]).to_csv(res_file, index=False)

OrTools Sat

import glob
import multiprocessing
import os
import numpy as np
import pandas as pd
import tsplib95
from ortools.sat.python import cp_model


class OrToolsSat:
    def __init__(self, cost: np.ndarray, problem=None):
        self.solver = None
        self.problem = problem
        self.cost = cost
        self.dimension = len(cost)
        self.x = None
        self.m = None

    @classmethod
    def read_tsplib95(cls, path):
        problem = tsplib95.load(path)
        cost = np.zeros([problem.dimension, problem.dimension], dtype=int)
        for i, start_node in enumerate(problem.get_nodes()):
            for j, end_node in enumerate(problem.get_nodes()):
                cost[i, j] = problem.get_weight(start_node, end_node)
        return cls(cost, problem)

    def create_solver(self):
        self.m = cp_model.CpModel()

    def add_vars(self):
        self.x = {}
        for i in range(self.dimension):
            for j in range(self.dimension):
                if i == j:
                    continue
                self.x[i, j] = self.m.NewBoolVar('%i follows %i' % (j, i))

    def add_circuit_cons(self):
        self.m.AddCircuit([[i, j, x] for (i, j), x in self.x.items()])

    def add_conss(self):
        self.add_circuit_cons()

    def set_obj(self):
        self.m.Minimize(sum(self.cost[i, j] * self.x[i, j] for (i, j) in self.x))

    def solve(self):
        # Solve and print out the solution.
        solver = cp_model.CpSolver()
        solver.parameters.log_search_progress = False
        # To benefit from the linearization of the circuit constraint.
        solver.parameters.linearization_level = 2
        # Sets a time limit of 10 seconds.
        solver.parameters.max_time_in_seconds = 600
        # The number of parallel workers (i.e. threads) to use during search
        solver.parameters.num_search_workers = multiprocessing.cpu_count()

        solver.Solve(self.m)

        self.solver = solver

    def get_status(self):
        res = {}
        for s in self.solver.ResponseStats().split('\n')[1:-1]:
            k, v = s.split(':')
            res[k] = v[1:]
        return res

    def get_sol(self):
        current_node = 0
        str_route = '%i' % current_node
        route_is_finished = False
        route_distance = 0
        while not route_is_finished:
            for i in range(self.dimension):
                if i == current_node:
                    continue
                if self.solver.BooleanValue(self.x[current_node, i]):
                    str_route += ' -> %i' % i
                    route_distance += self.cost[current_node][i]
                    current_node = i
                    if current_node == 0:
                        route_is_finished = True
                    break

        print('Route:', str_route)
        print('Travelled distance:', route_distance)

    def run(self):
        self.create_solver()
        self.add_vars()
        self.add_conss()
        self.set_obj()
        self.solve()


if __name__ == '__main__':
    res_file = 'result_ortools_sat.csv'
    for path in glob.glob('D:/Data/ALL_tsp/*.tsp'):
        print(path)
        if tsplib95.load(path).dimension > 1748:
            continue
        m = OrToolsSat.read_tsplib95(path)
        try:
            m.run()
        except:
            continue
        status = m.get_status()
        status['Name'] = m.problem.name
        status['Dimension'] = m.problem.dimension
        if os.path.exists(res_file):
            pd.DataFrame([status]).to_csv(res_file, index=False, header=False, mode='a+')
        else:
            pd.DataFrame([status]).to_csv(res_file, index=False)

算法原理(待补充。。。敬请期待!!!)

min ⁡ ∑ ( i , j ) ∈ A c i j x i j s.t. ∑ j ∈ V , ( i , j ) ∈ A x i j = 1 , ∀ i ∈ V ∑ i ∈ V , ( i , j ) ∈ A x i j = 1 , ∀ j ∈ V ∑ j ∉ S , i ∈ S , ( i , j ) ∈ A x i j ≥ 1 , ∀ S ⊂ V , 2 ≤ ∣ S ∣ ≤ n − 1 x i j ∈ { 0 , 1 } , ∀ ( i , j ) ∈ A \begin{align} \min \quad &\sum_{(i, j)\in A} c_{ij}x_{ij}\\ \text{s.t.} \quad & \sum_{j \in V, (i,j)\in A} x_{ij} = 1, \quad \forall i \in V \\ & \sum_{i \in V, (i,j)\in A} x_{ij} = 1, \quad \forall j \in V \\ & \sum_{j\notin S, i\in S, (i,j)\in A} x_{ij} \ge 1, \quad \forall S \subset V, 2\le |S| \le n-1 \\ & x_{ij} \in \{0, 1\}, \quad \forall (i, j) \in A \end{align} mins.t.(i,j)AcijxijjV,(i,j)Axij=1,iViV,(i,j)Axij=1,jVj/S,iS,(i,j)Axij1,SV,2Sn1xij{0,1},(i,j)A

你可能感兴趣的:(MILP,OR-Tools,python,算法,启发式算法)