多车场路径优化丨复现论文:改进蚁群算法求解MDCVRP问题

引言

本系列文章是路径优化问题学习过程中一个完整的学习路线。问题从简单的单车场容量约束CVRP问题到多车场容量约束MDCVRP问题,再到多车场容量+时间窗口复杂约束MDCVRPTW问题,复杂度是逐渐提升的。

如果大家想学习某一个算法,建议从最简单的TSP问题开始学习,一个阶梯一个阶梯走。

如果不是奔着学习算法源码的思路,只想求解出个结果,请看历史文章,有ortools、geatpy、scikit-opt等求解器相关文章,点击→路径优化历史文章,可直接跳转。

本系列文章汇总:

问题描述

多车场车辆路径问题可描述为:多个配送中心、每个中心有若干配送车辆、存在若干需求点、车辆从配送中心往需求点进行服务。其中:车辆存在载重约束。本文假设:

  • (1)需求点货物量已知,且配送中心能满足所有需求点的需求;

  • (2)配送车辆从配送中心出发,最终返回配送中心。一个需求点在配送任务中只能服务 一次;

  • (3)需求点位置已知,不考虑服务时间;

  • (4)所有车辆型号和载重相同;

数学模型

模型

具体模型见文末参考文献,目标函数是车辆总运行距离。
请添加图片描述

数据

数据是RC101改,增加2个配送中心:

请添加图片描述

前3行是配送中心,第二第三列是坐标,第3列是需求量,数据有100个需求点。

因为100个点需求量接近2000,假设每个车场有10辆车,共30辆车,每量车载重约束是70,代码有介绍。

数据见网站:http://web.cba.neu.edu/~msolomon/rc101.htm

数据处理

提取出坐标,需求量、计算距离矩阵。

def deal(self, df):
    x_code = df.iloc[:, 2].tolist()
    y_code = df.iloc[:, 3].tolist()
    demand = np.array(df.iloc[:, 4])
    dis = np.zeros((len(x_code), len(x_code)))
    for i in range(len(x_code)):
        for j in range(i+1, len(x_code)):
            di = np.sqrt((x_code[i] - x_code[j])**2 + (y_code[i] - y_code[j])**2)
            dis[i, j] = di
            dis[j, i] = di
    return x_code, y_code, demand, dis

代码运行环境

windows系统,python3.6.0,第三方库及版本号如下:

numpy==1.18.5
matplotlib==3.2.1

第三方库需要在安装完python之后,额外安装,以前文章有讲述过安装第三方库的解决办法。

运行结果

结果如下:

请添加图片描述

路线图如下:

请添加图片描述
成本随迭代次数的变化图如下:
请添加图片描述

算法设计

编码和解码

实数编码,3到102表示需求点:

customer = [i for i in range(3,103)]

[3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102]

0到2表示配送中心,用长为30的列表表示可使用车辆,值表示其归属:

