车辆路径问题——CVRP的Python实现

车辆路径问题通常被定义为装运一系列点或接收点,通过他们组织车辆适当途径有序。在一定的约束条件,如对商品的需求,交货数量,交付的交付时间,车辆容量限制,行驶里程限制,时间限制,以实现某些目标。如果最短距离,最低的成本,尽可能少的时间,尽量少使用车辆。因为运输点、更多的客户、商品种类繁多,区域交通网络等诸多影响因素的不均匀分布使得在城市的运输路线,运输服务中加深了处理问题的复杂性。再加上如时间窗等客户提出的约束要求,使得如何安排最佳路线,如何使用有效的运输路线已成为难题。合理的解决车辆路径问题,不仅可以简化流通过程,缩短交货时间,降低负载率运载工具,以降低物流成本提高经济效率,加快响应客户需求的速度,提高服务质量,提升客户满意的物流环节。因此,物流配送车辆调度问题是在这个过程中的一个关键问题。

一、车辆路径问题的类型

车辆路径问题(VRP)最早是由 Dantzig 和 Ramser 于1959年首次提出,它是指一定数量的客户,各自有不同数量的货物需求,配送中心向客户提供货物,由一个车队负责分送货物,组织适当的行车路线,目标是使得客户的需求得到满足,并能在一定的约束下,达到诸如路程最短、成本最小、耗费时间最少等目的。经典VRP可描述为:对一系列装卸货点进行适当的路径规划,在满足约束条件(客户需求、车辆载重和容积、车型、车辆行驶里程、配送时间窗、配送中心数量等限制)和目标最优化(路程最短、成本最低、使用车辆数最少、配送时间最快等)下,将客户的配送需求从配送中心送达客户点,然后从客户点送回配送中心。
实际应用中,车辆路径问题是由很多条件的限制。例如,首先,车辆容量限制总需求各车辆服务的客户,不得超过车辆的最大负载重量。二,时间窗的限制,每个客户端服务必须在一定的时间范围内。第三,物流公司可能对客户服务的多个配送中心。四,客户可能会返回部分商品的配送中心。第五,客户可以是不同的车辆服务。第六,客户需求和其他随机锻炼路线的数量。第七,服务订单的客户限制之间存在。在研究工作中,常常做出关于限制一些基本假设。如由一个配送中心,一个单一的模型来完成任务分配是商品的集散地混合每个客户的位置,并从配送中心到被称为他们的距离。配送中心有足够的货物交付,并有足够的运输能力,每个客户是汽车服务,也只能是对车辆的每一行需求的汽车服务必须不超过最大负载重量。所有车辆都从配送中心出发,完成任务的客户,最后回到配送中心。实际的分布也可以考虑多中心,多车,时间要求和客户需求的客户服务随机化等。对于一个特定的问题,所有的上述限制可能存在的,有可能是唯一的一个组成部分。

二、CVPR问题的数学模型

车辆路径问题(VRP)有非常多的变形,这里介绍VRP研究最多的基本问题——有容量约束的车辆路径问题(Capacitated Vehicle Routing Problem,CVRP)。在CVRP问题中,要求由一个车队承担将货物从一个仓库运输到其他预先指定的客户点上的任务。其中,车队的车辆都是同质的,且都只能从仓库出发,服务完客户点后,返回仓库。每个客户点也只能被一辆车访问一次。决策对象是车辆的行驶路线,每辆车在不同的路线上的行驶成本不同,最终的目标是要使得完成这个任务的车队的总成本最小。
CVRP问题具有下面特征

单向:纯取货/纯送货;
单配送中心:只有一个配送中心/车场;
单车型:只考虑一种车型;
需求不可拆分:客户需求只能有一辆车满足;
车辆封闭:完成配送任务的车辆需回到配送中心;
车辆充足:不限制车辆数量,即配送车辆需求均能满足;
非满载:任意客户点的需求量小于车辆最大载重。

符号说明

