首页 > 编程学习 > 【进阶二】Python实现(MD)VRPTW常见求解算法——差分进化算法(DE)

基于python语言,实现经典差分进化算法(DE)对(多车场)带有时间窗的车辆路径规划问题( (MD) VRPTW )进行求解。

目录

  • 1. 适用场景
  • 2. 求解效果
  • 3. 代码分析
  • 4. 数据格式
  • 5. 分步实现
  • 6. 完整代码
  • 参考

1. 适用场景

  • 求解MDVRPTW或VRPTW
  • 车辆类型单一
  • 车辆容量不小于需求节点最大需求
  • 车辆路径长度或运行时间无限制
  • 需求节点服务成本满足三角不等式
  • 节点时间窗至少满足车辆路径只包含一个需求节点的情况
  • 多车辆基地或单一
  • 各车场车辆总数满足实际需求

2. 求解效果

(1)收敛曲线
在这里插入图片描述

(2)车辆路径
在这里插入图片描述

3. 代码分析

应用DE算法求解MDVRPTW时保留了已有代码的架构与思路,为能够求解带有时间窗的(多车场)车辆路径规划问题,这里参考既有文献对路径分割算法进行了改进("splitRoutes"函数),在分割车辆路径时不仅考虑了车辆容量限制,还考虑了节点的时间窗约束,以此使得分割后的路径可行。在此改进下继承了大量原有代码,降低了代码改进量。

4. 数据格式

以csv文件储存数据,其中demand.csv文件记录需求节点数据,共包含需求节点id,需求节点横坐标,需求节点纵坐标,需求量;depot.csv文件记录车场节点数据,共包含车场id,车场横坐标,车场纵坐标,车队数量。需要注意的是:需求节点id应为整数,车场节点id任意,但不可与需求节点id重复。 可参考github主页相关文件。

5. 分步实现

(1)数据结构
定义Sol()类,Node()类,Model()类,其属性如下表:

  • Sol()类,表示一个可行解
属性描述
obj优化目标值
node_id_list需求节点id有序排列集合
cost_of_distance距离成本
cost_of_time时间成本
route_list车辆路径集合,对应MDVRPTW的解
timetable_list车辆节点访问时间集合,对应MDVRPTW的解
  • Node()类,表示一个网络节点
属性描述
id物理节点id,需唯一
x_coord物理节点x坐标
y_coord物理节点y坐标
demand物理节点需求
depot_capacity车辆基地车队规模
start_time最早开始服务(被服务)时间
end_time最晚结束服务(被服务)时间
service_time需求节点服务时间
  • Model()类,存储算法参数
属性描述
best_sol全局最优解,值类型为Sol()
sol_list可行解集合,值类型为Sol()
demand_dict需求节点集合(字典),值类型为Node()
depot_dict车场节点集合(字典),值类型为Node()
depot_id_list车场节点id集合
demand_id_list需求节点id集合
distance_matrix节点距离矩阵
time_matrix节点旅行时间矩阵
number_of_demands需求节点数量
opt_type优化目标类型,0:最小旅行距离,1:最小时间成本
vehicle_cap车辆容量
vehicle_speed车辆行驶速度,用于计算旅行时间
Cr交叉概率
F缩放因子
popsize种群规模

(2)文件读取

def readCSVFile(demand_file,depot_file,model):with open(demand_file,'r') as f:demand_reader=csv.DictReader(f)for row in demand_reader:node = Node()node.id = int(row['id'])node.x_coord = float(row['x_coord'])node.y_coord = float(row['y_coord'])node.demand = float(row['demand'])node.start_time=float(row['start_time'])node.end_time=float(row['end_time'])node.service_time=float(row['service_time'])model.demand_dict[node.id] = nodemodel.demand_id_list.append(node.id)model.number_of_demands=len(model.demand_id_list)with open(depot_file, 'r') as f:depot_reader = csv.DictReader(f)for row in depot_reader:node = Node()node.id = row['id']node.x_coord = float(row['x_coord'])node.y_coord = float(row['y_coord'])node.depot_capacity = float(row['capacity'])node.start_time=float(row['start_time'])node.end_time=float(row['end_time'])model.depot_dict[node.id] = nodemodel.depot_id_list.append(node.id)

