引言
本系列文章是路径优化问题学习过程中一个完整的学习路线。问题从简单的单车场容量约束CVRP问题到多车场容量约束MDCVRP问题,再到多车场容量+时间窗口复杂约束MDCVRPTW问题,复杂度是逐渐提升的。
如果大家想学习某一个算法,建议从最简单的TSP问题开始学习,一个阶梯一个阶梯走。
如果不是奔着学习算法源码的思路,只想求解出个结果,请看历史文章,有ortools、geatpy、scikit-opt等求解器相关文章,点击→路径优化历史文章,可直接跳转。
本系列文章汇总:
- 1、路径优化历史文章
- 2、路径优化丨带时间窗和载重约束的CVRPTW问题-改进遗传算法:算例RC108
- 3、路径优化丨带时间窗和载重约束的CVRPTW问题-改进和声搜索算法:算例RC108
- 4、路径优化丨复现论文-网约拼车出行的乘客车辆匹配及路径优化
- 5、多车场路径优化丨遗传算法求解MDCVRP问题
- 6、论文复现详解丨多车场路径优化问题:粒子群+模拟退火算法求解
- 7、路径优化丨复现论文外卖路径优化GA求解VRPTW
- 8、多车场路径优化丨蚁群算法求解MDCVRP问题
问题描述
多车场车辆路径问题可描述为:多个配送中心、每个中心有若干配送车辆、存在若干需求点、车辆从配送中心往需求点进行服务。其中:车辆存在载重约束。本文假设:
-
(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