当前位置:   article > 正文

【python】使用DOcplex解决CVRP问题【附源码】

docplex

一、CVRP问题简介

CVRP(Capacitated Vehicle Routing Problem)是一种组合优化问题,通常用于优化配送或运输问题。该问题的目标是有效地安排一组车辆,以满足一系列客户的需求,同时考虑车辆的容量限制。

以下是CVRP的一般介绍和要素:

  • 客户需求: 问题涉及到一组客户,每个客户对应一个配送点,有特定的需求量。需求可以表示为每个客户需要的货物数量。

  • 车辆: 问题还涉及到一组车辆,每辆车都有一个特定的容量限制,表示它可以携带的货物总量。

  • 路线: 目标是确定每辆车的路线,使得所有客户的需求都得到满足,并且每辆车的容量限制不被超过。车辆从一个中心点(通常是仓库)出发,访问所有客户后返回中心点。

  • 距离或成本: 优化问题通常还涉及到最小化总行驶距离或成本。这可以包括考虑道路网络、交通状况等因素。

  • 优化目标: 通常,优化的目标是最小化总成本,其中成本可以是行驶距离、时间、或其他相关因素。

解决CVRP一般采用高效的算法,例如启发式算法、遗传算法、模拟退火等。这是一个经典的组合优化问题,在物流和运输领域有很多实际应用,能够帮助提高运输效率、降低成本。

二、DOcplex库介绍

DOcplex是IBM在Python中提供的用于数学优化建模和求解的库。它是IBM Decision Optimization CPLEX Modeling for Python的一部分。这个库专注于提供一种方便的方式来定义和解决线性规划(LP)、整数规划(IP)和混合整数规划(MIP)等数学优化问题。

以下是DOcplex库的一些主要特点和介绍:

  1. 与CPLEX集成: DOcplex库是与IBM CPLEX(Optimization Studio中的求解器之一)紧密集成的。CPLEX是一个高性能的数学优化引擎,专注于解决线性规划、整数规划和混合整数规划问题。

  2. 优雅的建模语言: DOcplex提供了一种优雅的建模语言,使用户能够以自然的方式描述优化问题。这种建模语言允许用户指定目标函数、约束条件和变量,并且在底层使用CPLEX进行求解。

  3. 支持多种问题类型: 除了线性规划和整数规划,DOcplex还支持二次规划(Quadratic Programming)和混合整数二次规划(MIQP)等问题类型。

  4. 集成了约束编程: 除了数学规划,DOcplex还集成了约束编程。这使得它成为一个强大的工具,可以处理多种类型的优化问题。

  5. 与Jupyter Notebook兼容: DOcplex库可以轻松集成到Jupyter Notebook中,支持交互式建模和求解。

  6. 社区和文档: DOcplex有一个积极的社区,提供了详细的文档和示例,以帮助用户更好地理解和使用这个库。

总的来说,cplex是一个商业求解器,以分支定界和割平面理论构建了最优化问题的精确求解算法。DOcplex是IBM公司为了cplex引擎的通用化发展而在cplex Python API的基础上进行二次封装得到的工具库。使用该库构建和解决问题的流程具有普适性,同时求解速度与直接调用cplex API大同小异。IBM公司对cplex API的使用以及DOcplex库的使用都建立了实例文件,详见:Examples of CPLEX - IBM Documentation,然而恕我直言,初学者需要花费大量的精力才能看懂。

三、使用DOcplex解决CVRP问题

为了学习DOcplex的使用,我决定将经典的CVRP问题用这玩意重新写一遍。

1、DOcplex 安装

第一步,需要安装cplex。可以直接去这里:Site,用教育邮箱注册一个账户然后下载安装。(教育免费用,免费万岁)

然后

pip install docplex

