赞
踩
CVRP(Capacitated Vehicle Routing Problem)是一种组合优化问题,通常用于优化配送或运输问题。该问题的目标是有效地安排一组车辆,以满足一系列客户的需求,同时考虑车辆的容量限制。
以下是CVRP的一般介绍和要素:
客户需求: 问题涉及到一组客户,每个客户对应一个配送点,有特定的需求量。需求可以表示为每个客户需要的货物数量。
车辆: 问题还涉及到一组车辆,每辆车都有一个特定的容量限制,表示它可以携带的货物总量。
路线: 目标是确定每辆车的路线,使得所有客户的需求都得到满足,并且每辆车的容量限制不被超过。车辆从一个中心点(通常是仓库)出发,访问所有客户后返回中心点。
距离或成本: 优化问题通常还涉及到最小化总行驶距离或成本。这可以包括考虑道路网络、交通状况等因素。
优化目标: 通常,优化的目标是最小化总成本,其中成本可以是行驶距离、时间、或其他相关因素。
解决CVRP一般采用高效的算法,例如启发式算法、遗传算法、模拟退火等。这是一个经典的组合优化问题,在物流和运输领域有很多实际应用,能够帮助提高运输效率、降低成本。
DOcplex
是IBM在Python中提供的用于数学优化建模和求解的库。它是IBM Decision Optimization CPLEX Modeling for Python的一部分。这个库专注于提供一种方便的方式来定义和解决线性规划(LP)、整数规划(IP)和混合整数规划(MIP)等数学优化问题。
以下是DOcplex
库的一些主要特点和介绍:
与CPLEX集成: DOcplex
库是与IBM CPLEX(Optimization Studio中的求解器之一)紧密集成的。CPLEX是一个高性能的数学优化引擎,专注于解决线性规划、整数规划和混合整数规划问题。
优雅的建模语言: DOcplex
提供了一种优雅的建模语言,使用户能够以自然的方式描述优化问题。这种建模语言允许用户指定目标函数、约束条件和变量,并且在底层使用CPLEX进行求解。
支持多种问题类型: 除了线性规划和整数规划,DOcplex
还支持二次规划(Quadratic Programming)和混合整数二次规划(MIQP)等问题类型。
集成了约束编程: 除了数学规划,DOcplex
还集成了约束编程。这使得它成为一个强大的工具,可以处理多种类型的优化问题。
与Jupyter Notebook兼容: DOcplex
库可以轻松集成到Jupyter Notebook中,支持交互式建模和求解。
社区和文档: DOcplex
有一个积极的社区,提供了详细的文档和示例,以帮助用户更好地理解和使用这个库。
总的来说,cplex是一个商业求解器,以分支定界和割平面理论构建了最优化问题的精确求解算法。DOcplex是IBM公司为了cplex引擎的通用化发展而在cplex Python API的基础上进行二次封装得到的工具库。使用该库构建和解决问题的流程具有普适性,同时求解速度与直接调用cplex API大同小异。IBM公司对cplex API的使用以及DOcplex库的使用都建立了实例文件,详见:Examples of CPLEX - IBM Documentation,然而恕我直言,初学者需要花费大量的精力才能看懂。
为了学习DOcplex的使用,我决定将经典的CVRP问题用这玩意重新写一遍。
第一步,需要安装cplex。可以直接去这里:Site,用教育邮箱注册一个账户然后下载安装。(教育免费用,免费万岁)
然后
pip install docplex
- from docplex.mp.model import Model
- import time
- import numpy as np
- import pandas as pd
- class Problem:
- def __init__(self, file_path):
-
- # node data file path
- self.file_path = file_path
-
- # some parameter
- # the number of vehicle
- self.m = 20
- # vehicle capacity
- self.Q = 200
- # node number
- self.n = 0
-
- # some matrix
- # node data
- self.data = np.array([])
- # node distance
- self.distance = np.array([])
- # demand matrix
- self.demand = np.array([])
-
- def lord_data(self):
- """
- lord the node data
- get demand and node number
- :return: None
- """
- df = pd.read_excel(self.file_path)
- self.data = df.values
- self.demand = self.data[:, 3].copy().reshape(1, -1)[0]
- self.n = len(self.data)
-
- def calcu_distance_method(self, i, j):
- """
- distance function
- :param i: index of first node
- :param j: index of second node
- :return: distance between i and j
- """
- if i == j:
- return 1000000
- x1 = self.data[i, 1]
- y1 = self.data[i, 2]
- x2 = self.data[j, 1]
- y2 = self.data[j, 2]
- dis = np.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2)
- return dis
-
- def calcu_distance_matrix(self):
- """
- calculate distance matrix
- :return: None
- """
- dis = np.zeros((len(self.data), len(self.data)))
- for i in range(len(self.data)):
- for j in range(len(self.data)):
- dis[i, j] = self.calcu_distance_method(i, j)
- self.distance = dis
问题类主要用于在建模过程中关键参数的提供。
严格按照线性规划的显式公式进行算法构建,包括构建目标函数,构建约束条件等。对于CVRP问题,目标函数是运输成本(运输距离)最小化,约束包括唯一服务约束、流量平衡约束、子回路消除约束、需求满足约束等。其中最难搞的是子回路消除约束,本文中使用通用的MTZ约束方法来消除子回路。算法对应的CVRP模型公式如下:
算法构建如下,对应的约束和目标函数都加了注释,虽然是我蹩脚的英文注释。
- def create_program(problem):
- """
- create program and solve the problem
- :param problem: problem object
- :return: None
- """
-
- # instantiate
- model = Model("cvrp")
-
- # define variables
- X = model.binary_var_cube(np.arange(problem.m), np.arange(problem.n), np.arange(problem.n), name="X")
- mu = model.integer_var_matrix(np.arange(problem.m), np.arange(problem.n), name="Mu", lb=0, ub=100)
-
- # define objective function
- sum_dis = 0
- for k in range(problem.m):
- for i in range(problem.n):
- for j in range(problem.n):
- if i == j:
- sum_dis += 100000
- sum_dis += problem.distance[i][j] * X[(k, i, j)]
- model.minimize(sum_dis)
-
- # another method
- # model.maximize(model.sum(problem.distance[i][j] * X[(k, i, j)] for k in range(problem.m) for i in range(problem.n)
- # for j in range(problem.n)))
-
- # constraint 1: each node can only be accessed once
- for i in range(1, problem.n):
- cons = 0
- for k in range(problem.m):
- for j in range(problem.n):
- cons += X[(k, i, j)]
- model.add_constraint(cons == 1)
-
- # constraint 2: MTZ constraint
- for k in range(problem.m):
- for i in range(problem.n):
- for j in range(problem.n):
- if i != j and i != 0 and j != 0:
- model.add_indicator(X[(k, i, j)], mu[(k, j)] - mu[(k, i)] == 1)
-
- # constraint 3: path constraint
- for k in range(problem.m):
- for n in range(1, problem.n):
- cons1 = 0
- cons2 = 0
- for i in range(problem.n):
- if i != n:
- cons1 += X[(k, i, n)]
- for j in range(problem.n):
- if j != n:
- cons2 += X[(k, n, j)]
- model.add_constraint(cons1 == cons2)
-
- # constraint 4: origin constraint
- for k in range(problem.m):
- cons = 0
- for j in range(1, problem.n):
- cons += X[(k, 0, j)]
- model.add_constraint(cons == 1)
-
- # constraint 5: destination constraint
- for k in range(problem.m):
- cons = 0
- for i in range(1, problem.n):
- cons += X[(k, i, 0)]
- model.add_constraint(cons == 1)
-
- # constraint 6: capacity constraint
- for k in range(problem.m):
- cons = 0
- for i in range(problem.n):
- for j in range(1, problem.n):
- cons += X[(k, i, j)] * problem.demand[j]
- model.add_constraint(cons <= problem.Q)
- start_time = time.time()
- sol = model.solve()
- end_time = time.time()
- print(sol)
- print("spend time:", end_time - start_time)
- if __name__ == "__main__":
- # define the problem
- p = Problem("cvrp.xlsx")
- p.lord_data()
- p.calcu_distance_matrix()
-
- # create the solving program
- create_program(p)
自己新建一个excel文件,取名“cvrp.xlsx”,把这一堆复制进去,文件放在与代码同一个目录下。(够详细了吧)
id | x_coord | y_coord | demand |
1 | 40 | 50 | 0 |
2 | 25 | 85 | 20 |
3 | 22 | 75 | 30 |
4 | 22 | 85 | 10 |
5 | 20 | 80 | 40 |
6 | 20 | 85 | 20 |
7 | 18 | 75 | 20 |
8 | 15 | 75 | 20 |
9 | 15 | 80 | 10 |
10 | 10 | 35 | 20 |
11 | 10 | 40 | 30 |
12 | 8 | 40 | 40 |
13 | 8 | 45 | 20 |
14 | 5 | 35 | 10 |
15 | 5 | 45 | 10 |
16 | 2 | 40 | 20 |
17 | 0 | 40 | 20 |
18 | 0 | 45 | 20 |
19 | 44 | 5 | 20 |
20 | 42 | 10 | 40 |
21 | 42 | 15 | 10 |
22 | 40 | 5 | 10 |
23 | 40 | 15 | 40 |
24 | 38 | 5 | 30 |
25 | 38 | 15 | 10 |
26 | 35 | 5 | 20 |
27 | 95 | 30 | 30 |
28 | 95 | 35 | 20 |
29 | 92 | 30 | 10 |
30 | 90 | 35 | 10 |
31 | 88 | 30 | 10 |
32 | 88 | 35 | 20 |
33 | 87 | 30 | 10 |
34 | 85 | 25 | 10 |
35 | 85 | 35 | 30 |
36 | 67 | 85 | 20 |
37 | 65 | 85 | 40 |
38 | 65 | 82 | 10 |
39 | 62 | 80 | 30 |
40 | 60 | 80 | 10 |
41 | 60 | 85 | 30 |
42 | 58 | 75 | 20 |
43 | 55 | 80 | 10 |
44 | 55 | 85 | 20 |
45 | 55 | 82 | 10 |
46 | 20 | 82 | 10 |
47 | 18 | 80 | 10 |
48 | 2 | 45 | 10 |
49 | 42 | 5 | 10 |
50 | 42 | 12 | 10 |
51 | 72 | 35 | 30 |
52 | 55 | 20 | 19 |
53 | 25 | 30 | 3 |
54 | 20 | 50 | 5 |
55 | 55 | 60 | 16 |
56 | 30 | 60 | 16 |
57 | 50 | 35 | 19 |
58 | 30 | 25 | 23 |
59 | 15 | 10 | 20 |
60 | 10 | 20 | 19 |
61 | 15 | 60 | 17 |
62 | 45 | 65 | 9 |
63 | 65 | 35 | 3 |
64 | 65 | 20 | 6 |
65 | 45 | 30 | 17 |
66 | 35 | 40 | 16 |
67 | 41 | 37 | 16 |
68 | 64 | 42 | 9 |
69 | 40 | 60 | 21 |
70 | 31 | 52 | 27 |
71 | 35 | 69 | 23 |
72 | 65 | 55 | 14 |
73 | 63 | 65 | 8 |
74 | 2 | 60 | 5 |
75 | 20 | 20 | 8 |
76 | 5 | 5 | 16 |
77 | 60 | 12 | 31 |
78 | 23 | 3 | 7 |
79 | 8 | 56 | 27 |
80 | 6 | 68 | 30 |
81 | 47 | 47 | 13 |
82 | 49 | 58 | 10 |
83 | 27 | 43 | 9 |
84 | 37 | 31 | 14 |
85 | 57 | 29 | 18 |
86 | 63 | 23 | 2 |
87 | 21 | 24 | 28 |
88 | 12 | 24 | 13 |
89 | 24 | 58 | 19 |
90 | 67 | 5 | 25 |
91 | 37 | 47 | 6 |
92 | 49 | 42 | 13 |
93 | 53 | 43 | 14 |
94 | 61 | 52 | 3 |
95 | 57 | 48 | 23 |
96 | 56 | 37 | 6 |
97 | 55 | 54 | 26 |
98 | 4 | 18 | 35 |
99 | 26 | 52 | 9 |
100 | 26 | 35 | 15 |
101 | 31 | 67 | 3 |
solution for: cvrp
objective: 2.02002e+08
status: OPTIMAL_SOLUTION(2)
略
spend time: 186.94465684890747
首先,用该代码求出的是精确解,在论文中比用启发式算法认可度高了不知道多少倍。其次,100个顾客点的CVRP问题才跑了187秒,速度还是可以的。要知道很多论文的算法算例特别小,就是因为他们用的精确算法,而本身模型复杂度贼高,导致算例大了就跑不出来(可以忍受的时间之内)。总结,cplex牛批,省去我手撸单纯形法写分支定界了。
下期预告,过去的作业,手撸单纯形法。
- from docplex.mp.model import Model
- import time
- import numpy as np
- import pandas as pd
-
-
- # create by Grasshopper9527
- class Problem:
- def __init__(self, file_path):
-
- # node data file path
- self.file_path = file_path
-
- # some parameter
- # the maximum number of vehicle
- self.m = 20
- # vehicle capacity
- self.Q = 200
- # node number
- self.n = 0
-
- # some matrix
- # node data
- self.data = np.array([])
- # node distance
- self.distance = np.array([])
- # demand matrix
- self.demand = np.array([])
-
- def lord_data(self):
- """
- lord the node data
- get demand and node number
- :return: None
- """
- df = pd.read_excel(self.file_path)
- self.data = df.values
- self.demand = self.data[:, 3].copy().reshape(1, -1)[0]
- self.n = len(self.data)
-
- def calcu_distance_method(self, i, j):
- """
- distance function
- :param i: index of first node
- :param j: index of second node
- :return: distance between i and j
- """
- if i == j:
- return 1000000
- x1 = self.data[i, 1]
- y1 = self.data[i, 2]
- x2 = self.data[j, 1]
- y2 = self.data[j, 2]
- dis = np.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2)
- return dis
-
- def calcu_distance_matrix(self):
- """
- calculate distance matrix
- :return: None
- """
- dis = np.zeros((len(self.data), len(self.data)))
- for i in range(len(self.data)):
- for j in range(len(self.data)):
- dis[i, j] = self.calcu_distance_method(i, j)
- self.distance = dis
-
-
- def create_program(problem):
- """
- create program and solve the problem
- :param problem: problem object
- :return: None
- """
-
- # instantiate
- model = Model("cvrp")
-
- # define variables
- X = model.binary_var_cube(np.arange(problem.m), np.arange(problem.n), np.arange(problem.n), name="X")
- mu = model.integer_var_matrix(np.arange(problem.m), np.arange(problem.n), name="Mu", lb=0, ub=100)
-
- # define objective function
- sum_dis = 0
- for k in range(problem.m):
- for i in range(problem.n):
- for j in range(problem.n):
- if i == j:
- sum_dis += 100000
- sum_dis += problem.distance[i][j] * X[(k, i, j)]
- model.minimize(sum_dis)
-
- # another method
- # model.maximize(model.sum(problem.distance[i][j] * X[(k, i, j)] for k in range(problem.m) for i in range(problem.n)
- # for j in range(problem.n)))
-
- # each node can only be accessed once
- for i in range(1, problem.n):
- cons = 0
- for k in range(problem.m):
- for j in range(problem.n):
- cons += X[(k, i, j)]
- model.add_constraint(cons == 1)
-
- # i != j
- """for k in range(problem.m):
- for i in range(problem.n):
- for j in range(problem.n):
- if i == j:
- model.add_constraint(X[(k, i, j)] == 0)"""
- # MTZ constraint
- for k in range(problem.m):
- for i in range(problem.n):
- for j in range(problem.n):
- if i != j and i != 0 and j != 0:
- model.add_indicator(X[(k, i, j)], mu[(k, j)] - mu[(k, i)] == 1)
- """for k in range(problem.m):
- for i in range(problem.n):
- for j in range(problem.n):
- if i != j and i != 0 and j != 0:
- model.add_indicator(X[(k, i, j)], mu[(k, i)] - mu[(k, j)] + problem.n * X[(k, i, j)]
- <= problem.n - 1)"""
-
- # path constraint
- for k in range(problem.m):
- for n in range(1, problem.n):
- cons1 = 0
- cons2 = 0
- for i in range(problem.n):
- if i != n:
- cons1 += X[(k, i, n)]
- for j in range(problem.n):
- if j != n:
- cons2 += X[(k, n, j)]
- model.add_constraint(cons1 == cons2)
-
- # origin constraint
- for k in range(problem.m):
- cons = 0
- for j in range(1, problem.n):
- cons += X[(k, 0, j)]
- model.add_constraint(cons == 1)
-
- # destination constraint
- for k in range(problem.m):
- cons = 0
- for i in range(1, problem.n):
- cons += X[(k, i, 0)]
- model.add_constraint(cons == 1)
-
- # capacity constraint
- for k in range(problem.m):
- cons = 0
- for i in range(problem.n):
- for j in range(1, problem.n):
- cons += X[(k, i, j)] * problem.demand[j]
- model.add_constraint(cons <= problem.Q)
- start_time = time.time()
- sol = model.solve()
- end_time = time.time()
- print(sol)
- print("spend time:", end_time - start_time)
-
-
- if __name__ == "__main__":
- # define the problem
- p = Problem("cvrp.xlsx")
- p.lord_data()
- p.calcu_distance_matrix()
-
- # create the solving program
- create_program(p)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。