基础数学&计算数学,从事优化领域5年+,主要研究方向:MIP求解器、整数规划、随机规划、智能优化算法
本文以综述为主,将从常见的多种整数规划建模,如DFJ模型、MTZ模型等,使用Gurobi、Cplex、SCIP、Or-Tools、Cbc等常见求解器,并和常见的启发式LKH等给出性能分析报告,并浅谈其优缺点。
如有错误,欢迎指正。如有更好的算法,也欢迎交流!!!——@小猪快跑
TSP,即旅行商问题,又称TSP问题(Traveling Salesman Problem),是数学领域中著名问题之一。
假设有一个旅行商人要拜访n个城市,他必须选择所要走的路径,路径的限制是每个城市只能拜访一次,而且最后要回到原来出发的城市。路径的选择目标是要求所选的路径路程为所有路径中的最小值。
例如,下图显示 一个只有四个位置的 TSP,标记为 A、B、C 和 D。 任意两个位置之间的距离由边旁边的数字给出 加入他们。
通过计算所有可能路线的距离,您可以看到 最短路线是 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
使用著名的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] |
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")
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")
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)
目录结构如下,数据主要放在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)
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)
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)∈A∑cijxijj∈V,(i,j)∈A∑xij=1,∀i∈Vi∈V,(i,j)∈A∑xij=1,∀j∈Vj∈/S,i∈S,(i,j)∈A∑xij≥1,∀S⊂V,2≤∣S∣≤n−1xij∈{0,1},∀(i,j)∈A