2、 新建文件,导入必要的包

  1. from docplex.mp.model import Model
  2. import time
  3. import numpy as np
  4. import pandas as pd

 3、创建问题类,导入数据

  1. class Problem:
  2. def __init__(self, file_path):
  3. # node data file path
  4. self.file_path = file_path
  5. # some parameter
  6. # the number of vehicle
  7. self.m = 20
  8. # vehicle capacity
  9. self.Q = 200
  10. # node number
  11. self.n = 0
  12. # some matrix
  13. # node data
  14. self.data = np.array([])
  15. # node distance
  16. self.distance = np.array([])
  17. # demand matrix
  18. self.demand = np.array([])
  19. def lord_data(self):
  20. """
  21. lord the node data
  22. get demand and node number
  23. :return: None
  24. """
  25. df = pd.read_excel(self.file_path)
  26. self.data = df.values
  27. self.demand = self.data[:, 3].copy().reshape(1, -1)[0]
  28. self.n = len(self.data)
  29. def calcu_distance_method(self, i, j):
  30. """
  31. distance function
  32. :param i: index of first node
  33. :param j: index of second node
  34. :return: distance between i and j
  35. """
  36. if i == j:
  37. return 1000000
  38. x1 = self.data[i, 1]
  39. y1 = self.data[i, 2]
  40. x2 = self.data[j, 1]
  41. y2 = self.data[j, 2]
  42. dis = np.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2)
  43. return dis
  44. def calcu_distance_matrix(self):
  45. """
  46. calculate distance matrix
  47. :return: None
  48. """
  49. dis = np.zeros((len(self.data), len(self.data)))
  50. for i in range(len(self.data)):
  51. for j in range(len(self.data)):
  52. dis[i, j] = self.calcu_distance_method(i, j)
  53. self.distance = dis

问题类主要用于在建模过程中关键参数的提供。

4、构建算法

严格按照线性规划的显式公式进行算法构建,包括构建目标函数,构建约束条件等。对于CVRP问题,目标函数是运输成本(运输距离)最小化,约束包括唯一服务约束、流量平衡约束、子回路消除约束、需求满足约束等。其中最难搞的是子回路消除约束,本文中使用通用的MTZ约束方法来消除子回路。算法对应的CVRP模型公式如下:

(1)目标函数

min \sum_{k=1}^{K}\sum_{i=0}^{N}\sum_{j=0}^{N}c_{ij}x_{ij}^k

(2)每个点只被一辆车服务,每个点均被服务

\sum_{k=1}^{K}\sum_{i=1}^{N}x_{ij}^k = 1, \forall j = 1,...,N;

(3)MTZ消除子回路约束

\mu_{i}^{k}-\mu_{j}^{k} + Mx_{ij}^k \leq M-1,\forall i = 1,...,N; \forall j = 1,...,N; \forall k = 1,...,K;

(4)到达n点的路径数等于从n点出发的路径数

\sum_{i=1}^{N}x_{in}^k =\sum_{i=1}^{N}x_{nj}^k, \forall n = 1,...,N; \forall k =1,...,K;

(5)每条路径的终点必须是原点

\sum_{i=1}^{N}x_{i0}^k = 1, \forall k =1,...,N;

(6)每条路径的起点必须是原点

\sum_{j=1}^{N}x_{0j}^k = 1, \forall k =1,...,N;

(7)每条路径上的需求总量不超过车最大载重

\sum_{j=1}^Nd_jx_{ij}^k \leq Q, \forall i = 1,...,N; \forall k = 1,...,K;

算法构建如下,对应的约束和目标函数都加了注释,虽然是我蹩脚的英文注释。

  1. def create_program(problem):
  2. """
  3. create program and solve the problem
  4. :param problem: problem object
  5. :return: None
  6. """
  7. # instantiate
  8. model = Model("cvrp")
  9. # define variables
  10. X = model.binary_var_cube(np.arange(problem.m), np.arange(problem.n), np.arange(problem.n), name="X")
  11. mu = model.integer_var_matrix(np.arange(problem.m), np.arange(problem.n), name="Mu", lb=0, ub=100)
  12. # define objective function
  13. sum_dis = 0
  14. for k in range(problem.m):
  15. for i in range(problem.n):
  16. for j in range(problem.n):
  17. if i == j:
  18. sum_dis += 100000
  19. sum_dis += problem.distance[i][j] * X[(k, i, j)]
  20. model.minimize(sum_dis)
  21. # another method
  22. # model.maximize(model.sum(problem.distance[i][j] * X[(k, i, j)] for k in range(problem.m) for i in range(problem.n)
  23. # for j in range(problem.n)))
  24. # constraint 1: each node can only be accessed once
  25. for i in range(1, problem.n):
  26. cons = 0
  27. for k in range(problem.m):
  28. for j in range(problem.n):
  29. cons += X[(k, i, j)]
  30. model.add_constraint(cons == 1)
  31. # constraint 2: MTZ constraint
  32. for k in range(problem.m):
  33. for i in range(problem.n):
  34. for j in range(problem.n):
  35. if i != j and i != 0 and j != 0:
  36. model.add_indicator(X[(k, i, j)], mu[(k, j)] - mu[(k, i)] == 1)
  37. # constraint 3: path constraint
  38. for k in range(problem.m):
  39. for n in range(1, problem.n):
  40. cons1 = 0
  41. cons2 = 0
  42. for i in range(problem.n):
  43. if i != n:
  44. cons1 += X[(k, i, n)]
  45. for j in range(problem.n):
  46. if j != n:
  47. cons2 += X[(k, n, j)]
  48. model.add_constraint(cons1 == cons2)
  49. # constraint 4: origin constraint
  50. for k in range(problem.m):
  51. cons = 0
  52. for j in range(1, problem.n):
  53. cons += X[(k, 0, j)]
  54. model.add_constraint(cons == 1)
  55. # constraint 5: destination constraint
  56. for k in range(problem.m):
  57. cons = 0
  58. for i in range(1, problem.n):
  59. cons += X[(k, i, 0)]
  60. model.add_constraint(cons == 1)
  61. # constraint 6: capacity constraint
  62. for k in range(problem.m):
  63. cons = 0
  64. for i in range(problem.n):
  65. for j in range(1, problem.n):
  66. cons += X[(k, i, j)] * problem.demand[j]
  67. model.add_constraint(cons <= problem.Q)
  68. start_time = time.time()
  69. sol = model.solve()
  70. end_time = time.time()
  71. print(sol)
  72. print("spend time:", end_time - start_time)