\(C_0\)\(C_1\) 表示车辆启动的固定成本、单位距离的行驶成本
\(N\) 服务客户点数量,索引为\(i,j\)
\(n\) 节点集合(\(n=0,1,2,\ldots, N\)),其中0表示配送中心;\(1,2,\ldots, N\)表示客户点
\(D\) 车辆行驶的最大距离
\(K\) 使用的最大车辆数
\(m\) 拥有的车辆数,索引为k
\(d_{ij}\) 客户点\(i(x_i,y_i)\)\(j (x_j,y_j)\) 的欧氏距离
\(m_j\) \(j\)个客户的需求量
\(M\) 车辆的最大载重

\[x_{ijk}= \begin{cases} 1, & 表示车辆k从客户点i行驶到客户点j \\ 0, & 其他\end{cases}\]

目标函数:

\[\operatorname{Min} Z=C_0 K+C_1 \sum_{j=0}^N \sum_{j=0}^N \sum_{k=1}^K d_{ij} x_{ijk} \]

约束:
a. 配送中心约束: 所有车辆均由配送中心出发, 完成所有的配送任务 后返回配送中心:

\[\sum_{k=1}^K \sum_{j=1}^N x_{0 j k}=\sum_{k=1}^K \sum_{j=1}^N x_{j 0 k}=K \]

b. 客户点流量平衡:进出车辆数相等

\[\sum_{i=1}^N x_{i j k}=\sum_{i=1}^N x_{j i k}(k \in m, \forall j=1,2, \ldots, N) \]

c. 重量约束: 每辆车的装载量不能超过其最大载重量限制

\[\sum_{i=0}^N \sum_{j=0}^N \mathrm{x}_{ijk} m_j \leq M(k \in m) \]

d. 客户点服务约束: 每个客户点被服务 1 次

\[\sum_{k=1}^K \sum_{i=0}^N x_{ijk}=1(j=1,2, \ldots, N) \]

e. 车辆行驶距离约束: 每辆车配送不超过最大配送距离

\[\sum_{i=0}^N \sum_{j=0}^N \mathrm{x}_{i j k} d_{i j} \leq \mathrm{D}(k \in m) \]

f. 所需车辆数约束:

\[K \geq\left\lceil\frac{\sum_{j=1}^N m_j}{M}\right\rceil\left(K=1,2, \ldots, N \right) \]

CVRP问题是TSP问题的拓展,车辆容量无限大的CVRP问题可认为是TSP问题,即一辆车就可以服务所有的客户。CVRP问题的求解与TSP问题的求解类似, CVRP问题的解为一组满足需求节点需求的多个车辆的路径集合。假设某物流网络中共有10个顾客节点,编号为1~10,一个车辆基地,编号为0,在满足车辆容量约束与顾客节点需求约束的条件下,此问题的一个可行解可表示为:[0-1-2-0,0-3-4-5-0,0-6-7-8-0,0-9-10-0],即需要4个车辆来提供服务,车辆的行驶路线分别为0-1-2-0,0-3-4-5-0,0-6-7-8-0,0-9-10-0。由于车辆的容量固定,基地固定,因此可以将上述问题的解先表示为[1-2-3-4-5-6-7-8-9-10]的有序序列,然后根据车辆的容量约束,对序列进行切割得到若干车辆的行驶路线。

三、CVPR的求解

下图中黑色节点表示配送中心,蓝色节点表示送货客户服务点,节点旁边数据表示需完成的送货需求。每辆车的最大容量为15,最大行驶距离为250公里,从0点出发运送货物再回到0点。求完成所有节点需求时的车辆最短运输路线。(为简化计算,车辆启动成本 \(C_0\) 取30,车辆单位距离行驶成本\(C_1\) 取1)

import math
import random
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.pylab import mpl

mpl.rcParams['font.sans-serif'] = ['SimHei']  # 添加这条可以让图形显示中文


