目录:
-
Pick and delivery problem简介
-
多对多(M-M)问题
-
Python调用Gurobi实现
-
模型建立
-
lazy constraints
-
算例与结果
-
-
参考文献
Pick and delivery problem简介
Pick and delivery problem(PDP)是一类重要的路径规划问题,其中货物或乘客必须从不同的起点运输到不同的目的地。这些问题通常定义在一个图上,其中顶点表示要运输的不同实体(或商品)的起点或目的地。
根据需求的类型和路线结构,PDP问题可分为三大类(下图中正方形表示仓库,其他顶点表示客户):
多对多(M-M)问题: 每个商品可能有多个来源和目的地,任何地点都可能是多个商品的来源或目的地。例如,在零售店之间的库存调度问题,或者自行车或汽车共享系统的管理问题。
一对多对一(1-M-1)问题: 存在一些商品从仓库交付给许多客户,而其他商品则在客户处收集并运回仓库。例如,分发饮料同时收集空罐空瓶的场景,以及正向和逆向物流系统,即除了交付新产品外,还必须计划收集使用过的、有缺陷的或过时的产品。
一对一(1-1)问题: 每种商品都有一个来源地和一个目的地,必须在这两者之间运输。这类问题的典型应用是城市快递业务以及共享乘车业务(Ride-sharing,dial-a-ride problem等)。
与其他VRP一样,PDP也可以根据信息的可用性分为静态问题和动态问题。在静态问题中,我们假设所有的问题参数都是确定的,并且在确定车辆路径之前是已知的。在动态问题中,决策所需的一些信息会随着时间的推移逐渐出现,因此需要更新解决方案。动态问题可能会具有随机性,某些不确定参数将是概率分布的形式。
多对多(M-M)问题
关于多对多(M-M)问题的文献大多集中在单个商品在多个来源地和目的地之间运输的情况。单商品M-M接送车辆路径问题(1-PDVRP)可定义如下:
Python调用Gurobi实现
模型建立
import pandas as pd
import numpy as np
from gurobipy import *
if __name__ == '__main__':
data = pd.read_csv('PDVRP_1.csv').values
vehicleNum = 2
vehicleQ = 40
# 计算c_ij
c = np.zeros(shape=[data.shape[0], data.shape[0]])
for i in range(data.shape[0]):
for j in range(data.shape[0]):
if i != j:
c[i, j] = ((data[i, 1] - data[j, 1]) ** 2 + (data[i, 2] - data[j, 2]) ** 2) ** 0.5
PDVRP = Model()
x = {}
for i in range(data.shape[0]):
for j in range(data.shape[0]):
for k in range(vehicleNum):
if i != j:
x[i, j, k] = PDVRP.addVar(obj=c[i, j], vtype=GRB.BINARY, name='x_'+str(i)+'_'+str(j)+'_'+str(k))
f = {}
for i in range(data.shape[0]):
for j in range(data.shape[0]):
if i != j:
f[i, j] = PDVRP.addVar(lb=0, vtype=GRB.CONTINUOUS, name='f_' + str(i) + '_' + str(j))
for i in range(1, data.shape[0]):
expr1 = LinExpr(0)
for j in range(data.shape[0]):
for k in range(vehicleNum):
if i != j:
expr1.addTerms(1, x[i, j, k])
PDVRP.addConstr(expr1 == 1, name='cons1')
for i in range(data.shape[0]):
for k in range(vehicleNum):
expr2 = LinExpr(0)
for j in range(data.shape[0]):
if i != j:
expr2.addTerms(1, x[i, j, k])
expr2.addTerms(-1, x[j, i, k])
PDVRP.addConstr(expr2 == 0, name='cons2')
for i in range(data.shape[0]):
for j in range(data.shape[0]):
if i != j:
expr3 = LinExpr(0)
expr3.addTerms(1, f[i, j])
for k in range(vehicleNum):
expr3.addTerms(-vehicleQ, x[i, j, k])
PDVRP.addConstr(expr3 <= 0, name='cons3')
for i in range(1, data.shape[0]):
expr4 = LinExpr(0)
for j in range(data.shape[0]):
if i != j:
expr4.addTerms(-1, f[i, j])
expr4.addTerms(1, f[j, i])
PDVRP.addConstr(expr4 == data[i, 3], name='cons4')
PDVRP._vars = x
PDVRP._nodeNum = data.shape[0]
PDVRP._vehicleNum = vehicleNum
PDVRP.Params.lazyConstraints = 1
PDVRP.optimize(subtourelim)
S = []
for i in range(1, data.shape[0]):
for k in range(vehicleNum):
if i not in S and x[0, i, k].x > 1 - 1e-3:
print("[0-" + str(i), end='')
currNode = i
S.append(currNode)
flag = True
while flag:
flag = False
for j in range(1, data.shape[0]):
if j not in S and x[currNode, j, k].x > 1 - 1e-3:
print("-" + str(j), end='')
currNode = j
S.append(currNode)
flag = True
break
print("-0]")
lazy constraints
由于在模型中的子回路去除约束不方便直接写出,这里将其作为 lazy constraints,调用了gurobi 的callback函数进行实现:
# Callback - 去除子回路
def subtourelim(model, where):
if where == GRB.Callback.MIPSOL:
# 找到模型当前的解
print('enter')
vals = model.cbGetSolution(model._vars)
# 找到所有与depot直接或间接相连的点
isVisited = [0]
for i in range(1, model._nodeNum):
for k in range(model._vehicleNum):
if i not in isVisited and vals[0, i, k] > 1 - 1e-3:
currNode = i
isVisited.append(currNode)
flag = True
while flag:
flag = False
for j in range(1, model._nodeNum):
if j not in isVisited and vals[currNode, j, k] > 1 - 1e-3:
currNode = j
isVisited.append(currNode)
flag = True
break
# 找到剩余组成回路的点,进行
if len(isVisited) != model._nodeNum:
S = list(set(range(model._nodeNum)) - set(isVisited))
expr = LinExpr()
for i in S:
for j in S:
for k in range(model._vehicleNum):
if i != j:
expr.addTerms(1, model._vars[i,j,k])
model.cbLazy(expr <= len(S) - 1)
参考文献
[1] Toth P , Vigo D . Vehicle Routing: Problems, Methods, and Applications, Second Edition[M]. 2014.