5、求解

  1. if __name__ == "__main__":
  2. # define the problem
  3. p = Problem("cvrp.xlsx")
  4. p.lord_data()
  5. p.calcu_distance_matrix()
  6. # create the solving program
  7. create_program(p)

6、数据

自己新建一个excel文件,取名“cvrp.xlsx”,把这一堆复制进去,文件放在与代码同一个目录下。(够详细了吧)

idx_coordy_coorddemand
140500
2258520
3227530
4228510
5208040
6208520
7187520
8157520
9158010
10103520
11104030
1284040
1384520
1453510
1554510
1624020
1704020
1804520
1944520
20421040
21421510
2240510
23401540
2438530
25381510
2635520
27953030
28953520
29923010
30903510
31883010
32883520
33873010
34852510
35853530
36678520
37658540
38658210
39628030
40608010
41608530
42587520
43558010
44558520
45558210
46208210
47188010
4824510
4942510
50421210
51723530
52552019
5325303
5420505
55556016
56306016
57503519
58302523
59151020
60102019
61156017
6245659
6365353
6465206
65453017
66354016
67413716
6864429
69406021
70315227
71356923
72655514
7363658
742605
7520208
765516
77601231
782337
7985627
8066830
81474713
82495810
8327439
84373114
85572918
8663232
87212428
88122413
89245819
9067525
9137476
92494213
93534314
9461523
95574823
9656376
97555426
9841835
9926529
100263515
10131673

7、结果

solution for: cvrp
objective: 2.02002e+08
status: OPTIMAL_SOLUTION(2)


spend time: 186.94465684890747

8、分析

首先,用该代码求出的是精确解,在论文中比用启发式算法认可度高了不知道多少倍。其次,100个顾客点的CVRP问题才跑了187秒,速度还是可以的。要知道很多论文的算法算例特别小,就是因为他们用的精确算法,而本身模型复杂度贼高,导致算例大了就跑不出来(可以忍受的时间之内)。总结,cplex牛批,省去我手撸单纯形法写分支定界了。

四、展望

下期预告,过去的作业,手撸单纯形法。