def calDistance(CityCoordinates):
    '''
    计算城市间距离
    输入:CityCoordinates-城市坐标;
    输出:城市间距离矩阵-dis_matrix
    '''
    dis_matrix = pd.DataFrame(data=None, columns=range(len(CityCoordinates)), index=range(len(CityCoordinates)))
    for i in range(len(CityCoordinates)):
        xi, yi = CityCoordinates[i][0], CityCoordinates[i][1]
        for j in range(len(CityCoordinates)):
            xj, yj = CityCoordinates[j][0], CityCoordinates[j][1]
            dis_matrix.iloc[i, j] = round(math.sqrt((xi - xj) ** 2 + (yi - yj) ** 2), 2)
    return dis_matrix


def greedy(CityCoordinates, dis_matrix):
    '''
    贪婪策略构造初始解,初始化时将VRP简化为TSP进行构造。
    输入:CityCoordinates-节点坐标,dis_matrix-距离矩阵
    输出:初始解-line
    '''
    # 修改dis_matrix以适应求解需要
    dis_matrix = dis_matrix.astype('float64')
    for i in range(len(CityCoordinates)): dis_matrix.loc[i, i] = math.pow(10, 10)
    dis_matrix.loc[:, 0] = math.pow(10, 10)  # 0不在编码内
    line = []  # 初始化
    now_city = random.randint(1, len(CityCoordinates) - 1)  # 随机生成出发城市
    line.append(now_city)  # 添加当前城市到路径
    dis_matrix.loc[:, now_city] = math.pow(10, 10)  # 更新距离矩阵,已经过城市不再被取出
    for i in range(1, len(CityCoordinates) - 1):
        next_city = dis_matrix.loc[now_city, :].idxmin()  # 距离最近的城市
        line.append(next_city)  # 添加进路径
        dis_matrix.loc[:, next_city] = math.pow(10, 10)  # 更新距离矩阵
        now_city = next_city  # 更新当前城市
    return line


def calFitness(birdPop, Demand, dis_matrix, CAPACITY, DISTABCE, C0, C1):
    '''
    贪婪策略分配车辆(解码),计算路径距离(评价函数)
    输入:birdPop-路径,Demand-客户需求,dis_matrix-城市间距离矩阵,CAPACITY-车辆最大载重,DISTABCE-车辆最大行驶距离,C0-车辆启动成本,C1-车辆单位距离行驶成本;
    输出:birdPop_car-分车后路径,fits-适应度
    '''
    birdPop_car, fits = [], []  # 初始化
    for j in range(len(birdPop)):
        bird = birdPop[j]
        lines = []  # 存储线路分车
        line = [0]  # 每辆车服务客户点
        dis_sum = 0  # 线路距离
        dis, d = 0, 0  # 当前客户距离前一个客户的距离、当前客户需求量
        i = 0  # 指向配送中心
        while i < len(bird):
            if line == [0]:  # 车辆未分配客户点
                dis += dis_matrix.loc[0, bird[i]]  # 记录距离
                line.append(bird[i])  # 为客户点分车
                d += Demand[bird[i]]  # 记录需求量
                i += 1  # 指向下一个客户点
            else:  # 已分配客户点则需判断车辆载重和行驶距离
                if (dis_matrix.loc[line[-1], bird[i]] + dis_matrix.loc[bird[i], 0] + dis <= DISTABCE) & (
                        d + Demand[bird[i]] <= CAPACITY):
                    dis += dis_matrix.loc[line[-1], bird[i]]
                    line.append(bird[i])
                    d += Demand[bird[i]]
                    i += 1
                else:
                    dis += dis_matrix.loc[line[-1], 0]  # 当前车辆装满
                    line.append(0)
                    dis_sum += dis
                    lines.append(line)
                    # 下一辆车
                    dis, d = 0, 0
                    line = [0]

        # 最后一辆车
        dis += dis_matrix.loc[line[-1], 0]
        line.append(0)
        dis_sum += dis
        lines.append(line)

        birdPop_car.append(lines)
        fits.append(round(C1 * dis_sum + C0 * len(lines), 1))

    return birdPop_car, fits


