最近在做一个基于物流配送的路径规划的问题,才了解到这个算法,也算是在做一个算法的入门吧。
智能配送计划本质是车辆路径规划(VRP)问题,是运筹中的经典问题。
算法主要设计两个大类
设计算法的大概思路
算法实现的效果:
算法具体实现代码:
# -*- coding: utf-8 -*-
import matplotlib.pyplot as plt
import numpy as np
import random
import pandas as pd
class Node(object):
'''
顾客点类:
c_id:Number,顾客点编号
x:Number,点的横坐标
y:Number,点的纵坐标
demand:Number,点的需求量
ready_time:Number,点的最早访问时间
due_time:Number,点的最晚访问时间
service_time:Number,点的服务时间
belong_veh:所属车辆编号
'''
def __init__(self, c_id, x, y, demand, ready_time, due_time, service_time):
self.c_id = c_id
self.x = x
self.y = y
self.demand = demand
self.ready_time = ready_time
self.due_time = due_time
self.service_time = service_time
self.belong_veh = None
class Vehicle(object):
'''
车辆类:
v_id:Number,车辆编号
cap:Number,车的最大载重量
load:Number,车的载重量
distance:Number,车的行驶距离
violate_time:Number,车违反其经过的各点时间窗时长总和
route:List,车经过的点index的列表
start_time:List,车在每个点的开始服务时间
'''
def __init__(self, v_id: int, cap: int):
self.v_id = v_id
self.cap = cap
self.load = 0
self.distance = 0
self.violate_time = 0
self.route = [0]
self.start_time = [0]
# 插入节点
def insert_node(self, node: int, index: int = 0) -> None:
if index == 0:
self.route.append(node)
else:
self.route.insert(index, node)
# node.belong_veh = self.v_id
self.update_info()
# 根据索引删除节点
def del_node_by_index(self, index: int) -> None:
self.route.pop(index)
self.update_info()
# 根据对象删除节点
def del_node_by_node(self, node: Node) -> None:
self.route.remove(node.c_id)
self.update_info()
# 更新载重、距离、开始服务时间、时间窗违反
def update_info(self) -> None:
# 更新载重
cur_load = 0
for n in self.route:
cur_load += nodes[n].demand
self.load = cur_load
# 更新距离
cur_distance = 0
for i in range(len(self.route) - 1):
cur_distance += distance_matrix[self.route[i]][self.route[i + 1]]
self.distance = cur_distance
# 更新违反时间窗时长总和(硬时间窗,早到等待,不可晚到)
arrival_time = 0
self.start_time = [0]
cur_violate_time = 0
for i in range(1, len(self.route)):
arrival_time += distance_matrix[self.route[i - 1]][self.route[i]] + nodes[self.route[i - 1]].service_time
if arrival_time > nodes[self.route[i]].due_time:
cur_violate_time += arrival_time - nodes[self.route[i]].due_time
elif arrival_time < nodes[self.route[i]].ready_time:
arrival_time = nodes[self.route[i]].ready_time
self.start_time.append(arrival_time)
self.violate_time = cur_violate_time
def __str__(self): # 重载print()
routes = [n for n in self.route]
return '车{}:距离[{:.4f}];载重[{}];时间违反[{:.4f}]\n路径{}\n开始服务时间{}\n'.format(self.v_id, self.distance, self.load,
self.violate_time, routes, self.start_time)
#读取数据文件,返回车辆最大载重,最大车辆数,所有Node组成的列表
def read_data(path:str) -> (int,int,list):
trans_data = pd.read_excel(path, sheet_name=0)
capacity = 200
max_vehicle = 6
nodes = []
for index,row in trans_data.iterrows():
node = Node(*row)
nodes.append(node)
return capacity, max_vehicle, nodes
# 计算距离矩阵
def cal_distance_matrix(nodes: list) -> np.array:
distance_matrix = np.zeros((len(nodes), len(nodes)))
for i in range(len(nodes)):
for j in range(i + 1, len(nodes)):
if i != j:
dis = np.sqrt((nodes[i].x - nodes[j].x) ** 2 + (nodes[i].y - nodes[j].y) ** 2)
distance_matrix[i][j] = distance_matrix[j][i] = dis
return distance_matrix
# 计算一个解的目标函数值,包括惩罚部分:f(vehicles) = distance + p_l*Q +p_t*T
def cal_obj(vehicles: list, p_l: float = 1, p_t: float = 1) -> float:
distance = sum([v.distance for v in vehicles])
T = sum([v.violate_time for v in vehicles])
Q = sum([max(v.load - v.cap, 0) for v in vehicles])
return distance + p_l * Q + p_t * T
# 判断一个解是否可行,并更新惩罚系数p_l和p_t
def check_feasible(vehicles: list, p_l: float = 1, p_t: float = 1, sita: float = 0.5) -> (bool, float, float):
T = sum([v.violate_time for v in vehicles])
Q = sum([max(v.load - v.cap, 0) for v in vehicles])
if Q == 0 and p_l >= 0.001:
p_l /= (1 + sita)
elif Q != 0 and p_l <= 2000:
p_l *= (1 + sita)
if T == 0 and p_t >= 0.001:
p_t /= (1 + sita)
elif T != 0 and p_t <= 2000:
p_t *= (1 + sita)
if T == 0 and Q == 0:
return True, p_l, p_t
else:
return False, p_l, p_t
# APRCH:自适应并行算法(没有调整参数)
def aprch(capacity: int, w_dis=0.6, w_urg=0.11) -> (list, float):
# 输入capacity,w_d,w_u,w_w
# 返回插入的解对应车组成的list和总距离
w_wai = 1 - w_dis - w_urg
FAILED = 1e6
assigned_node_id = set() # 已被分配过的点的集合
num_veh_least = int(sum([n.demand for n in nodes]) / capacity) + 1
vehicles = [Vehicle(num_veh, capacity) for num_veh in range(num_veh_least)]
# 主循环
while len(assigned_node_id) < len(nodes) - 1:
fit_all = np.ones((len(vehicles), len(nodes) - 1)) * np.inf
# 计算每辆车末尾插入每个点的fitness
for k in vehicles:
for n in range(1, len(nodes)):
if n not in assigned_node_id:
fit_dis = distance_matrix[k.route[-1]][n]
fit_urg = nodes[n].due_time - (k.start_time[-1] + nodes[k.route[-1]].service_time + fit_dis)
fit_wai = max(0,
nodes[n].ready_time - (k.start_time[-1] + nodes[k.route[-1]].service_time + fit_dis))
if fit_urg >= 0 and k.load + nodes[n].demand <= capacity:
# 因n从1开始,所以存储索引为 n-1
fit_all[k.v_id][n - 1] = w_dis * fit_dis + w_urg * fit_urg + w_wai * fit_wai
else:
fit_all[k.v_id][n - 1] = FAILED
# 判断是否需要新开一辆车
for row in range(len(vehicles)):
# 因n从1开始,所以实际点编号为存储索引 + 1
node_veh_failed = set(np.ravel(np.argwhere(fit_all[row] == FAILED)) + 1)
if row == 0:
nodes_failed = node_veh_failed
else:
nodes_failed = nodes_failed & node_veh_failed
if len(nodes_failed) > 0:
# 新加入一辆车
vehicles.append(Vehicle(num_veh_least, capacity))
num_veh_least += 1
fit_all = np.ones((len(vehicles), len(nodes) - 1)) * np.inf
# 重新计算每辆车末尾插入每个点的fitness
for k in vehicles:
for n in range(1, len(nodes)):
if n not in assigned_node_id:
fit_dis = distance_matrix[k.route[-1]][n]
fit_urg = nodes[n].due_time - (k.start_time[-1] + nodes[k.route[-1]].service_time + fit_dis)
fit_wai = max(0, nodes[n].ready_time - (
k.start_time[-1] + nodes[k.route[-1]].service_time + fit_dis))
if fit_urg >= 0 and k.load + nodes[n].demand:
# 因n从1开始,所以存储索引为 n-1
fit_all[k.v_id][n - 1] = w_dis * fit_dis + w_urg * fit_urg + w_wai * fit_wai
else:
fit_all[k.v_id][n - 1] = FAILED
# 选择最优的插入车和点
veh_index, node_index = np.where(fit_all == np.min(fit_all))
veh_index = veh_index[0]
# 点编号 = 其存储索引 + 1
node_index = node_index[0] + 1
vehicles[veh_index].insert_node(node_index)
assigned_node_id.add(node_index)
# 为每条路末尾加上0
for k in vehicles:
k.insert_node(0)
# 为每个顾客点更新所属车编号
for v in vehicles:
for i in range(1, len(v.route) - 1):
nodes[v.route[i]].belong_veh = v.v_id
return vehicles, sum([v.distance for v in vehicles])
# %%主程序
if __name__ == '__main__':
import time
# 读取数据
path = r'/Users/hujinhong/Desktop/ct/路径规划/customer.xlsx'
capacity, max_vehicle, nodes = read_data(path) # 获取车的载重,最大车数,顾客节点
print(capacity)
print(max_vehicle)
distance_matrix = cal_distance_matrix(nodes) # 将距离矩阵赋值给车辆的类变量
start = time.perf_counter()
vehicles_aprch, distance_aprch = aprch(capacity, 0.69, 0.11)
end = time.perf_counter()
print('Aprch耗时{:.2f}'.format(end - start))
print('Aprch耗时最优解{:.6f}'.format(distance_aprch))
# print(len(vehicles_aprch))
for i in vehicles_aprch:
print(i.__str__())
listcolor = ['black', 'red', 'blue', 'y', 'brown', 'green', 'orange']
for i in vehicles_aprch:
# print("车辆 ",i.v_id," 的规划情况")
# print(i.route[1])
if (i.route[1] != 0):
x1 = []
y1 = []
# print(i.v_id)
for j in i.route:
x1.append(nodes[j].x)
y1.append(nodes[j].y)
b = random.randint(0, 6)
plt.plot(x1, y1, color=listcolor[b])
plt.scatter(x1, y1, marker='D')
# print(nodes[0].x,nodes[0].y)
# plt.scatter(nodes[0].x, nodes[0].y, marker='D')
plt.show()