car = [num//10 for num in range(30)]

[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]

文章是用概率矩阵轮盘赌选择每个需求点,初始信息素矩阵为103×103的矩阵,矩阵的值都是1,从某个需求点到任意需求点的概率都相等。

ant_matrix = np.ones((103,103))

[[1. 1. 1. ... 1. 1. 1.]
 [1. 1. 1. ... 1. 1. 1.]
 [1. 1. 1. ... 1. 1. 1.]
 ...
 [1. 1. 1. ... 1. 1. 1.]
 [1. 1. 1. ... 1. 1. 1.]
 [1. 1. 1. ... 1. 1. 1.]]

编码生成并解码

1、 根据位置读出各个点的需求;

2、 用车的载重去匹配需求量,载重约束下多个可行点轮盘赌选择一个,如果没有可行点,车回到配送中心,换下一辆车;

3、所有需求点匹配完后,计算各辆车的运行距离,即解码:

请添加图片描述编码解码就是步骤1到步骤8
代码如下:

    def rand_choose(self, p):                      #轮盘赌选择
                    x = np.random.rand()
                    q = 0
                    for i, px in enumerate(p):
                        q += px                                # 累计概率
                        if x <= q:
                            break
                    return i
    
        def road_creat(self, customer, ant_matrix):
            customer1 = customer.copy()
            car = [num//self.num_car_every for num in range(self.num_park*self.num_car_every)]
            car1 = car.copy()
            way_all = [[]]
            road = []
            demandx = [[]]
            dij = []
            car_select = []
            while customer1:                        # 需求点非空
                if way_all[-1] == []:
                    last = self.load_max            # 切换新车辆时初始载重           
                if way_all[-1] != []:
                    last =  self.load_max - sum(self.demand[way_all[-1]])  # 否则载重更新
    
                cus_select = []
                for cust in customer1:
                    if self.demand[cust] <= last:    # 挑选载重约束下的所有点
                        cus_select.append(cust)
                if  cus_select == []:                # 车辆结束后添加配送中心,并新增新列表存新车辆路径
                    way_all[-1].append(way_all[-1][0])
                    way_all.append([])     
                    demandx.append([])
                if cus_select != []:
                    if way_all[-1] == []:           # 可用车辆随机选择
                        idx_select =  np.random.randint(0,len(car1))
                        way_all[-1].append(car1[idx_select])
                        car_select.append(car1[idx_select])
                        del car1[idx_select]        # 删除已经选择的车辆
                    index_select = [cus  for cus in cus_select]
                    begin = way_all[-1][-1] 
                    matrix = ant_matrix[begin,:]
                    matrix = matrix[index_select]
                    p = matrix/sum(matrix)         # 可用需求点的概率计算
            
                    idx = self.rand_choose(p)      # 轮盘赌选择一个点
                    cust_choose = cus_select[idx]
                    road.append(cust_choose)
                    demandx[-1].append(self.demand[cust_choose])
    
                    way_all[-1].append(cust_choose)
                    del_idx = customer1.index(cust_choose)
    
                    del customer1[del_idx]          # 删除选择的点
    
            way_all[-1].append(way_all[-1][0])
            for way in way_all:                     # 计算各辆车的距离
                dij.append([])
                for k in range(1,len(way)):
                    start = way[k-1]
                    end = way[k]
                    dij[-1].append(self.dis[start,end])
            return way_all, road, demandx, dij, car_select

不含编码生成的单独解码见road_exp(self, road, car_select)

改进蚁群算法设计

主要介绍矩阵更新和文献的3种领域寻优:
矩阵更新

蚁群信息素矩阵更新,具体介绍见文献:
请添加图片描述

标红求和部分放在解码部分,因为载重约束下的可行需求点才需计算概率。

代码:

def update_Tau(self, ant_matrix, way_all, dij):  
        L = sum([sum(di) for di in dij])                                      # 更新信息素
        c1 = -1
        for way in way_all:
            c1 += 1
            c2 = -1
            for k in range(1,len(way)):
                c2 += 1
                start = way[k-1]
                end = way[k]
                ant_matrix[start,end] =  (1-self.ρ)*ant_matrix[start,end] + self.Q/L                            # 更新公式
                ant_matrix[start,end] = ant_matrix[start,end]**self.α + (1/dij[c1][c2])**self.β
        return ant_matrix

点交换邻域操作

在编码序列中随机选取两个客户点,互换其位置。

代码:

def area_search1(self, road):
      location=random.sample(range(len(road)),2)    # 生成两个不重复的位置
      road[location[0]],road[location[1]]=road[location[1]],road[location[0]]
      return road

点插入邻域操作

在编码序列中随机选取两个客户点,将其中一点插入到另一点之后。

代码:

def area_search2(self, road):
      location=random.sample(range(len(road)),2)    # 生成两个不重复的位置
      idx1, idx2 = location[0], location[1]
      if idx1> idx2:
          idx1, idx2 = idx2, idx1
      road1 = road[:idx1+1] + [road[idx2]] + road[idx1+1:idx2] + road[idx2+1:]
      return road1

子路径交换邻域操作

在编码序列中随机选取两个客户点,获取其所在车辆路径,交换该两点之后的车辆子路径。

代码:

def area_search3(self, way_all, road):
    location = random.sample(range(len(road)), 2)    # 生成两个不重复的位置
    # ind = list(set(location))
    idx1, idx2 = road[location[0]], road[location[1]]
    road1 = []
    
    for way in way_all:
        if idx1 in way:
            idd = way.index(idx1)    # 第一个点的其余路径换成第二辆
            ro1, ro2 = way[:way.index(idx1)], way[way.index(idx1)+1:]
            ro = ro1 + [idx1] + ro2  
            road1 += ro[1:-1]
        elif idx2 in way:            # 第二个点的其余路径换成第一辆
            ro1, ro2 = way[:way.index(idx2)], way[way.index(idx2)+1:]
            ro = ro1 + [idx2] + ro2
            road1 += ro[1:-1]
        else:
            road1 += way[1:-1]
    return road1

详细介绍见文末参考文献。

算法步骤

见参考文献:

请添加图片描述

主函数

设计主函数如下:

import pandas as pd
import numpy as np
from decode import data_collect
from ant_just import ant_j
from decode import road_decode

path = './数据1.xlsx'
df = pd.read_excel(path, sheet_name = 'Sheet1')
x_code, y_code, demand, dist = data_collect().deal(df)  #数据提取



customer = [i for i in range(3,103)]         # 3到102是需求点


parm_data = [demand, dist, 3, 10, 70]        # 3个配送中心,每个10辆车,载重70
rd = road_decode(parm_data)                  # 解码模块

parm_ant = [1.25, 2.5, .1, 500, 30]         # 信息素权重,启发因子权重,信息素挥发因子,信息素强度,蚂蚁数量(个)
parm_tg = [rd,20]                            # 解码方法和迭代次数  
ant = ant_j(parm_ant, parm_tg)
road1, car_select, L, result = ant.ant_all(customer, len(demand))
way_all, demandx, dij, car_select = rd.road_exp(road1, car_select, 0)
print('*****')

L = sum([sum(de) for de in dij])
print(L)
rd.draw(way_all, x_code, y_code, L)        # 路径路
rd.draw_change(result)                     # 成本变化

代码

为了方便,把代码浓缩在3个代码里,excel数据生成后每次运行main.py无需再运行data_collect().txt_to_xlsx() 。

小结

  • 1、本推文主要是对参考论文进行复现,也有自己的改进,但基本是复现原文,具体算法步骤见文献,不做过多介绍。

数据采用rc101,可修改数据

  • 2、参考文献:

[1]陈文博.增强蚁群优化算法求解多车场车辆路径问题[D].昆明理工大学,2021.

演示视频:

论文复现丨改进蚁群算法求解MDVRP问题

完整算法+数据:
完整算法源码+数据:见下方微信公众号:关注后回复:调度

# 微信公众号:学长带你飞
# 主要更新方向:1、柔性车间调度问题求解算法
#              2、学术写作技巧
#              3、读书感悟
# @Author  : Jack Hao


车场车辆路径问题是指在多个车场中,如何合理地安排车辆路径,以最小化总行驶距离或时间。遗传算法是一种启发式优化算法,可以用于解决这类问题。 遗传算法的基本思想是模拟生物进化过程中的遗传和适应度选择机制。它通过对候选解进行编码、交叉、变异等操作,生成新的解,并根据适应度函数对解进行评估和选择,从而逐步优化得到最优解。 在多车场车辆路径问题中,遗传算法可以按照以下步骤进行求解: 1. 初始化种群:随机生成一组初始解,每个解表示一种车辆路径方案。 2. 适应度评估:根据问题的具体要求,定义适应度函数来评估每个解的质量,例如总行驶距离或时间。 3. 选择操作:根据适应度函数的值,采用选择算子(如轮盘赌选择)选择一部分优秀的解作为父代。 4. 交叉操作:对选出的父代进行交叉操作,生成新的解。交叉可以通过交换路径片段、插入或删除节点等方式进行。 5. 变异操作:对新生成的解进行变异操作,引入一定的随机性。变异可以通过交换节点位置、改变路径顺序等方式进行。 6. 更新种群:将新生成的解与原有种群进行合并,得到更新后的种群。 7. 终止条件判断:根据预设的终止条件(如达到最大迭代次数或找到满意解),判断是否结束算法。 8. 返回最优解:根据适应度函数的值,选择最优的解作为问题的解决方案。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值