五、附完整代码

  1. from docplex.mp.model import Model
  2. import time
  3. import numpy as np
  4. import pandas as pd
  5. # create by Grasshopper9527
  6. class Problem:
  7. def __init__(self, file_path):
  8. # node data file path
  9. self.file_path = file_path
  10. # some parameter
  11. # the maximum number of vehicle
  12. self.m = 20
  13. # vehicle capacity
  14. self.Q = 200
  15. # node number
  16. self.n = 0
  17. # some matrix
  18. # node data
  19. self.data = np.array([])
  20. # node distance
  21. self.distance = np.array([])
  22. # demand matrix
  23. self.demand = np.array([])
  24. def lord_data(self):
  25. """
  26. lord the node data
  27. get demand and node number
  28. :return: None
  29. """
  30. df = pd.read_excel(self.file_path)
  31. self.data = df.values
  32. self.demand = self.data[:, 3].copy().reshape(1, -1)[0]
  33. self.n = len(self.data)
  34. def calcu_distance_method(self, i, j):
  35. """
  36. distance function
  37. :param i: index of first node
  38. :param j: index of second node
  39. :return: distance between i and j
  40. """
  41. if i == j:
  42. return 1000000
  43. x1 = self.data[i, 1]
  44. y1 = self.data[i, 2]
  45. x2 = self.data[j, 1]
  46. y2 = self.data[j, 2]
  47. dis = np.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2)
  48. return dis
  49. def calcu_distance_matrix(self):
  50. """
  51. calculate distance matrix
  52. :return: None
  53. """
  54. dis = np.zeros((len(self.data), len(self.data)))
  55. for i in range(len(self.data)):
  56. for j in range(len(self.data)):
  57. dis[i, j] = self.calcu_distance_method(i, j)
  58. self.distance = dis
  59. def create_program(problem):
  60. """
  61. create program and solve the problem
  62. :param problem: problem object
  63. :return: None
  64. """
  65. # instantiate
  66. model = Model("cvrp")
  67. # define variables
  68. X = model.binary_var_cube(np.arange(problem.m), np.arange(problem.n), np.arange(problem.n), name="X")
  69. mu = model.integer_var_matrix(np.arange(problem.m), np.arange(problem.n), name="Mu", lb=0, ub=100)
  70. # define objective function
  71. sum_dis = 0
  72. for k in range(problem.m):
  73. for i in range(problem.n):
  74. for j in range(problem.n):
  75. if i == j:
  76. sum_dis += 100000
  77. sum_dis += problem.distance[i][j] * X[(k, i, j)]
  78. model.minimize(sum_dis)
  79. # another method
  80. # model.maximize(model.sum(problem.distance[i][j] * X[(k, i, j)] for k in range(problem.m) for i in range(problem.n)
  81. # for j in range(problem.n)))
  82. # each node can only be accessed once
  83. for i in range(1, problem.n):
  84. cons = 0
  85. for k in range(problem.m):
  86. for j in range(problem.n):
  87. cons += X[(k, i, j)]
  88. model.add_constraint(cons == 1)
  89. # i != j
  90. """for k in range(problem.m):
  91. for i in range(problem.n):
  92. for j in range(problem.n):
  93. if i == j:
  94. model.add_constraint(X[(k, i, j)] == 0)"""
  95. # MTZ constraint
  96. for k in range(problem.m):
  97. for i in range(problem.n):
  98. for j in range(problem.n):
  99. if i != j and i != 0 and j != 0:
  100. model.add_indicator(X[(k, i, j)], mu[(k, j)] - mu[(k, i)] == 1)
  101. """for k in range(problem.m):
  102. for i in range(problem.n):
  103. for j in range(problem.n):
  104. if i != j and i != 0 and j != 0:
  105. model.add_indicator(X[(k, i, j)], mu[(k, i)] - mu[(k, j)] + problem.n * X[(k, i, j)]
  106. <= problem.n - 1)"""
  107. # path constraint
  108. for k in range(problem.m):
  109. for n in range(1, problem.n):
  110. cons1 = 0
  111. cons2 = 0
  112. for i in range(problem.n):
  113. if i != n:
  114. cons1 += X[(k, i, n)]
  115. for j in range(problem.n):
  116. if j != n:
  117. cons2 += X[(k, n, j)]
  118. model.add_constraint(cons1 == cons2)
  119. # origin constraint
  120. for k in range(problem.m):
  121. cons = 0
  122. for j in range(1, problem.n):
  123. cons += X[(k, 0, j)]
  124. model.add_constraint(cons == 1)
  125. # destination constraint
  126. for k in range(problem.m):
  127. cons = 0
  128. for i in range(1, problem.n):
  129. cons += X[(k, i, 0)]
  130. model.add_constraint(cons == 1)
  131. # capacity constraint
  132. for k in range(problem.m):
  133. cons = 0
  134. for i in range(problem.n):
  135. for j in range(1, problem.n):
  136. cons += X[(k, i, j)] * problem.demand[j]
  137. model.add_constraint(cons <= problem.Q)
  138. start_time = time.time()
  139. sol = model.solve()
  140. end_time = time.time()
  141. print(sol)
  142. print("spend time:", end_time - start_time)
  143. if __name__ == "__main__":
  144. # define the problem
  145. p = Problem("cvrp.xlsx")
  146. p.lord_data()
  147. p.calcu_distance_matrix()
  148. # create the solving program
  149. create_program(p)

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/593205
推荐阅读
相关标签
  

闽ICP备14008679号