CVRP建模与求解-基于粒子群算法(python实现)

CVRP建模与求解—基于粒子群算法

1. VRP简要描述

经典VRP可描述为:对一系列装卸货点进行适当的路径规划,在满足约束条件(客户需求、车辆载重和容积、车型、车辆行驶里程、配送时间窗、配送中心数量等限制)和目标最优化(路程最短、成本最低、使用车辆数最少、配送时间最快等)下,将客户的配送需求从配送中心送达客户点,或从客户点送回配送中心。

2. 课题场景设计

2.1 场景

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

2.2 要求

优化目标:最小化车辆启动成本和车辆行驶成本之和;
约束条件:车辆行驶距离约束,重量约束;
已知信息:配送中心位置、客户点位置、客户点需求、车辆最大载重、车辆最大行驶距离、车辆启动成本、车辆单位距离行驶成本;

3. 数学模型

3.1 符号说明

CVRP建模与求解-基于粒子群算法(python实现)_第1张图片

3.2 数学模型

CVRP建模与求解-基于粒子群算法(python实现)_第2张图片

4. 粒子群算法设计

4.1 算法设计

CVRP问题是TSP问题的拓展,车辆容量无限大的CVRP问题可认为是TSP问题,即一辆车就可以服务所有的客户。CVRP问题的求解与TSP问题的求解类似(可先参考【粒子群算法求解TSP问题】),这里以客户点(编号为1,2,3…)为自然数编码(不包含配送中心0),在载重和行驶距离约束下采用贪婪策略为客户点分配车辆(即依次将客户点分配给车,直到车辆无法再装下下一个客户点的需求,如图所示)。采用基于遗传算法交叉算子的混合型粒子群算法进行解空间搜索,这里采用顺序交叉算子,对惯性因子w、自我认知因子c1、社会认知因子c2则以w/(w+c1+c2),c1/(w+c1+c2),c2/(w+c1+c2)的概率接受粒子本身、当前最优解、全局最优解交叉的父代之一(即按概率选择其中一个作为父代,不加权)。初始解构造采用贪婪算法构造初始解的方法,不考虑客户点的需求和行驶距离限制,即在初始化时将VRP问题简化为TSP问题进行构造。
CVRP建模与求解-基于粒子群算法(python实现)_第3张图片

4.2 python程序设计

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 = 120#车辆最大容量
    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 = [(50, 50),(96, 24),(40, 5),(49, 8),(13, 7),(29, 89),(48, 30),(84, 39),(14, 47),(2, 24),(3, 82),(65, 10),(98, 52),(84, 25),(41, 69),(1, 65),
                       (51, 71),(75, 83),(29, 32),(83, 3),(50, 93),(80, 94),(5, 42),(62, 70),(31, 62),(19, 97),(91, 75),(27, 49),(23, 15),(20, 70),(85, 60),(98, 85)]
    Demand = [0,16,11,6,10,7,12,16,6,16,8,14,7,16,3,22,18,19,1,14,8,12,4,8,24,24,2,10,15,2,14,9]
    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)#画路径图

4.3 例子求解结果

采用1个配送中心和31个客户点的数据样例进行测试:

#客户点位置和需求量,Customer[0]表示配送中心
Customer = [(50, 50),(96, 24),(40, 5),(49, 8),(13, 7),(29, 89),(48, 30),(84, 39),(14, 47),(2, 24),(3, 82),(65, 10),(98, 52),(84, 25),(41, 69),(1, 65),
                       (51, 71),(75, 83),(29, 32),(83, 3),(50, 93),(80, 94),(5, 42),(62, 70),(31, 62),(19, 97),(91, 75),(27, 49),(23, 15),(20, 70),(85, 60),(98, 85)]
Demand = [0,16,11,6,10,7,12,16,6,16,8,14,7,16,3,22,18,19,1,14,8,12,4,8,24,24,2,10,15,2,14,9]

车辆启动成本 C0 取30,车辆单位距离行驶成本C1 取1,运算结果最优解为728.1,路径为[0, 6, 19, 11, 3, 2, 28, 4, 9, 22, 8, 27, 18, 0], [0, 24, 29, 15, 10, 25, 5, 20, 16, 14, 0], [0, 23, 17, 21, 31, 26, 30, 12, 1, 13, 7, 0],路径图如下:
CVRP建模与求解-基于粒子群算法(python实现)_第4张图片
记录学习过程,欢迎指正

你可能感兴趣的:(#,VRP,算法,python)