def crossover(bird, pLine, gLine, w, c1, c2):
    '''
    采用顺序交叉方式;交叉的parent1为粒子本身,分别以w/(w+c1+c2),c1/(w+c1+c2),c2/(w+c1+c2)
    的概率接受粒子本身逆序、当前最优解、全局最优解作为parent2,只选择其中一个作为parent2;
    输入:bird-粒子,pLine-当前最优解,gLine-全局最优解,w-惯性因子,c1-自我认知因子,c2-社会认知因子;
    输出:交叉后的粒子-croBird;
    '''
    croBird = [None] * len(bird)  # 初始化
    parent1 = bird  # 选择parent1
    # 选择parent2(轮盘赌操作)
    randNum = random.uniform(0, sum([w, c1, c2]))
    if randNum <= w:
        parent2 = [bird[i] for i in range(len(bird) - 1, -1, -1)]  # bird的逆序
    elif randNum <= w + c1:
        parent2 = pLine
    else:
        parent2 = gLine

    # parent1-> croBird
    start_pos = random.randint(0, len(parent1) - 1)
    end_pos = random.randint(0, len(parent1) - 1)
    if start_pos > end_pos: start_pos, end_pos = end_pos, start_pos
    croBird[start_pos:end_pos + 1] = parent1[start_pos:end_pos + 1].copy()

    # parent2 -> croBird
    list2 = list(range(0, start_pos))
    list1 = list(range(end_pos + 1, len(parent2)))
    list_index = list1 + list2  # croBird从后往前填充
    j = -1
    for i in list_index:
        for j in range(j + 1, len(parent2) + 1):
            if parent2[j] not in croBird:
                croBird[i] = parent2[j]
                break

    return croBird


def draw_path(car_routes, CityCoordinates):
    '''
    #画路径图
    输入:line-路径,CityCoordinates-城市坐标;
    输出:路径图
    '''
    for route in car_routes:
        x, y = [], []
        for i in route:
            Coordinate = CityCoordinates[i]
            x.append(Coordinate[0])
            y.append(Coordinate[1])
        x.append(x[0])
        y.append(y[0])
        plt.plot(x, y, 'o-', alpha=0.8, linewidth=0.8)
    plt.xlabel('x')
    plt.ylabel('y')
    plt.show()


if __name__ == '__main__':
    # 车辆参数
    CAPACITY = 15  # 车辆最大容量
    DISTABCE = 250  # 车辆最大行驶距离
    C0 = 30  # 车辆启动成本
    C1 = 1  # 车辆单位距离行驶成本

    # PSO参数
    birdNum = 50  # 粒子数量
    w = 0.2  # 惯性因子
    c1 = 0.4  # 自我认知因子
    c2 = 0.4  # 社会认知因子
    pBest, pLine = 0, []  # 当前最优值、当前最优解,(自我认知部分)
    gBest, gLine = 0, []  # 全局最优值、全局最优解,(社会认知部分)

    # 其他参数
    iterMax = 1000  # 迭代次数
    iterI = 1  # 当前迭代次数
    bestfit = []  # 记录每代最优值

    # 读入数据,
    # DistributionCenter = #配送中心
    Customer =[(4,4),(2,8),(8,8),(0,7),(1,7),(5,6),(7,6),(3,5),(6,5),(5,3),(8,3),(1,2),(2,2),(3,1),(6,1),(0,0),(7,0)]
    Demand =  [0,1,1,2,4,2,4,8,8,1,2,1,2,4,4,8,8]
    dis_matrix = calDistance(Customer)  # 计算城市间距离

    birdPop = [greedy(Customer, dis_matrix) for i in range(birdNum)]  # 贪婪算法构造初始解
    # birdPop = [random.sample(range(1,len(Customer)),len(Customer)-1) for i in range(birdNum)]#客户点编码,随机初始化生成种群

    birdPop_car, fits = calFitness(birdPop, Demand, dis_matrix, CAPACITY, DISTABCE, C0, C1)  # 分配车辆,计算种群适应度

    gBest = pBest = min(fits)  # 全局最优值、当前最优值
    gLine = pLine = birdPop[fits.index(min(fits))]  # 全局最优解、当前最优解
    gLine_car = pLine_car = birdPop_car[fits.index(min(fits))]
    bestfit.append(gBest)

    while iterI <= iterMax:  # 迭代开始
        for i in range(birdNum):
            birdPop[i] = crossover(birdPop[i], pLine, gLine, w, c1, c2)

        birdPop_car, fits = calFitness(birdPop, Demand, dis_matrix, CAPACITY, DISTABCE, C0, C1)  # 分配车辆,计算种群适应度
        pBest, pLine, pLine_car = min(fits), birdPop[fits.index(min(fits))], birdPop_car[fits.index(min(fits))]
        if min(fits) <= gBest:
            gBest, gLine, gLine_car = min(fits), birdPop[fits.index(min(fits))], birdPop_car[fits.index(min(fits))]

        bestfit.append(gBest)
        print(iterI, gBest)  # 打印当前代数和最佳适应度值
        iterI += 1  # 迭代计数加一

    print(gLine_car)  # 路径顺序
    draw_path(gLine_car, Customer)  # 画路径图