(3)计算距离&时间矩阵

def calDistanceTimeMatrix(model):for i in range(len(model.demand_id_list)):from_node_id = model.demand_id_list[i]for j in range(i + 1, len(model.demand_id_list)):to_node_id = model.demand_id_list[j]dist = math.sqrt((model.demand_dict[from_node_id].x_coord - model.demand_dict[to_node_id].x_coord) ** 2+ (model.demand_dict[from_node_id].y_coord - model.demand_dict[to_node_id].y_coord) ** 2)model.distance_matrix[from_node_id, to_node_id] = distmodel.distance_matrix[to_node_id, from_node_id] = distmodel.time_matrix[from_node_id,to_node_id] = math.ceil(dist/model.vehicle_speed)model.time_matrix[to_node_id,from_node_id] = math.ceil(dist/model.vehicle_speed)for _, depot in model.depot_dict.items():dist = math.sqrt((model.demand_dict[from_node_id].x_coord - depot.x_coord) ** 2+ (model.demand_dict[from_node_id].y_coord - depot.y_coord) ** 2)model.distance_matrix[from_node_id, depot.id] = distmodel.distance_matrix[depot.id, from_node_id] = distmodel.time_matrix[from_node_id,depot.id] = math.ceil(dist/model.vehicle_speed)model.time_matrix[depot.id,from_node_id] = math.ceil(dist/model.vehicle_speed)

(4)目标值计算
适应度计算依赖" splitRoutes "函数对有序节点序列行解分割得到车辆行驶路线,同时在得到各车辆形式路线后在满足车场车队规模条件下分配最近车场,之后调用 " calTravelCost "函数确定车辆访问各路径节点的到达和离开时间点,并计算旅行距离成本和旅行时间成本。

def selectDepot(route,depot_dict,model):min_in_out_distance=float('inf')index=Nonefor _,depot in depot_dict.items():if depot.depot_capacity>0:in_out_distance=model.distance_matrix[depot.id,route[0]]+model.distance_matrix[route[-1],depot.id]if in_out_distance<min_in_out_distance:index=depot.idmin_in_out_distance=in_out_distanceif index is None:print("there is no vehicle to dispatch")sys.exit(0)route.insert(0,index)route.append(index)depot_dict[index].depot_capacity=depot_dict[index].depot_capacity-1return route,depot_dictdef calTravelCost(route_list,model):timetable_list=[]cost_of_distance=0cost_of_time=0for route in route_list:timetable=[]for i in range(len(route)):if i == 0:depot_id=route[i]next_node_id=route[i+1]travel_time=model.time_matrix[depot_id,next_node_id]departure=max(0,model.demand_dict[next_node_id].start_time-travel_time)timetable.append((departure,departure))elif 1<= i <= len(route)-2:last_node_id=route[i-1]current_node_id=route[i]current_node = model.demand_dict[current_node_id]travel_time=model.time_matrix[last_node_id,current_node_id]arrival=max(timetable[-1][1]+travel_time,current_node.start_time)departure=arrival+current_node.service_timetimetable.append((arrival,departure))cost_of_distance += model.distance_matrix[last_node_id, current_node_id]cost_of_time += model.time_matrix[last_node_id, current_node_id]+ current_node.service_time\+ max(current_node.start_time - timetable[-1][1] + travel_time, 0)else:last_node_id = route[i - 1]depot_id=route[i]travel_time = model.time_matrix[last_node_id,depot_id]departure = timetable[-1][1]+travel_timetimetable.append((departure,departure))cost_of_distance +=model.distance_matrix[last_node_id,depot_id]cost_of_time+=model.time_matrix[last_node_id,depot_id]timetable_list.append(timetable)return timetable_list,cost_of_time,cost_of_distancedef extractRoutes(node_id_list,Pred,model):depot_dict=copy.deepcopy(model.depot_dict)route_list = []route = []label = Pred[node_id_list[0]]for node_id in node_id_list:if Pred[node_id] == label:route.append(node_id)else:route, depot_dict=selectDepot(route,depot_dict,model)route_list.append(route)route = [node_id]label = Pred[node_id]route, depot_dict = selectDepot(route, depot_dict, model)route_list.append(route)return route_listdef splitRoutes(node_id_list,model):depot=model.depot_id_list[0]V={id:float('inf') for id in model.demand_id_list}V[depot]=0Pred={id:depot for id in model.demand_id_list}for i in range(len(node_id_list)):n_1=node_id_list[i]demand=0departure=0j=icost=0while True:n_2 = node_id_list[j]demand = demand + model.demand_dict[n_2].demandif n_1 == n_2:arrival= max(model.demand_dict[n_2].start_time,model.depot_dict[depot].start_time+model.time_matrix[depot,n_2])departure=arrival+model.demand_dict[n_2].service_time+model.time_matrix[n_2,depot]if model.opt_type == 0:cost=model.distance_matrix[depot,n_2]*2else:cost=model.time_matrix[depot,n_2]*2else:n_3=node_id_list[j-1]arrival= max(departure-model.time_matrix[n_3,depot]+model.time_matrix[n_3,n_2],model.demand_dict[n_2].start_time)departure=arrival+model.demand_dict[n_2].service_time+model.time_matrix[n_2,depot]if model.opt_type == 0:cost=cost-model.distance_matrix[n_3,depot]+model.distance_matrix[n_3,n_2]+model.distance_matrix[n_2,depot]else:cost=cost-model.time_matrix[n_3,depot]+model.time_matrix[n_3,n_2]\+model.demand_dict[n_2].start_time-departure-model.time_matrix[n_3,depot]+model.time_matrix[n_3,n_2]\+model.time_matrix[n_2,depot]if demand<=model.vehicle_cap and departure-model.time_matrix[n_2,depot] <= model.demand_dict[n_2].end_time:if departure <= model.depot_dict[depot].end_time:n_4=node_id_list[i-1] if i-1>=0 else depotif V[n_4]+cost <= V[n_2]:V[n_2]=V[n_4]+costPred[n_2]=i-1j=j+1else:breakif j==len(node_id_list):breakroute_list= extractRoutes(node_id_list,Pred,model)return len(route_list),route_listdef calObj(sol,model):node_id_list=copy.deepcopy(sol.node_id_list)num_vehicle, sol.route_list = splitRoutes(node_id_list, model)# travel costsol.timetable_list,sol.cost_of_time,sol.cost_of_distance =calTravelCost(sol.route_list,model)if model.opt_type == 0:sol.obj=sol.cost_of_distanceelse:sol.obj=sol.cost_of_time

