虽然很忙,挤出时间也要写啊!
CVRP (capacitated vehicle routing problem)
首先,我们来描述CVRP。车辆从仓库出发,经过多个顾客之后,再回到仓库。
车辆的最大载重量 (容量) 已知。
顾客的位置已知,并且各个顾客的需求量已知。
各地点之间的移动费用已知
一条路径的顾客需求量不能超过车辆的最大载重量。
车辆种类假定有一种,且车辆数提前设定。
Modeling
这里我们开始建模。
首先,我们设定车辆数为
,点 (仓库和顾客) 的数为
。顾客
的需求里为
。
其次,车辆
的载重量上限为
。一般的情况下,我们假定顾客需求量的最大值
不超过
。
然后,车辆从点
到点
的移动费用为
,并且我们假定
。
最后,我们给定变量
,如果对于和仓库不相接的枝,车辆通过的时候
,否则,
,但是,如果从仓库向某个点
移动之后,立马返回仓库的时候,
。
经过以上准备我们可以得到以下模型:
第一个约束条件是从仓库有
台车辆,第二个约束条件保证对于各个顾客有一辆车辆访问。第三个约束条件是禁止车辆的容量约束和部分巡回路。
这里
为了满足顾客的部分集合
内的需求,必要的车辆数。为了计算
,我们通常用以下的下界代用。
Code
下面我们给出代码。
"""vrp.py: model for the vehicle routing problem using callback for adding cuts.approach:- start with assignment model- add cuts until all components of the graph are connectedreference: Prof. KUBO's code"""
import math
import random
import networkx
from gurobipy import *
def vrp(V,c,m,q,Q):
"""solve_vrp -- solve the vehicle routing problem.- start with assignment model (depot has a special status)- add cuts until all components of the graph are connectedParameters:- V: set/list of nodes in the graph- c[i,j]: cost for traversing edge (i,j)- m: number of vehicles available- q[i]: demand for customer i- Q: vehicle capacityReturns the optimum objective value and the list of edges used."""
def vrp_callback(model,where):
"""vrp_callback: add constraint to eliminate infeasible solutionsParameters: gurobi standard:- model: current model- where: indicator for location in the searchIf solution is infeasible, adds a cut using cbLazy"""
# remember to set model.params.DualReductions = 0 before using!
# remember to set model.params.LazyConstraints = 1 before using!
if where != GRB.callback.MIPSOL:
return
edges = []
for (i,j) in x:
if model.cbGetSolution(x[i,j]) > .5:
if i != V[0] and j != V[0]:
edges.append( (i,j) )
G = networkx.Graph()
G.add_edges_from(edges)
Components = networkx.connected_components(G)
for S in Components:
S_card = len(S)
q_sum = sum(q[i] for i in S)
NS = int(math.ceil(float(q_sum)/Q))
S_edges = [(i,j) for i in S for j in S if i
if S_card >= 3 and (len(S_edges) >= S_card or NS > 1):
model.cbLazy(quicksum(x[i,j] for i in S for j in S if j > i) <= S_card-NS)
print ("adding cut for",S_edges)
return
model = Model("vrp")
x = {}
for i in V:
for j in V:
if j > i and i == V[0]: # depot
x[i,j] = model.addVar(ub=2, vtype="I", name="x(%s,%s)"%(i,j))
elif j > i:
x[i,j] = model.addVar(ub=1, vtype="I", name="x(%s,%s)"%(i,j))
model.update()
model.addConstr(quicksum(x[V[0],j] for j in V[1:]) == 2*m, "DegreeDepot")
for i in V[1:]:
model.addConstr(quicksum(x[j,i] for j in V if j < i) +
quicksum(x[i,j] for j in V if j > i) == 2, "Degree(%s)"%i)
model.setObjective(quicksum(c[i,j]*x[i,j] for i in V for j in V if j>i), GRB.MINIMIZE)
model.update()
model.__data = x
return model,vrp_callback
def distance(x1,y1,x2,y2):
"""distance: euclidean distance between (x1,y1) and (x2,y2)"""
return math.sqrt((x2-x1)**2 + (y2-y1)**2)
def make_data(n):
"""make_data: compute matrix distance based on euclidean distance"""
V = range(1,n+1)
x = dict([(i,random.random()) for i in V])
y = dict([(i,random.random()) for i in V])
c,q = {},{}
Q = 100
for i in V:
q[i] = random.randint(10,20)
for j in V:
if j > i:
c[i,j] = distance(x[i],y[i],x[j],y[j])
return V,c,q,Q
if __name__ == "__main__":
import sys
n = 19
m = 3
seed = 1
random.seed(seed)
V,c,q,Q = make_data(n)
model,vrp_callback = vrp(V,c,m,q,Q)
# model.Params.OutputFlag = 0 # silent mode
model.params.DualReductions = 0
model.params.LazyConstraints = 1
model.optimize(vrp_callback)
x = model.__data
edges = []
for (i,j) in x:
if x[i,j].X > .5:
if i != V[0] and j != V[0]:
edges.append( (i,j) )
print ("Optimal solution:",model.ObjVal)
print ("Edges in the solution:")
print (sorted(edges))
下面给去数据。
20 3 100
12 6 8 12 6 12 19 2 15 1 14 18 17 4 14 2 8 3 17 13
0 1 0.8854
0 2 0.347
0 3 0.0657
0 4 0.7677
0 5 0.6219
0 6 0.1078
0 7 0.421
0 8 0.1479
0 9 0.214
0 10 0.5961
0 11 0.2563
0 12 0.2391
0 13 0.4847
0 14 0.7771
0 15 0.5314
0 16 0.2772
0 17 0.3874
0 18 0.3692
0 19 0.5806
1 2 0.2748
1 3 0.8172
1 4 0.3031
1 5 0.0957
1 6 0.1769
1 7 0.2286
1 8 0.8382
1 9 0.193
1 10 0.2046
1 11 0.0724
1 12 0.1526
1 13 0.1958
1 14 0.693
1 15 0.2348
1 16 0.8967
1 17 0.5607
1 18 0.8567
1 19 0.1045
2 3 0.0817
2 4 0.1047
2 5 0.3185
2 6 0.4131
2 7 0.0962
2 8 0.2928
2 9 0.8978
2 10 0.6085
2 11 0.8242
2 12 0.0102
2 13 0.0959
2 14 0.2935
2 15 0.326
2 16 0.1059
2 17 0.8459
2 18 0.3644
2 19 0.2017
3 4 0.758
3 5 0.593
3 6 0.1399
3 7 0.051
3 8 0.7976
3 9 0.8476
3 10 0.8388
3 11 0.7286
3 12 0.6406
3 13 0.1736
3 14 0.7253
3 15 0.3014
3 16 0.7656
3 17 0.565
3 18 0.1183
3 19 0.6055
4 5 0.6187
4 6 0.5314
4 7 0.7017
4 8 0.0115
4 9 0.2644
4 10 0.4103
4 11 0.571
4 12 0.0099
4 13 0.2414
4 14 0.8645
4 15 0.3359
4 16 0.3474
4 17 0.5456
4 18 0.4355
4 19 0.2843
5 6 0.4037
5 7 0.7638
5 8 0.1594
5 9 0.1899
5 10 0.6614
5 11 0.107
5 12 0.1288
5 13 0.2253
5 14 0.7477
5 15 0.0376
5 16 0.6858
5 17 0.7843
5 18 0.8032
5 19 0.3509
6 7 0.6378
6 8 0.2439
6 9 0.0696
6 10 0.2693
6 11 0.0457
6 12 0.7164
6 13 0.2689
6 14 0.1912
6 15 0.3874
6 16 0.2788
6 17 0.1678
6 18 0.0871
6 19 0.35
7 8 0.2504
7 9 0.6327
7 10 0.5207
7 11 0.5347
7 12 0.1364
7 13 0.3845
7 14 0.6942
7 15 0.0616
7 16 0.7812
7 17 0.5364
7 18 0.1904
7 19 0.7417
8 9 0.3841
8 10 0.8632
8 11 0.5275
8 12 0.0036
8 13 0.7665
8 14 0.6136
8 15 0.6415
8 16 0.7456
8 17 0.6833
8 18 0.646
8 19 0.7913
9 10 0.2349
9 11 0.6501
9 12 0.7177
9 13 0.6223
9 14 0.6642
9 15 0.6208
9 16 0.7094
9 17 0.7494
9 18 0.8712
9 19 0.1773
10 11 0.3701
10 12 0.2412
10 13 0.049
10 14 0.7547
10 15 0.0354
10 16 0.1106
10 17 0.6359
10 18 0.5718
10 19 0.0362
11 12 0.4776
11 13 0.6912
11 14 0.8994
11 15 0.7403
11 16 0.43
11 17 0.5011
11 18 0.454
11 19 0.8067
12 13 0.3468
12 14 0.8725
12 15 0.5527
12 16 0.8733
12 17 0.2074
12 18 0.0381
12 19 0.6911
13 14 0.5649
13 15 0.7023
13 16 0.4119
13 17 0.1095
13 18 0.5517
13 19 0.1183
14 15 0.022
14 16 0.0218
14 17 0.3595
14 18 0.071
14 19 0.5117
15 16 0.1301
15 17 0.8168
15 18 0.8828
15 19 0.702
16 17 0.853
16 18 0.1956
16 19 0.2284
17 18 0.5877
17 19 0.036
18 19 0.6584
结果部分截图如下。
大家如果有时间一定要亲自尝试一下哦!
如果有什么疑问或者建议,可以给我发邮件。➡ zhaoyou728 at outlook.com