Pick and delivery problem的简介与建模实现(一)

目录:

  • 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.

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值