(5)初始解生成

def genInitialSol(model):demand_id_list=copy.deepcopy(model.demand_id_list)for i in range(model.popsize):seed=int(random.randint(0,10))random.seed(seed)random.shuffle(demand_id_list)sol=Sol()sol.node_id_list=copy.deepcopy(demand_id_list)calObj(sol,model)model.sol_list.append(sol)if sol.obj<model.best_sol.obj:model.best_sol=copy.deepcopy(sol)

(6)突变操作

def muSol(model,v1):x1=model.sol_list[v1].node_id_listwhile True:v2=random.randint(0,len(model.demand_id_list)-1)if v2!=v1:breakwhile True:v3=random.randint(0,len(model.demand_id_list)-1)if v3!=v2 and v3!=v1:breakx2=model.sol_list[v2].node_id_listx3=model.sol_list[v3].node_id_listmu_x=[min(int(x1[i]+model.F*(x2[i]-x3[i])),len(model.demand_id_list)-1) for i in range(len(model.demand_id_list)) ]return mu_x

(7)交叉操作

def adjustRoutes(demand_id_list,model):all_node_list=copy.deepcopy(model.demand_id_list)repeat_node=[]for id,node_id in enumerate(demand_id_list):if node_id in all_node_list:all_node_list.remove(node_id)else:repeat_node.append(id)for i in range(len(repeat_node)):demand_id_list[repeat_node[i]]=all_node_list[i]return demand_id_listdef crossSol(model,vx,vy):cro_x=[]for i in range(len(model.demand_id_list)):if random.random()<model.Cr:cro_x.append(vy[i])else:cro_x.append(vx[i])cro_x=adjustRoutes(cro_x,model)return cro_x

(8)绘制收敛曲线