车辆启动成本\(C_0\)取30,车辆单位距离行驶成本 \(C_1\)取1,运算结果最优解为168.4,车辆路径为[0, 1, 4, 3, 7, 0], [0, 8, 6, 2, 5, 0], [0, 10, 16, 14, 9, 0], [0, 12, 11, 15, 13, 0](这些点是程序中Customer那些点对应的索引 ),路径图如下:

四、总结

车辆路径问题由货物配送中心,客户,车辆,运输网络,优化目标和约束条件和组合物的其他元素车辆路径问题。首先,是商品配送服务的对象。这项服务可以是分销服务还可以收集服务。二,配送中心。在车辆路径问题,配送中心是地方货物每辆车装载路线开始或结束。点也可被称为码或仓库。配送中心包含了一些车辆的客户是负责完成分配或收集服务。第三,顾客。车辆路径问题的客户服务对象送货车辆也可零售门店,经销点,如个别本文统称为客户或客户端指向送货上门。客户有特定属性,诸如用于货物,服务,时间,时间,以及服务和其他服务的优先级的持续时间的需求。第四车辆。货车为客户完成维修工具。车辆的基本属性,包括车辆的停放在顾客服务的位置之前和之后的完成等的类型,车辆的负载,车辆的最大行驶时间或距离,以及车辆。第五,传输网络。运输网络是由节点和赋有圆弧的非负权重。节点可以是一个配送中心或客户的角度弧客户端或客户站点之间的配送中心和道路连接点。弧具有某些属性,包括方向,重量等。取决于道路弧到弧和特征可分为无向弧。每个弧赋予了权重,权重可以按照不同的含义如运输成本,运输时间,运输距离的研究需要被定义。节点之间的双向正确的重量可以相等或不等前者称为对称车辆路径问题后者是不对称的车辆路径问题。第六,优化目标。在实际应用中车辆路径问题可以是一个单目标优化目标也可以是多目标。单目标优化,包括最短的运输距离,最短旅行时间,以及车辆和其他间接成本最少的最小数目。需要在解决多目标车辆路径问题需要同时优化多个目标,如车辆的最短距离的最小数目,以完成交货,以满足客户的要求等。第七,约束。组合优化问题,它有一定的限制,不同类型的VRP问题其约束是不一样的。在VRP系统软件的研究,总重量容量限制的共同制约①任何车辆路径不能超过车辆的承载能力。②时间窗约束范围内指定的时间窗口配送需求,达到客户,包括软,硬时间窗时间窗的限制。③车辆行驶距离的限制最大行驶距离不超过一个预先指定的值。④优先约束根据每个客户的重要性,为客户提供不同的服务优先级。⑤多模型约束。在物流中车辆路径问题是比较普遍的问题,无论是在服务质量上,还是降低物流成本上都对物流公司具有很大的影响。

参考文献

1.cvrp代码_Python数学规划案例:路径优化CVRP
2.CVRP建模与求解-基于粒子群算法(python实现)
3.基于python下sko.GA的遗产算法解决CVRP(含容量约束的车辆最短路径)问题

posted @ 2023-05-11 23:17  郝hai  阅读(5721)  评论(3编辑  收藏  举报