遗传算法+Split 求解异构车辆路径规划问题
对孤峰绝顶,云烟竞秀
信息传递
- python实现6种智能算法求解CVRP问题
- python实现7种智能算法求解MDVRP问题
- python实现7种智能算法求解MDVRPTW问题
- Python版MDHFVRPTW问题智能求解算法代码【TS算法】
- Python版MDHFVRPTW问题智能求解算法代码【SA算法】
- Python版MDHFVRPTW问题智能求解算法代码【GA算法】
- Python版MDHFVRPTW问题智能求解算法代码【DPSO算法】
- Python版MDHFVRPTW问题智能求解算法代码【DE算法】
- Python版MDHFVRPTW问题智能求解算法代码【ACO算法】
- Python版HVRP问题智能求解算法代码【GA算法】
1. 适用场景
- 求解HVRP或MDHVRP
- 多车辆类型
- 车辆容量不小于需求节点最大需求
- (多)车辆基地
- 车场车辆总数满足实际需求
2. 求解效果
(1)收敛曲线
(2)车辆路径
3. 代码分析
Christian Prins给出的HVRP的Split方法过程如下:
以上算法在搜索过程中需求节点的备选标签规模会非常大,占用内存且降低搜索效率,因此采用了帕累托思想来遗传非支配解(19行~21行)。为了进一步控制备选标签数量,本期文章还附加实现了通过剩余容量与剩余需求来判断是否产生新的备选标签,提前避免产生不可行的标签。尽管如此,当考虑的异构车辆类型较多、需求点规模较大时,求解效率依然较低(代码结构、python特性、硬件环境)。所以本期文章进一步加入了最大标签数量参数来控制每个需求节点的备选标签规模,通过调节该参数实现速度与质量的相对均衡。
4. 数据格式
这里采用solomon开源数据对算法进行测试,数据文件结构如下图所示,共100个需求点,3个车场,3类车辆,以.csv文件储存数据。
demand.csv文件内容如下图,其中第一行为标题栏,节点id从0开始递增编号。
depot.csv文件内容如下图,其中第一行为标题栏,依次为:车场id,车场平面x坐标,车场平面y坐标,车辆类型,车辆装载量,车辆数量,固定成本,单位里程运输成本。
5. 分步实现
(1)代码整体架构
(2)数据结构
定义Sol()类,Demand()类,Vehicle()类,Model()类,其属性如下表:
- Sol()类,表示一个可行解
属性 | 描述 |
---|---|
node_id_list | 需求节点id有序排列集合 |
obj | 优化目标值 |
fitness | 解的适应度 |
route_list | 车辆路径集合,对应MDVRPTW的解 |
- Demand()类,表示一个需求节点
属性 | 描述 |
---|---|
id | 物理节点id,需唯一 |
x_coord | 物理节点x坐标 |
y_coord | 物理节点y坐标 |
demand | 物理节点需求 |
- Vehicle()类,表示一个车队节点
属性 | 描述 |
---|---|
depot_id | 车辆归属的车场节点节点id,需唯一 |
x_coord | 车辆归属车场节点x坐标 |
y_coord | 车辆归属车场节点y坐标 |
type | 车辆类型 |
capacity | 车辆容量 |
free_speed | 车辆运营速度 |
fixed_cost | 车辆固定成本 |
variable_cost | 车辆变动成本 |
- Model()类,存储算法参数
属性 | 描述 |
---|---|
best_sol | 全局最优解,值类型为Sol() |
demand_dict | 需求节点集合(字典),值类型为Demand() |
vehicle_dict | 车队集合(字典),值类型为Vehicle() |
vehicle_type_list | 车队id集合 |
demand_id_list | 需求节点id集合 |
sol_list | 种群,值类型为Sol() |
distance_matrix | 节点距离矩阵 |
number_of_demands | 需求节点数量 |
pc | 交叉概率 |
pm | 突变概率 |
n_select | 优良个体选择数量 |
popsize | 种群规模 |
(3)数据读取
def readCSVFile(demand_file,depot_file,model):
with open(demand_file,'r') as f:
demand_reader=csv.DictReader(f)
for row in demand_reader:
demand = Demand()
demand.id = int(row['id'])
demand.x_coord = float(row['x_coord'])
demand.y_coord = float(row['y_coord'])
demand.demand = float(row['demand'])
model.demand_dict[demand.id] = demand
model.demand_id_list.append(demand.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:
vehicle = Vehicle()
vehicle.depot_id = row['depot_id']
vehicle.x_coord = float(row['x_coord'])
vehicle.y_coord = float(row['y_coord'])
vehicle.type = row['vehicle_type']
vehicle.capacity=float(row['vehicle_capacity'])
vehicle.numbers=float(row['number_of_vehicle'])
vehicle.fixed_cost=float(row['fixed_cost'])
vehicle.variable_cost=float(row['variable_cost'])
model.vehicle_dict[vehicle.type] = vehicle
model.vehicle_type_list.append(vehicle.type)
(4)距离计算
def calDistanceMatrix(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] = int(dist)
model.distance_matrix[to_node_id, from_node_id] = int(dist)
for _, vehicle in model.vehicle_dict.items():
dist = math.sqrt((model.demand_dict[from_node_id].x_coord - vehicle.x_coord) ** 2
+ (model.demand_dict[from_node_id].y_coord - vehicle.y_coord) ** 2)
model.distance_matrix[from_node_id, vehicle.type] = int(dist)
model.distance_matrix[vehicle.type, from_node_id] = int(dist)
(5)路径成本计算
def calTravelCost(route_list,model):
obj=0
for route in route_list:
vehicle=model.vehicle_dict[route[0]]
distance=0
fixed_cost=vehicle.fixed_cost
variable_cost=vehicle.variable_cost
for i in range(len(route) - 1):
from_node = route[i]
to_node = route[i + 1]
distance += model.distance_matrix[from_node, to_node]
obj += fixed_cost + distance * variable_cost
return obj
(6)适应度计算
def calFitness(model):
max_obj=-float('inf')
best_sol=Sol()
best_sol.obj=float('inf')
number_of_split_failures=0
# calculate travel distance
for sol in model.sol_list:
node_id_list=copy.deepcopy(sol.node_id_list)
ret=splitRoutes(node_id_list, model)
if ret is not None:
sol.route_list=ret
sol.obj=calTravelCost(sol.route_list,model)
if sol.obj > max_obj:
max_obj=sol.obj
if sol.obj < best_sol.obj:
best_sol=copy.deepcopy(sol)
else:
number_of_split_failures+=1
sol.obj=None
# calculate fitness
for sol in model.sol_list:
sol.fitness=max_obj-sol.obj if sol.obj is not None else max_obj
if best_sol.obj<model.best_sol.obj:
model.best_sol=copy.deepcopy(best_sol)
if number_of_split_failures>=model.popsize*0.3:
print("There are {} sols that are unfeasible. Please increase the number of vehicles to obtain better solutions."
.format(number_of_split_failures))
(7)初始解
def generateInitialSol(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)
model.sol_list.append(sol)
(8)个体选择
def selectSol(model):
sol_list=copy.deepcopy(model.sol_list)
model.sol_list=[]
for i in range(model.n_select):
f1_index=random.randint(0,len(sol_list)-1)
f2_index=random.randint(0,len(sol_list)-1)
f1_fit=sol_list[f1_index].fitness
f2_fit=sol_list[f2_index].fitness
if f1_fit<f2_fit:
model.sol_list.append(sol_list[f2_index])
else:
model.sol_list.append(sol_list[f1_index])
(9)交叉
def crossSol(model):
sol_list=copy.deepcopy(model.sol_list)
model.sol_list=[]
while True:
f1_index = random.randint(0, len(sol_list) - 1)
f2_index = random.randint(0, len(sol_list) - 1)
if f1_index!=f2_index:
f1 = copy.deepcopy(sol_list[f1_index])
f2 = copy.deepcopy(sol_list[f2_index])
if random.random() <= model.pc:
cro1_index=int(random.randint(0,len(model.demand_id_list)-1))
cro2_index=int(random.randint(cro1_index,len(model.demand_id_list)-1))
new_c1_f = []
new_c1_m=f1.node_id_list[cro1_index:cro2_index+1]
new_c1_b = []
new_c2_f = []
new_c2_m=f2.node_id_list[cro1_index:cro2_index+1]
new_c2_b = []
for index in range(len(model.demand_id_list)):
if len(new_c1_f)<cro1_index:
if f2.node_id_list[index] not in new_c1_m:
new_c1_f.append(f2.node_id_list[index])
else:
if f2.node_id_list[index] not in new_c1_m:
new_c1_b.append(f2.node_id_list[index])
for index in range(len(model.demand_id_list)):
if len(new_c2_f)<cro1_index:
if f1.node_id_list[index] not in new_c2_m:
new_c2_f.append(f1.node_id_list[index])
else:
if f1.node_id_list[index] not in new_c2_m:
new_c2_b.append(f1.node_id_list[index])
new_c1=copy.deepcopy(new_c1_f)
new_c1.extend(new_c1_m)
new_c1.extend(new_c1_b)
f1.nodes_seq=new_c1
new_c2=copy.deepcopy(new_c2_f)
new_c2.extend(new_c2_m)
new_c2.extend(new_c2_b)
f2.nodes_seq=new_c2
model.sol_list.append(copy.deepcopy(f1))
model.sol_list.append(copy.deepcopy(f2))
else:
model.sol_list.append(copy.deepcopy(f1))
model.sol_list.append(copy.deepcopy(f2))
if len(model.sol_list)>model.popsize:
break
(10)变异
def muSol(model):
sol_list=copy.deepcopy(model.sol_list)
model.sol_list=[]
while True:
f1_index = int(random.randint(0, len(sol_list) - 1))
f1 = copy.deepcopy(sol_list[f1_index])
m1_index=random.randint(0,len(model.demand_id_list)-1)
m2_index=random.randint(0,len(model.demand_id_list)-1)
if m1_index!=m2_index:
if random.random() <= model.pm:
node1=f1.node_id_list[m1_index]
f1.node_id_list[m1_index]=f1.node_id_list[m2_index]
f1.node_id_list[m2_index]=node1
model.sol_list.append(copy.deepcopy(f1))
else:
model.sol_list.append(copy.deepcopy(f1))
if len(model.sol_list)>model.popsize:
break
(11)绘制收敛曲线
def plotObj(obj_list):
plt.rcParams['font.sans-serif'] = ['SimHei'] #show chinese
plt.rcParams['axes.unicode_minus'] = False # Show minus sign
plt.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()
(12)绘制车辆路径
def plotRoutes(model):
for route in model.best_sol.route_list:
x_coord=[model.vehicle_dict[route[0]].x_coord]
y_coord=[model.vehicle_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.vehicle_dict[route[-1]].x_coord)
y_coord.append(model.vehicle_dict[route[-1]].y_coord)
plt.grid()
if route[0]=='v1':
plt.plot(x_coord,y_coord,marker='o',color='black',linewidth=0.5,markersize=5)
elif route[0]=='v2':
plt.plot(x_coord,y_coord,marker='o',color='orange',linewidth=0.5,markersize=5)
elif route[0]=='v3':
plt.plot(x_coord,y_coord,marker='o',color='r',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()
(13)输出结果
def outPut(model):
work=xlsxwriter.Workbook('result.xlsx')
worksheet=work.add_worksheet()
worksheet.write(0, 0, 'obj')
worksheet.write(0, 1,model.best_sol.obj)
worksheet.write(1, 0,'vehicleID')
worksheet.write(1, 1,'depotID')
worksheet.write(1, 2, 'vehicleType')
worksheet.write(1, 3,'route')
for row,route in enumerate(model.best_sol.route_list):
worksheet.write(row+2,0,str(row+1))
depot_id=model.vehicle_dict[route[0]].depot_id
worksheet.write(row+2,1,depot_id)
worksheet.write(row+2,2,route[0])
r=[str(i)for i in route]
worksheet.write(row+2,3, '-'.join(r))
work.close()
(14)主函数
def run(demand_file,depot_file,epochs,pc,pm,popsize,n_select,max_labels):
"""
:param demand_file: demand file path
:param depot_file: depot file path
:param epochs: Iterations
:param pc: Crossover probability
:param pm: Mutation probability
:param popsize: Population size
:param max_labels: maxium number of labels of each node
:return:
"""
model=Model()
model.pc=pc
model.pm=pm
model.popsize=popsize
model.n_select=n_select
model.max_labels = max_labels
readCSVFile(demand_file,depot_file,model)
calDistanceMatrix(model)
generateInitialSol(model)
history_best_obj = []
best_sol=Sol()
best_sol.obj=float('inf')
model.best_sol=best_sol
start_time=time.time()
for ep in range(epochs):
calFitness(model)
selectSol(model)
crossSol(model)
muSol(model)
history_best_obj.append(model.best_sol.obj)
print("%s/%s, best obj: %.3f, runtime: %.3f" % (ep+1,epochs,model.best_sol.obj,time.time()-start_time))
plotObj(history_best_obj)
plotRoutes(model)
outPut(model)
6. 完整代码
如有错误,欢迎交流。
https://download.csdn.net/download/python_n/85619855
参考
- Order-first split-second methods for vehicle routing problems: A review