def plotObj(obj_list):plt.rcParams['font.sans-serif'] = ['SimHei'] #show chineseplt.rcParams['axes.unicode_minus'] = False  # Show minus signplt.plot(np.arange(1,len(obj_list)+1),obj_list)plt.xlabel('Iterations')plt.ylabel('Obj Value')plt.grid()plt.xlim(1,len(obj_list)+1)plt.show()

(9)绘制车辆路线

def plotRoutes(model):for route in model.best_sol.route_list:x_coord=[model.depot_dict[route[0]].x_coord]y_coord=[model.depot_dict[route[0]].y_coord]for node_id in route[1:-1]:x_coord.append(model.demand_dict[node_id].x_coord)y_coord.append(model.demand_dict[node_id].y_coord)x_coord.append(model.depot_dict[route[-1]].x_coord)y_coord.append(model.depot_dict[route[-1]].y_coord)plt.grid()if route[0]=='d1':plt.plot(x_coord,y_coord,marker='o',color='black',linewidth=0.5,markersize=5)elif route[0]=='d2':plt.plot(x_coord,y_coord,marker='o',color='orange',linewidth=0.5,markersize=5)else:plt.plot(x_coord,y_coord,marker='o',color='b',linewidth=0.5,markersize=5)plt.xlabel('x_coord')plt.ylabel('y_coord')plt.show()

(10)输出结果

def outPut(model):work=xlsxwriter.Workbook('result.xlsx')worksheet=work.add_worksheet()worksheet.write(0, 0, 'cost_of_time')worksheet.write(0, 1, 'cost_of_distance')worksheet.write(0, 2, 'opt_type')worksheet.write(0, 3, 'obj')worksheet.write(1,0,model.best_sol.cost_of_time)worksheet.write(1,1,model.best_sol.cost_of_distance)worksheet.write(1,2,model.opt_type)worksheet.write(1,3,model.best_sol.obj)worksheet.write(2,0,'vehicleID')worksheet.write(2,1,'route')worksheet.write(2,2,'timetable')for row,route in enumerate(model.best_sol.route_list):worksheet.write(row+3,0,'v'+str(row+1))r=[str(i)for i in route]worksheet.write(row+3,1, '-'.join(r))r=[str(i)for i in model.best_sol.timetable_list[row]]worksheet.write(row+3,2, '-'.join(r))work.close()

(11)主函数

def run(demand_file,depot_file,epochs,Cr,F,popsize,v_cap,opt_type):""":param demand_file: 需求节点文件路径:param depot_file: 车场节点文件路径:param epochs:迭代次数:param Cr:差分交叉概率:param F:缩放因子:param popsize:种群规模:param v_cap:车辆容量:param opt_type:优化类型:0:最小化车辆数,1:最小化行驶距离:return:"""model=Model()model.vehicle_cap=v_capmodel.Cr=Crmodel.F=Fmodel.popsize=popsizemodel.opt_type=opt_typereadCSVFile(demand_file,depot_file,model)calDistanceTimeMatrix(model)best_sol = Sol()best_sol.obj = float('inf')model.best_sol = best_solgenInitialSol(model)history_best_obj = []for ep in range(epochs):for i in range(popsize):v1=random.randint(0,len(model.demand_id_list)-1)sol=model.sol_list[v1]mu_x=muSol(model,v1)u=crossSol(model,sol.node_id_list,mu_x)new_sol=Sol()new_sol.node_id_list=ucalObj(new_sol,model)if new_sol.obj<=sol.obj:sol=copy.deepcopy(new_sol)if sol.obj<model.best_sol.obj:model.best_sol=copy.deepcopy(sol)history_best_obj.append(model.best_sol.obj)print("%s/%s, best obj: %s" % (ep, epochs, model.best_sol.obj))plotObj(history_best_obj)plotRoutes(model)outPut(model)

6. 完整代码

如有错误,欢迎交流。
代码和数据文件可从github主页免费获取:

https://github.com/PariseC/Algorithms_for_solving_VRP

参考

  1. A Genetic Algorithm for The Vehicle Routing Problem with Time Windows
  2. A simple and effective evolutionary algorithm for the vehicle routing problem

本文链接:https://www.ngui.cc/el/1524563.html
Copyright © 2010-2022 ngui.cc 版权所有 |关于我们| 联系方式| 豫B2-20100000