├── MyFunction.py ├── Parameter.py ├── mian.py ├── CVRPV_CW.py ├── My_soea_psy_EGA_templet.py ├── MyProblem.py └── CVRPDV_ortools.py /MyFunction.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import numpy as np 4 | from CVRPDV_ortools import vehicle_routing_OR 5 | from CVRPV_CW import vehicle_routing_CW 6 | def Ind2Chroms(vehicle_routing): 7 | Chrom1=[i for i in vehicle_routing if i !=0] 8 | Chrom2=[] 9 | indx=0 10 | for i in vehicle_routing: 11 | if i==0: 12 | indx+=1 13 | else: 14 | Chrom2.append(indx) 15 | Chroms=[Chrom1,Chrom2] 16 | return Chroms 17 | 18 | 19 | Chroms1=Ind2Chroms(vehicle_routing=vehicle_routing_OR) 20 | Chroms2=Ind2Chroms(vehicle_routing=vehicle_routing_CW) 21 | 22 | Chroms_pro=[np.array([Chroms1[0],Chroms2[0]]),np.array([Chroms1[1],Chroms2[1]])] 23 | # print(Chroms_pro) 24 | 25 | -------------------------------------------------------------------------------- /Parameter.py: -------------------------------------------------------------------------------- 1 | 2 | import pandas as pd 3 | import numpy as np 4 | """输入参数:由于即时调度时间太短,现将时间参数放大十倍""" 5 | 6 | """前置仓参数""" 7 | L=15 #通道长度 8 | W=2 #两通道之间距离 9 | Vpick=15 #拣选速度 10 | Vtravel=80 #行走速度 11 | t_set_up=0.15*10 #拣货准备时间(并入拣货时间计算) 12 | t_convey=round(3/4,1)*10 #分区传送时间 13 | t_pack=0.05*10 #每个品项需要0.05分钟打包时间(单个订单打包时间控制在1分钟以内) 14 | c_op=1.5 #单位时间订单拣选成本 15 | 16 | """配送参数""" 17 | v_trans=500 #车辆行驶速度:500米/分钟 18 | Q=12 #配送车容量(最大载重量) 19 | delivery_cost_perMin=0 20 | c_d=0.005 # 配送员行驶成本(车耗、燃油、工资)5元/公里 21 | f=3 #派车固定成本(配送员每单基础工资) 22 | G=999 23 | h1=0.3 #from o to i caution intensity 24 | # h2= 0.75*h1 #from i to j caution intensity i,j!=0 25 | h2= 0.15 26 | """其他参数""" 27 | 28 | num=25 #客户订单/数 29 | 30 | #订单履行时间期限 31 | lm=30*10 #30min 32 | c_od=2 #拣选逾期单位惩罚成本 33 | c_od_d=5 #配送逾期单位惩罚成本 34 | url="D://Onedrive/SEU/Paper_chen/Paper_2/data_figure_table/cus_data_origin.csv" #数据存放文件路径 35 | rawdata=pd.read_csv(url,nrows =num+1, header=None) 36 | # 坐标 37 | X = list(rawdata.iloc[:, 1]) 38 | Y = list(rawdata.iloc[:, 2]) 39 | # 最早到达时间 40 | eh = list(rawdata.iloc[:, 4]) 41 | # 最晚到达时间 42 | lh = list(rawdata.iloc[:, 5]) 43 | demands=list(rawdata.iloc[:, 3]) #q[i] 44 | srtime=list(rawdata.iloc[:, 3]) 45 | location=list(zip(X,Y)) 46 | time_windows =[(eh[i], lh[i]) for i in range(len(rawdata))] 47 | def distance(location): 48 | row=len(location) 49 | Dis=np.zeros((row,row)) 50 | for i in range(row): 51 | for j in range(i+1,row): 52 | Dis[i,j]=(abs(location[i][0]-location[j][0])+abs(location[i][1]-location[j][1]))*300 53 | Dis[j,i]=Dis[i,j] 54 | return Dis 55 | D=distance(location) 56 | def traveltime(D,v,h1,h2): 57 | row=D.shape[0] 58 | T=np.zeros((row,row)) 59 | for i in range(row): 60 | for j in range(row): 61 | if i==0: 62 | T[i,j]=D[i,j]/(v*(1-h1)) 63 | elif j==0: 64 | T[i,j]=D[i,j]/v 65 | else: 66 | T[i,j]=D[i,j]/(v*(1-h2)) 67 | return T 68 | T=traveltime(D,v_trans,h1,h2) -------------------------------------------------------------------------------- /mian.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import numpy as np 3 | import pandas as pd 4 | import geatpy as ea # import geatpy 5 | import matplotlib.pyplot as plt 6 | from matplotlib import rcParams 7 | import pandas as pd 8 | import seaborn as sns 9 | from MyProblem import MyProblem # 导入自定义问题接口 10 | from My_soea_psy_EGA_templet import My_soea_psy_EGA_templet 11 | 12 | """================================外部环境设置============================""" 13 | sns.set()#切换到seaborn的默认运行配置 14 | sns.set_style("whitegrid") 15 | 16 | params={'font.family':'serif', 17 | 'font.serif':'Times New Roman', 18 | # 'figure.dpi': 300, 19 | # 'savefig.dpi': 300, 20 | # 'font.style':'normal', # ‘normal’, ‘italic’ or ‘oblique’. 21 | # 'font.weight':'normal', #or 'blod' 22 | 'font.size':12, #or large,small 23 | } 24 | rcParams.update(params) 25 | 26 | 27 | """================================实例化问题对象============================""" 28 | problem = MyProblem() # 生成问题对象 29 | """==================================种群设置===============================""" 30 | NIND = 60 # 种群规模 31 | # 创建区域描述器,这里需要创建两个,前25个变量用P编码,剩余变量用RI编码 32 | Encodings = ['P', 'RI'] 33 | Field1 = ea.crtfld(Encodings[0], problem.varTypes[:problem.num] ,problem.ranges[:,:problem.num], problem.borders[:,:problem.num]) 34 | Field2 = ea.crtfld(Encodings[1], problem.varTypes[problem.num:], problem.ranges[:,problem.num:], problem.borders[:,problem.num:]) 35 | Fields = [Field1, Field2] 36 | population = ea.PsyPopulation(Encodings, Fields, NIND) # 实例化种群对象(此时种群还没被初始化,仅仅是完成种群对象的实例化) 37 | 38 | # prophetPop = ea.PsyPopulation(Encodings, Fields, 2) # 实例化先知种群对象(此时种群还没被初始化,仅仅是完成种群对象的实例化) 39 | # from MyFunction import Chroms_pro 40 | # prophetPop.initChrom( NIND = 2) 41 | # prophetPop.Chroms=Chroms_pro 42 | """================================算法参数设置=============================""" 43 | myAlgorithm = My_soea_psy_EGA_templet(problem, population) # 实例化一个算法模板对象 44 | myAlgorithm.MAXGEN = 400 # 最大进化代数 45 | myAlgorithm.trappedValue = 1e-10 # “进化停滞”判断阈值 46 | myAlgorithm.maxTrappedCount = 80 # 进化停滞计数器最大上限值,如果连续maxTrappedCount代被判定进化陷入停滞,则终止进化 47 | myAlgorithm.drawing = 1 # 设置绘图方式(0:不绘图;1:绘制结果图;2:绘制目标空间过程动画;3:绘制决策空间过程动画) 48 | 49 | 50 | """=======================调用算法模板进行种群进化=======================""" 51 | [population, obj_trace, var_trace] = myAlgorithm.run() # 执行算法模板 52 | 53 | 54 | population.save() # 把最后一代种群的信息保存到文件中 55 | """===============================输出结果及绘图============================""" 56 | # 输出结果 57 | best_gen = np.argmin(problem.maxormins * obj_trace[:, 1]) # 记录最优种群个体是在哪一代 58 | best_ObjV = np.min(obj_trace[:, 1]) 59 | ind_best=var_trace[best_gen, :] #最优个体(基因型) 60 | routes_best=problem.decodeInd(ind_best) #最优车辆调度 61 | C_ol=problem.loadPenalty(routes_best) 62 | C_od_d=problem.timePenalty(routes_best) 63 | C_d=best_ObjV-C_ol-C_od_d 64 | 65 | print('有效进化代数:%s'%(obj_trace.shape[0])) 66 | print('最优的一代是第 %s 代'%(best_gen + 1)) 67 | print('评价次数:%s'%(myAlgorithm.evalsNum)) 68 | print('时间已过 %s 秒'%(myAlgorithm.passTime)) 69 | 70 | 71 | print('最优车辆调度为:',routes_best) 72 | # print("各点访问时间:",problem.timeTable(routes_best)) 73 | print('最优目标函数值:%s'%(best_ObjV)) 74 | print("超载惩罚成本:",C_ol) 75 | print("时间窗惩罚成本:",C_od_d) 76 | print("配送成本:",C_d) 77 | 78 | 79 | # 绘图 80 | def plot(routes): 81 | """ 82 | :data 配送批次顺序, eg:data=[0,10,4,14,12,0,7,8,19,3,0,9,16,0,15,5,6,0,13,11,18,20,2,0,17,1,0] 83 | :url:客户数据文件路径, eg:url = "D://Onedrive/SEU/Paper_chen/Paper_2/data_figure_table/cus_data_origin.csv" 84 | :num 客户数 85 | """ 86 | rawdata = pd.read_csv(problem.url,nrows =problem.num+1, header=None) 87 | temp=[i[1::] for i in routes] 88 | ind=[0]+[item for sublist in temp for item in sublist] 89 | # 坐标 90 | X = list(rawdata.iloc[:, 1]) 91 | Y = list(rawdata.iloc[:, 2]) 92 | Xorder = [X[i] for i in ind] 93 | Yorder = [Y[i] for i in ind] 94 | plt.plot(Xorder, Yorder, c='black', lw=1,zorder=1) 95 | plt.scatter(X, Y, c='black',marker='*',zorder=2) 96 | plt.scatter([X[0]], [Y[0]], c='black',marker='o', zorder=3) 97 | # plt.scatter(X[,m:], Y[,m:], marker='^', zorder=3) 98 | 99 | plt.xticks(range(11)) 100 | plt.yticks(range(11)) 101 | # plt.xlabel('x坐标') 102 | # plt.ylabel('y坐标') 103 | # plt.title(self.name) 104 | plt.savefig('roadmap.eps', dpi=600, bbox_inches='tight') 105 | plt.show() 106 | 107 | plt.figure() 108 | plot(routes_best) 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /CVRPV_CW.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | 4 | import csv 5 | from operator import itemgetter 6 | import pandas as pd 7 | import numpy as np 8 | from Parameter import num,url,v_trans,Q,c_d,f,h1,h2,rawdata,D,T,time_windows,location,demands 9 | 10 | class Vrp(): 11 | 12 | # -----------初始数据定义--------------------- 13 | 14 | def __init__(self): 15 | self.mans = num # 客户数量 16 | self.tons = Q # 车辆载重 17 | self.distanceLimit = 60000 # 车辆一次行驶的最大距离 18 | # self.distance = [] # 各个客户及配送中心距离 19 | self.q = demands # 8个客户分布需要的货物的需求量,第0位为配送中心自己 20 | self.savings = [] # 节约度 21 | self.Routes = [] # 路线 22 | self.Cost = 0 # 总路程 23 | def distance(location,h1=h1,h2=h2): 24 | row=len(location) 25 | Dis=np.zeros((row,row)) 26 | for i in range(row): 27 | for j in range(i+1,row): 28 | Dis[i,j]=(abs(location[i][0]-location[j][0])+abs(location[i][1]-location[j][1]))*300 29 | Dis[j,i]=Dis[i,j] 30 | return Dis 31 | self.distance=distance(location) 32 | 33 | # -----------导入距离数据--------------------- 34 | 35 | def datainput(self): 36 | self.distance=distance(location) 37 | 38 | 39 | 40 | # -----------节约算法主程序--------------------- 41 | 42 | def savingsAlgorithms(self): 43 | saving = 0 44 | for i in range(1, len(self.q)): 45 | self.Routes.append([i]) 46 | 47 | for i in range(1, len(self.Routes) + 1): # 使用Sij = Ci0 + C0j - Cij计算节约度 48 | for j in range(1, len(self.Routes) + 1): 49 | if i == j: 50 | pass 51 | else: 52 | saving = (self.distance[i][0] + self.distance[0][j]) - self.distance[i][j] 53 | self.savings.append([i, j, saving]) # 将结果以元组形式存放在列表中 54 | 55 | self.savings = sorted(self.savings, key=itemgetter(2), reverse=True) # 按照节约度从大到小进行排序 56 | # for i in range(len(self.savings)): 57 | # print(self.savings[i][0],'--',self.savings[i][1], " ",self.savings[i][2]) # 打印节约度 58 | 59 | for i in range(len(self.savings)): 60 | startRoute = [] 61 | endRoute = [] 62 | routeDemand = 0 63 | for j in range(len(self.Routes)): 64 | if (self.savings[i][0] == self.Routes[j][-1]): 65 | endRoute = self.Routes[j] 66 | elif (self.savings[i][1] == self.Routes[j][0]): 67 | startRoute = self.Routes[j] 68 | 69 | if ((len(startRoute) != 0) and (len(endRoute) != 0)): 70 | for k in range(len(startRoute)): 71 | routeDemand += self.q[startRoute[k]] 72 | for k in range(len(endRoute)): 73 | routeDemand += self.q[endRoute[k]] 74 | routeDistance = 0 75 | routestore = [0]+endRoute+startRoute+[0] 76 | for i in range(len(routestore)-1): 77 | # print(routestore[i],routestore[i+1]) 78 | # print(self.distance[routestore[i]][routestore[i+1]]) 79 | routeDistance += self.distance[routestore[i]][routestore[i+1]] 80 | 81 | #print(routestore,"== ==:",routeDistance) 82 | 83 | if (routeDemand <= self.tons) and (routeDistance <= self.distanceLimit): # 按照限制规则对​​路线进行更改 84 | self.Routes.remove(startRoute) 85 | self.Routes.remove(endRoute) 86 | self.Routes.append(endRoute + startRoute) 87 | break 88 | 89 | for i in range(len(self.Routes)): 90 | self.Routes[i].insert(0, 0) 91 | self.Routes[i].insert(len(self.Routes[i]), 0) 92 | 93 | # -----------输出最终结果--------------------- 94 | 95 | def printRoutes(self): 96 | for i in self.Routes: 97 | costs = 0 98 | for j in range(len(i)-1): 99 | costs += self.distance[i[j]][i[j+1]] 100 | print("路线: ",i," 路程: ",costs) 101 | 102 | def calcCosts(self): 103 | for i in range(len(self.Routes)): 104 | for j in range(len(self.Routes[i]) - 1): 105 | self.Cost += self.distance[self.Routes[i][j]][self.Routes[i][j + 1]] 106 | 107 | # print("\nTotal Distance: ", round(self.Cost, 3)) 108 | 109 | # -----------Master函数--------------------- 110 | 111 | def start(self): # Master函数,调用所有其他函数 112 | # print("== == == == == == == == == == == == == == == 导入数据 == == == == == == == = == == == == == == == =") 113 | # self.distance() 114 | # print("== == == 距离表 == == ==") 115 | # for i in self.distance: 116 | # print(i) 117 | # print("== == == 需求表 == == ==") 118 | # print(self.q) 119 | # print("== == == 限制条件 == == ==") 120 | # print("车辆最大载重:",self.tons) 121 | # print("车辆最长运输距离:", self.distanceLimit) 122 | # print("== == == == == == == == == == == == == == == 节约度 == == == == == == == = == == == == == == == =") 123 | self.savingsAlgorithms() # 函数调用计算节省量并生成路线 124 | # print("== == == == == == == == == == == == == == == 结果 == == == == == == == = == == == == == == == =") 125 | # self.printRoutes() 126 | self.calcCosts() 127 | # self.datainput() 128 | return self.Routes,self.Cost 129 | 130 | vrp = Vrp() 131 | # Routes,Cost=vrp.start() 132 | Routes,TDis=vrp.start() 133 | vehicle_routing_CW=[0] 134 | for i in Routes: 135 | vehicle_routing_CW+=i[1:] 136 | 137 | 138 | -------------------------------------------------------------------------------- /My_soea_psy_EGA_templet.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import numpy as np 3 | import geatpy as ea # 导入geatpy库 4 | from sys import path as paths 5 | from os import path 6 | import time 7 | paths.append(path.split(path.split(path.realpath(__file__))[0])[0]) 8 | 9 | class My_soea_psy_EGA_templet(ea.SoeaAlgorithm): 10 | 11 | """ 12 | Msoea_psy_EGA_templet.py - Polysomy Elitist Reservation GA templet(精英保留的多染色体遗传算法模板) 13 | 14 | 模板说明: 15 | 该模板基于内置算法模板soea_EGA_templet的多染色体版本, 16 | 因此里面的种群对象为支持混合编码的多染色体种群类PsyPopulation类的对象。 17 | 18 | 算法描述: 19 | 本模板实现的是基于杰出保留的单目标遗传算法。算法流程如下: 20 | 1) 根据编码规则初始化N个个体的种群。 21 | 2) 若满足停止条件则停止,否则继续执行。 22 | 3) 对当前种群进行统计分析,比如记录其最优个体、平均适应度等等。 23 | 4) 独立地从当前种群中选取N-1个母体。 24 | 5) 独立地对这N-1个母体进行交叉操作。 25 | 6) 独立地对这N-1个交叉后的个体进行变异。 26 | 7) 计算当代种群的最优个体,并把它插入到这N-1个交叉后的个体的第一位,得到新一代种群。 27 | 8) 回到第2步。 28 | 29 | 模板使用注意: 30 | 本模板调用的目标函数形如:aimFunc(pop), 31 | 其中pop为种群类的对象,代表一个种群, 32 | pop对象的Phen属性(即种群染色体的表现型)等价于种群所有个体的决策变量组成的矩阵, 33 | 该函数根据该Phen计算得到种群所有个体的目标函数值组成的矩阵,并将其赋值给pop对象的ObjV属性。 34 | 例如:population为一个种群对象,则调用aimFunc(population)即可完成目标函数值的计算, 35 | 此时可通过population.ObjV得到求得的目标函数值,population.CV得到违反约束程度矩阵。 36 | 若不符合上述规范,则请修改算法模板或自定义新算法模板。 37 | 38 | """ 39 | 40 | def __init__(self, problem, population): 41 | ea.SoeaAlgorithm.__init__(self, problem, population) # 先调用父类构造方法 42 | if str(type(population)) != "": 43 | raise RuntimeError('传入的种群对象必须为PsyPopulation类型') 44 | self.name = 'psy-EGA' 45 | self.selFunc = 'tour' # 锦标赛选择算子 46 | # 由于有多个染色体,因此需要用多个重组和变异算子 47 | self.recOpers = [] 48 | self.mutOpers = [] 49 | for i in range(population.ChromNum): 50 | if population.Encodings[i] == 'P': 51 | recOper = ea.Xovpmx(XOVR = 0.8) # 生成部分匹配交叉算子对象 52 | mutOper = ea.Mutinv(Pm = 0.7) # 生成逆转变异算子对象 53 | else: 54 | recOper = ea.Xovdp(XOVR = 1) # 生成两点交叉算子对象 55 | if population.Encodings[i] == 'BG': 56 | mutOper = ea.Mutbin(Pm = None) # 生成二进制变异算子对象,Pm设置为None时,具体数值取变异算子中Pm的默认值 57 | elif population.Encodings[i] == 'RI': 58 | mutOper = ea.Mutbga(Pm = 1/self.problem.Dim, MutShrink = 0.5, Gradient = 20) # 生成breeder GA变异算子对象 59 | else: 60 | raise RuntimeError('编码方式必须为''BG''、''RI''或''P''.') 61 | self.recOpers.append(recOper) 62 | self.mutOpers.append(mutOper) 63 | 64 | def finishing(self, population): # 重新改写,进化完成后调用的函数 65 | # 处理进化记录器 66 | delIdx = np.where(np.isnan(self.obj_trace))[0] 67 | self.obj_trace = np.delete(self.obj_trace, delIdx, 0) 68 | self.var_trace = np.delete(self.var_trace, delIdx, 0) 69 | if self.obj_trace.shape[0] == 0: 70 | raise RuntimeError('error: No feasible solution. (有效进化代数为0,没找到可行解。)') 71 | self.passTime += time.time() - self.timeSlot # 更新用时记录 72 | # 绘图 73 | if self.drawing != 0: 74 | #调用绘图函数,用C++写的底层函数 75 | ea.trcplot(self.obj_trace, 76 | [['The Mean Value', 'The Best Value']], 77 | xlabels = [['Number of Generation']], 78 | ylabels = [['Value']], 79 | gridFlags = [[False]]) 80 | # 返回最后一代种群、进化记录器、变量记录器以及执行时间 81 | return [population, self.obj_trace, self.var_trace] 82 | 83 | 84 | 85 | def run(self, prophetPop = None): 86 | # prophetPop为先知种群(即包含先验知识的种群) 87 | #==========================初始化配置=========================== 88 | population = self.population 89 | NIND = population.sizes 90 | self.initialization() # 初始化算法模板的一些动态参数 91 | #===========================准备进化============================ 92 | population.initChrom(NIND) # 初始化种群染色体矩阵(内含染色体解码,详见PsyPopulation类的源码) 93 | 94 | #插入较优初始解 95 | 96 | from MyFunction import Chroms_pro 97 | population.setChrom(Chroms_pro) #插入想要初始解 98 | # from MyFunction import Chroms1,Chroms2 99 | # population.setChrom(Chroms2) 100 | # population.setChrom(Chroms1) 101 | # print(population.Chroms) 102 | 103 | self.problem.aimFunc(population) # 计算种群的目标函数值 104 | self.evalsNum = population.sizes # 记录评价次数 105 | # 插入先验知识(注意:这里不会对先知种群prophetPop的合法性进行检查,故应确保prophetPop是一个种群类且拥有合法的Chrom、ObjV、Phen等属性) 106 | if prophetPop is not None: 107 | population = (prophetPop + population)[:NIND] # 插入先知种群 108 | population.FitnV = ea.scaling(self.problem.maxormins * population.ObjV) # 计算适应度 109 | #===========================开始进化============================ 110 | """EGA""" 111 | # while self.terminated(population) == False: 112 | # bestIndi = population[np.argmax(population.FitnV, 0)] # 得到当代的最优个体 113 | # # 选择 114 | # offspring = population[ea.selecting(self.selFunc, population.FitnV, NIND - 1)] 115 | # # 进行进化操作,分别对各种编码的染色体进行重组和变异 116 | # for i in range(population.ChromNum): 117 | # offspring.Chroms[i] = self.recOpers[i].do(offspring.Chroms[i]) # 重组 118 | # offspring.Chroms[i] = self.mutOpers[i].do(offspring.Encodings[i], offspring.Chroms[i], offspring.Fields[i]) # 变异 119 | # # 求进化后个体的目标函数值 120 | # offspring.Phen = offspring.decoding() # 染色体解码 121 | # self.problem.aimFunc(offspring) # 计算目标函数值 122 | # self.evalsNum += offspring.sizes # 更新评价次数 123 | # population = bestIndi + offspring # 更新种群 124 | # population.FitnV = ea.scaling(self.problem.maxormins * population.ObjV) # 计算适应度 125 | 126 | """SEGA""" #增强版精英保留 127 | while self.terminated(population) == False: 128 | # 选择 129 | offspring = population[ea.selecting(self.selFunc, population.FitnV, NIND)] 130 | # 进行进化操作,分别对各种编码的染色体进行重组和变异 131 | for i in range(population.ChromNum): 132 | offspring.Chroms[i] = self.recOpers[i].do(offspring.Chroms[i]) # 重组 133 | offspring.Chroms[i] = self.mutOpers[i].do(offspring.Encodings[i], offspring.Chroms[i], offspring.Fields[i]) # 变异 134 | # 求进化后个体的目标函数值 135 | offspring.Phen = offspring.decoding() # 染色体解码 136 | self.problem.aimFunc(offspring) # 计算目标函数值 137 | self.evalsNum += offspring.sizes # 更新评价次数 138 | population = population + offspring # 父子合并 139 | population.FitnV = ea.scaling(self.problem.maxormins * population.ObjV, population.CV) # 计算适应度 140 | # 得到新一代种群 141 | population = population[ea.selecting('dup', population.FitnV, NIND)] # 采用基于适应度排序的直接复制选择生成新一代种群 142 | 143 | return self.finishing(population) # 调用finishing完成后续工作并返回结果 144 | -------------------------------------------------------------------------------- /MyProblem.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import numpy as np 4 | import pandas as pd 5 | import geatpy as ea 6 | 7 | """ 8 | 一个带时间窗和载重约束的单目标车辆路径优化问题:CVRPTW 9 | 10 | """ 11 | 12 | class MyProblem(ea.Problem): # 继承Problem父类 13 | def __init__(self): 14 | name = 'CVRPTW' # 初始化name(函数名称,可以随意设置) 15 | M = 1 # 初始化M(目标维数) 16 | maxormins = [1] # 初始化maxormins(目标最小最大化标记列表,1:最小化该目标;-1:最大化该目标) 17 | Dim = 50 # 初始化Dim(决策变量维数)(订单排列以及对应指派的车辆) 18 | varTypes = [1] * Dim # 初始化varTypes(决策变量的类型,元素为0表示对应的变量是连续的;1表示是离散的) 19 | lb = [1] * Dim # 决策变量下界 20 | ub =[25] * int(Dim/2)+[20]*int(Dim/2) # 决策变量上界, 25张订单,20辆车 21 | lbin = [1] * Dim # 决策变量下边界 22 | ubin = [1] * Dim # 决策变量上边界 23 | # 调用父类构造方法完成实例化 24 | ea.Problem.__init__(self, name, M, maxormins, Dim, varTypes, lb, ub, lbin, ubin) 25 | 26 | """前置仓参数""" 27 | self.L=15 #通道长度 28 | self.W=2 #两通道之间距离 29 | self.Vpick=15 #拣选速度 30 | self.Vtravel=80 #行走速度 31 | self.t_set_up=0.15*10 #拣货准备时间(并入拣货时间计算) 32 | self.t_convey=round(3/4,1)*10 #分区传送时间 33 | self.t_pack=0.05*10 #每个品项需要0.05分钟打包时间(单个订单打包时间控制在1分钟以内) 34 | self.c_op=1.5 #单位时间订单拣选成本 35 | 36 | """配送参数""" 37 | self.v_trans=500 #车辆行驶速度:500米/分钟 38 | self.Q=12 #配送车容量(最大载重量) 39 | self.c_d=0.005 # 配送员行驶成本(车耗、燃油、工资)5元/公里 40 | self.f=3 #派车固定成本(配送员每单基础工资)#3 41 | self.G=999 42 | self.h1=0.3 #from o to i caution intensity 43 | # self.h2= 0.75*h1 #from i to j caution intensity i,j!=0 44 | self.h2= 0.15 45 | 46 | """其他参数""" 47 | self.num=25 #客户订单/数 48 | self.lm=30*10 #30min #订单履行时间期限 49 | self.c_od_p=2 #拣选逾期惩罚因子 #2 50 | self.c_od_d=5 #配送逾期惩罚因子 #5 51 | self.c_ol=20 #超载惩罚因子 #20 52 | self.url="D://Onedrive/SEU/Paper_chen/Paper_2/data_figure_table/cus_data_origin.csv" #数据存放文件路径 53 | self.rawdata=pd.read_csv(self.url,nrows =self.num+1, header=None) 54 | # 坐标 55 | self.X = list(self.rawdata.iloc[:, 1]) 56 | self.Y = list(self.rawdata.iloc[:, 2]) 57 | self.locations=list(zip(self.X,self.Y)) #各点坐标 58 | self.eh = list(self.rawdata.iloc[:, 4]) # 最早到达时间 59 | self.lh = list(self.rawdata.iloc[:, 5]) # 最晚到达时间 60 | self.time_windows =[(self.eh[i], self.lh[i]) for i in range(len(self.rawdata))] 61 | self.demands=list(self.rawdata.iloc[:, 3]) #各点需求量 62 | self.svtime=list(self.rawdata.iloc[:, 6]) #各点服务时间 63 | 64 | 65 | 66 | def decodeInd(self,ind): 67 | """ 68 | 功能:用于解构染色体成为路线集 69 | 输入:染色体,例如:[1,2,4,3,5,6,3,2,1,1,2,4] 70 | 输出:解构后的路线集,例如:[[0, 6, 0], [0, 1, 0], [0, 4, 3, 0], [0, 2, 5, 0]] 71 | 72 | """ 73 | lind=int(len(ind)/2) #单条染色体长度(客户数) 74 | ind_cus=ind[:lind] 75 | ind_vhi=ind[lind:] 76 | Idx_vhi=[] #车辆索引值,定位承运订单 77 | for i in ind_vhi: 78 | Idx_vhi.append(tuple([m for m,x in enumerate(ind_vhi) if x==i])) 79 | Idx_vhi=list(set(Idx_vhi)) 80 | routes=[] #根据索引值,将订单进行合并 81 | for i in Idx_vhi: 82 | temp=[0] 83 | for j in i: 84 | temp.append(int(ind_cus[j])) 85 | temp.append(0) 86 | routes.append(temp) 87 | return routes 88 | 89 | 90 | def loadPenalty(self,routes): 91 | ''' 92 | 功能:辅助函数,因为在交叉和突变中可能会产生不符合负载约束的个体,需要对不合要求的个体进行惩罚 93 | 输入:实例,单个个体的路径集 94 | 输出:每个该个体的超载惩罚成本 95 | ''' 96 | overload = 0 97 | # 计算每条路径的负载,取max(0, routeLoad - maxLoad)计入惩罚项 98 | for eachRoute in routes: 99 | routeLoad = np.sum([self.demands[i] for i in eachRoute]) 100 | overload += max(0, routeLoad - self.Q) 101 | penalty=overload * self.c_ol 102 | return penalty 103 | 104 | 105 | def calDist(self,pos1, pos2): 106 | ''' 107 | 功能:计算距离的辅助函数,根据给出的坐标pos1和pos2,返回两点之间的距离 108 | 输入: pos1, pos2 -- (x,y)元组 ; 109 | 输出: 曼哈顿距离 110 | ''' 111 | return (abs(pos1[0] - pos2[0]) + abs(pos1[1] - pos2[1]))*300 112 | 113 | def calcRouteVisitTime(self,route): 114 | ''' 115 | 功能:辅助函数,根据给定路径,计算该路径上各点访问时间(实际开始服务的时间) 116 | 输入:实例,单条路径 117 | 输出:该路径上各点访问时间(实际开始服务的时间) 118 | ''' 119 | # 初始化visitTime数组,其长度应该比eachRoute小2(去除头尾的0节点) 120 | visitTime = [0] * len(route) 121 | arrivalTime=0 122 | arrivalTime += MyProblem.calDist(self,self.locations[0], self.locations[route[1]])/(self.v_trans*(1-self.h1)) #从0出发到下一点的行驶时间 123 | arrivalTime = max(arrivalTime, self.time_windows[route[1]][0]) #实际开始服务时间(需等到左侧时间窗开启) 124 | visitTime[1] = arrivalTime 125 | arrivalTime += self.svtime[route[1]] # 在出发前往下个节点前完成服务,该值等于离开正在访问节点的时间 126 | 127 | for i in range(1, len(route)-1): 128 | 129 | # 计算从路径上当前节点[i]到下一个节点[i+1]的花费的时间 130 | if route[i+1]==0: 131 | arrivalTime += MyProblem.calDist(self,self.locations[route[i]], self.locations[route[i+1]])/(self.v_trans*(1-0)) 132 | else: 133 | arrivalTime += MyProblem.calDist(self,self.locations[route[i]], self.locations[route[i+1]])/(self.v_trans*(1-self.h2)) 134 | arrivalTime = max(arrivalTime, self.time_windows[route[i+1]][0]) 135 | visitTime[i+1] = arrivalTime 136 | arrivalTime += self.svtime[route[i+1]] # 在前往下个节点前完成服务 137 | return visitTime 138 | 139 | def timeTable(self,routes): 140 | ''' 141 | 功能:辅助函数,依照给定配送计划,返回每个顾客受到服务的时间 142 | 输入:单个个体路径集合 143 | 输出:每个节点的访问时间 144 | ''' 145 | visitTimeArrangement = [] #容器,用于存储每个顾客受到服务的时间 146 | for eachRoute in routes: 147 | visitTime = MyProblem.calcRouteVisitTime(self,eachRoute) 148 | visitTimeArrangement.append(visitTime) 149 | return visitTimeArrangement 150 | 151 | def timePenalty(self,routes): 152 | ''' 153 | 功能:辅助函数,对不能按服务时间到达顾客的情况进行惩罚 154 | 输入:单个个体路径集合 155 | 输出:逾期惩罚成本 156 | ''' 157 | visitTimeArrangement = MyProblem.timeTable(self,routes) # 对给定路线,计算到达每个客户的时间 158 | visitTimeArrangement_flatten=[item for sublist in visitTimeArrangement for item in sublist] #合并子列表,拉平 159 | # 索引给定的最迟到达时间 160 | lateTimeArrangement=[] 161 | for eachRoute in routes: 162 | lateTime=[] 163 | for j in eachRoute: 164 | lateTime.append(self.time_windows[j][1]) 165 | lateTime[-1]=100 166 | lateTimeArrangement.append(lateTime) 167 | lateTimeArrangement_flatten=[item for sublist in lateTimeArrangement for item in sublist] #合并子列表,拉平 168 | # 计算各节点延迟时间 169 | timeDelay_flatten=[max(visitTimeArrangement_flatten[i]-lateTimeArrangement_flatten[i],0) for i in range(len(visitTimeArrangement_flatten))] 170 | return np.sum(timeDelay_flatten)*self.c_od_d 171 | 172 | def calRouteLen(self,routes): 173 | '''辅助函数,返回给定路径的总长度''' 174 | totalDistance = 0 # 记录各条路线的总长度 175 | for eachRoute in routes: 176 | # 从每条路径中抽取相邻两个节点,计算节点距离并进行累加 177 | for i,j in zip(eachRoute[0:-1], eachRoute[1::]): 178 | totalDistance += MyProblem.calDist(self,self.locations[int(i)], self.locations[int(j)]) 179 | return totalDistance 180 | 181 | 182 | def evaluate(self,routes): 183 | ''' 184 | 功能:评价函数,目标函数值+惩罚值 185 | 输入:单个个体的路径集 186 | 输出:该个体的评价值 187 | ''' 188 | totalDistance = MyProblem.calRouteLen(self,routes) 189 | cost_num_node=0 190 | for i in routes: 191 | if len(i)<=3: 192 | cost_num_node=+10 #惩罚一条线路只有1个订单的情况 193 | C_d=self.f*len(routes)+self.c_d*totalDistance 194 | C_penalty=MyProblem.loadPenalty(self,routes)+MyProblem.timePenalty(self,routes) 195 | return C_d+C_penalty+cost_num_node 196 | 197 | def aimFunc(self, pop): 198 | ''' 199 | 功能:计算种群的目标函数 200 | 输入:实例,种群 201 | 输出:种群的目标函数值 202 | ''' 203 | inds = pop.Phen # 得到决策变量矩阵,种群的表现型矩阵 204 | routess=[MyProblem.decodeInd(self,i) for i in inds] #解构后的所有个体路径集的集合 205 | ObjV =np.array( [MyProblem.evaluate(self,routes) for routes in routess]).T # 存储所有种群个体对应的目标函数值 206 | pop.ObjV=ObjV.reshape(len(ObjV),1) 207 | 208 | -------------------------------------------------------------------------------- /CVRPDV_ortools.py: -------------------------------------------------------------------------------- 1 | """Capacitated Vehicle Routing Problem with Time Windows (CVRPTW). 2 | ortools==6.10.6025 3 | """ 4 | from __future__ import print_function 5 | from ortools.constraint_solver import pywrapcp 6 | from ortools.constraint_solver import routing_enums_pb2 7 | import pandas as pd 8 | import time 9 | import numpy as np 10 | from Parameter import num,url,v_trans,Q,c_d,f,h1,h2,rawdata,D,T,time_windows,location,demands 11 | 12 | ########################### 13 | # Problem Data Definition # 14 | ########################### 15 | def create_data_model(num=num,url=url): 16 | """Stores the data for the problem""" 17 | data = {} 18 | 19 | _locations = location 20 | 21 | # Multiply coordinates in block units by the dimensions of an average city block, 114m x 80m, 22 | # to get location coordinates. 23 | data["locations"] = [(l[0], l[1]) for l in _locations] 24 | data["num_locations"] = len(data["locations"]) 25 | num_vehicles=50 26 | data["num_vehicles"] = num_vehicles 27 | data["depot"] = 0 28 | data["demands"] = demands 29 | capacities = [] 30 | for i in range(num_vehicles): 31 | capacities.append(12) 32 | data["vehicle_capacities"] = capacities 33 | data["time_windows"] = time_windows #min 34 | data["time_per_demand_unit"] = 0.5 35 | data["vehicle_speed"] = 500 #m/min 即30km/h 36 | return data 37 | ####################### 38 | # Problem Constraints # 39 | ####################### 40 | def manhattan_distance(position_1, position_2): 41 | """Computes the Manhattan distance between two points""" 42 | return (abs(position_1[0] - position_2[0]) + abs(position_1[1] - position_2[1]))*300 43 | 44 | def create_distance_callback(data): 45 | """Creates callback to return distance between points.""" 46 | _distances = {} 47 | 48 | for from_node in range(data["num_locations"]): 49 | _distances[from_node] = {} 50 | for to_node in range(data["num_locations"]): 51 | if from_node == to_node: 52 | _distances[from_node][to_node] = 0 53 | else: 54 | _distances[from_node][to_node] = ( 55 | manhattan_distance(data["locations"][from_node], 56 | data["locations"][to_node])) 57 | 58 | def distance_callback(from_node, to_node): 59 | """Returns the manhattan distance between the two nodes""" 60 | return _distances[from_node][to_node] 61 | 62 | return distance_callback 63 | 64 | def create_demand_callback(data): 65 | """Creates callback to get demands at each location.""" 66 | def demand_callback(from_node, to_node): 67 | return data["demands"][from_node] 68 | return demand_callback 69 | 70 | 71 | def add_capacity_constraints(routing, data, demand_evaluator): 72 | """Adds capacity constraint""" 73 | capacity = "Capacity" 74 | routing.AddDimensionWithVehicleCapacity( 75 | demand_evaluator, 76 | 0, # null capacity slack 77 | data["vehicle_capacities"], # vehicle maximum capacities 78 | True, # start cumul to zero 79 | capacity) 80 | 81 | def create_time_callback(data): 82 | """Creates callback to get total times between locations.""" 83 | def service_time(node): 84 | """Gets the service time for the specified location.""" 85 | return round(data["demands"][node] * data["time_per_demand_unit"],0) 86 | 87 | def travel_time(from_node, to_node,h1=0.3,h2=0.15): 88 | """Gets the travel times between two locations.""" 89 | #h1=0.3 caution intensity when depart from depot 90 | #h2=0.15 caution intensity in-transit 91 | if from_node == to_node: 92 | travel_time = 0 93 | elif from_node==(5,5): 94 | travel_time = manhattan_distance( 95 | data["locations"][from_node], 96 | data["locations"][to_node]) / (data["vehicle_speed"]*(1-h1)) 97 | elif to_node==(5,5): 98 | travel_time = manhattan_distance( 99 | data["locations"][from_node], 100 | data["locations"][to_node]) / (data["vehicle_speed"]*(1-0)) 101 | else: 102 | travel_time = manhattan_distance( 103 | data["locations"][from_node], 104 | data["locations"][to_node]) / (data["vehicle_speed"]*(1-h2)) 105 | return travel_time 106 | 107 | def time_callback(from_node, to_node): 108 | """Returns the total time between the two nodes""" 109 | serv_time = service_time(from_node) 110 | trav_time = travel_time(from_node, to_node) 111 | return serv_time + trav_time 112 | 113 | return time_callback 114 | def add_time_window_constraints(routing, data, time_callback): 115 | """Add Global Span constraint""" 116 | time = "Time" 117 | horizon = 300 118 | routing.AddDimension( 119 | time_callback, 120 | horizon, # allow waiting time 121 | horizon, # maximum time per vehicle 122 | False, # Don't force start cumul to zero. This doesn't have any effect in this example, 123 | # since the depot has a start window of (0, 0). 124 | time) 125 | time_dimension = routing.GetDimensionOrDie(time) 126 | for location_node, location_time_window in enumerate(data["time_windows"]): 127 | index = routing.NodeToIndex(location_node) 128 | time_dimension.CumulVar(index).SetRange(location_time_window[0], location_time_window[1]) 129 | 130 | ########### 131 | # Printer # 132 | ########### 133 | def print_solution(data, routing, assignment): 134 | """Prints assignment on console""" 135 | # Inspect solution. 136 | capacity_dimension = routing.GetDimensionOrDie('Capacity') 137 | time_dimension = routing.GetDimensionOrDie('Time') 138 | total_dist = 0 139 | time_matrix = 0 140 | plan_routings=[] 141 | plan_delivery_time=[] 142 | route_dis=[] 143 | 144 | for vehicle_id in range(data["num_vehicles"]): 145 | plan_node=[] 146 | delivery_time=[] 147 | index = routing.Start(vehicle_id) 148 | plan_output = 'Route for vehicle {0}:\n'.format(vehicle_id) 149 | route_dist = 0 150 | while not routing.IsEnd(index): 151 | node_index = routing.IndexToNode(index) 152 | next_node_index = routing.IndexToNode( 153 | assignment.Value(routing.NextVar(index))) 154 | route_dist += manhattan_distance( 155 | data["locations"][node_index], 156 | data["locations"][next_node_index]) 157 | load_var = capacity_dimension.CumulVar(index) 158 | route_load = assignment.Value(load_var) 159 | time_var = time_dimension.CumulVar(index) 160 | time_min = assignment.Min(time_var) 161 | time_max = assignment.Max(time_var) 162 | plan_node.append(node_index) 163 | delivery_time.append(time_min) 164 | plan_output += ' {0} Load({1}) Time({2},{3}) ->'.format( 165 | node_index, 166 | route_load, 167 | time_min, time_max) 168 | 169 | index = assignment.Value(routing.NextVar(index)) 170 | 171 | node_index = routing.IndexToNode(index) 172 | load_var = capacity_dimension.CumulVar(index) 173 | route_load = assignment.Value(load_var) 174 | time_var = time_dimension.CumulVar(index) 175 | route_time = assignment.Value(time_var) 176 | time_min = assignment.Min(time_var) 177 | time_max = assignment.Max(time_var) 178 | total_dist += route_dist 179 | time_matrix += route_time 180 | delivery_time.append(time_min) 181 | plan_output += ' {0} Load({1}) Time({2},{3})\n'.format(node_index, route_load, 182 | time_min, time_max) 183 | plan_output += 'Distance of the route: {0} m\n'.format(route_dist) 184 | plan_output += 'Load of the route: {0}\n'.format(route_load) 185 | plan_output += 'Time of the route: {0} min\n'.format(route_time) 186 | route_dis.append(route_dist) 187 | plan_routings.append(plan_node) 188 | plan_delivery_time.append(delivery_time) 189 | # print("The nodes in the vehicle route:",plan_node) 190 | # print("The delivery_time at each node in the vehicle route:",delivery_time) 191 | 192 | # print(plan_output) 193 | # print(plan_delivery_time) 194 | 195 | vehicle_routing=[] 196 | for i in plan_routings: 197 | if len(i)==1: 198 | continue 199 | else: 200 | for j in i: 201 | vehicle_routing.append(j) 202 | vehicle_routing.append(0) 203 | batch_delivery_time=[] 204 | for i in plan_delivery_time: 205 | if len(i)==2: 206 | continue 207 | else: 208 | batch_delivery_time.append(i[-2]) 209 | batch_route_delivery_time=[] 210 | for i in plan_delivery_time: 211 | if len(i)==2: 212 | continue 213 | else: 214 | batch_route_delivery_time.append(i[-1]) 215 | # print("vehicle_routing:",vehicle_routing) 216 | batch_route_dis=[] 217 | for i in route_dis: 218 | if i==0: 219 | continue 220 | else: 221 | batch_route_dis.append(i) 222 | # print("batch_route_dis",batch_route_dis) 223 | # print('Total Distance of all routes: {0} m'.format(total_dist)) 224 | # print('Total Time of all routes: {0} min'.format(time_matrix)) 225 | # print('Total cost of all routes: {0} RMB'.format(total_dist) 226 | return vehicle_routing,plan_delivery_time,batch_delivery_time,batch_route_delivery_time,batch_route_dis 227 | 228 | ######## 229 | # Main # 230 | ######## 231 | def main(num,url): 232 | """Entry point of the program""" 233 | # Instantiate the data problem. 234 | data = create_data_model(num,url) 235 | # Create Routing Model 236 | routing = pywrapcp.RoutingModel(data["num_locations"], data["num_vehicles"], data["depot"]) 237 | # Define weight of each edge 238 | distance_callback = create_distance_callback(data) 239 | routing.SetArcCostEvaluatorOfAllVehicles(distance_callback) 240 | # Add Capacity constraint 241 | demand_callback = create_demand_callback(data) 242 | add_capacity_constraints(routing, data, demand_callback) 243 | # Add Time Window constraint 244 | time_callback = create_time_callback(data) 245 | add_time_window_constraints(routing, data, time_callback) 246 | 247 | # Setting first solution heuristic (cheapest addition). 248 | search_parameters = pywrapcp.RoutingModel.DefaultSearchParameters() 249 | search_parameters.first_solution_strategy = ( 250 | routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC) 251 | # Solve the problem. 252 | assignment = routing.SolveWithParameters(search_parameters) 253 | if assignment: 254 | vehicle_routing,plan_delivery_time,batch_delivery_time,batch_route_delivery_time,batch_route_dis= print_solution(data, routing, assignment) 255 | return vehicle_routing 256 | 257 | # if __name__ == '__main__': 258 | # start_time=time.time() 259 | # vehicle_routing=main() 260 | # print("vehicle_routing:",vehicle_routing) 261 | # end_time=time.time() 262 | # used_time = end_time - start_time 263 | # print("CPU Time used:", used_time) 264 | 265 | vehicle_routing_OR=main(num,url) 266 | --------------------------------------------------------------------------------