基于python语言,采用经典自适应大邻域算法(ALNS)对 带硬时间窗的需求拆分车辆路径规划问题(SDVRPTW) 进行求解。
目录
- 往期优质资源
- 1. 适用场景
- 2. 代码调整
- 2.1 需求拆分
- 2.2 需求拆分后的服务时长取值问题
- 3. 求解结果
- 4. 代码片段
- 参考
往期优质资源
经过一年多的创作,目前已经成熟的代码列举如下,如有需求可私信联系,表明需要的 **问题与算法**,原创不宜,有偿获取。
VRP问题 | GA | ACO | ALNS | DE | DPSO | QDPSO | TS | SA |
---|---|---|---|---|---|---|---|---|
CVRP | √ | √ | √ | √ | √ | √ | √ | √ |
VRPTW | √ | √ | √ | √ | √ | √ | √ | √ |
MDVRP | √ | √ | √ | √ | √ | √ | √ | √ |
MDHVRP | √ | √ | √ | √ | √ | √ | √ | √ |
MDHVRPTW | √ | √ | √ | √ | √ | √ | √ | √ |
SDVRP | √ | √ | √ | √ | √ | √ | √ | √ |
SDVRPTW | √ | √ | √ |
1. 适用场景
- 求解SDVRPTW
- 车辆类型单一
- 车辆容量小于部分需求节点需求
- 单一车辆基地
- 带硬时间窗
2. 代码调整
2.1 需求拆分
与SDVRP问题相比,SDVRPTW问题不仅允许客户需求大于车辆载重,而且考虑了客户节点的时间窗约束。为了使得每个客户的需求得到满足,必须派遣一辆或多辆车辆在规定时间窗内对客户进行服务。对于需求节点的拆分,这里依然采取先验拆分策略,本文采用文献[1]提出的先验分割策略,表述如下:
(1)20/10/5/1拆分规则
- m20 =max{ m ∈ Z + ∪ { 0 } ∣ 0.20 Q m < = D i m\in Z^+ \cup \{0\} | 0.20Qm <= D_i m∈Z+∪{0}∣0.20Qm<=Di }
- m10 =max{ m ∈ Z + ∪ { 0 } ∣ 0.10 Q m < = D i − 0.20 Q m 20 m\in Z^+ \cup \{0\} | 0.10Qm <= D_i-0.20Qm_{20}~ m∈Z+∪{0}∣0.10Qm<=Di−0.20Qm20 }
- m5 =max{ m ∈ Z + ∪ { 0 } ∣ 0.05 Q m < = D i − 0.20 Q m 20 − 0.10 Q m 10 m\in Z^+ \cup \{0\} | 0.05Qm <= D_i-0.20Qm_{20}-0.10Qm_{10} m∈Z+∪{0}∣0.05Qm<=Di−0.20Qm20−0.10Qm10 }
- m1 =max{ m ∈ Z + ∪ { 0 } ∣ 0.01 Q m < = D i − 0.20 Q m 20 − 0.10 Q m 10 − 0.05 Q m 5 m\in Z^+ \cup \{0\} | 0.01Qm <= D_i-0.20Qm_{20}-0.10Qm_{10}-0.05Qm_{5} m∈Z+∪{0}∣0.01Qm<=Di−0.20Qm20−0.10Qm10−0.05Qm5 }
(2)25/10/5/1拆分规则
- m25 =max{ m ∈ Z + ∪ { 0 } ∣ 0.25 Q m < = D i m\in Z^+ \cup \{0\} | 0.25Qm <= D_i m∈Z+∪{0}∣0.25Qm<=Di }
- m10 =max{ m ∈ Z + ∪ { 0 } ∣ 0.10 Q m < = D i − 0.25 Q m 25 m\in Z^+ \cup \{0\} | 0.10Qm <= D_i-0.25Qm_{25}~ m∈Z+∪{0}∣0.10Qm<=Di−0.25Qm25 }
- m5 =max{ m ∈ Z + ∪ { 0 } ∣ 0.05 Q m < = D i − 0.25 Q m 25 − 0.10 Q m 10 m\in Z^+ \cup \{0\} | 0.05Qm <= D_i-0.25Qm_{25}-0.10Qm_{10} m∈Z+∪{0}∣0.05Qm<=Di−0.25Qm25−0.10Qm10 }
- m1 =max{ m ∈ Z + ∪ { 0 } ∣ 0.01 Q m < = D i − 0.25 Q m 25 − 0.10 Q m 10 − 0.05 Q m 5 m\in Z^+ \cup \{0\} | 0.01Qm <= D_i-0.25Qm_{25}-0.10Qm_{10}-0.05Qm_{5} m∈Z+∪{0}∣0.01Qm<=Di−0.25Qm25−0.10Qm10−0.05Qm5 }
在实现过程中,对于需求超过车辆容量的客户必须进行需求拆分,而对于未超过车辆容量的客户可以拆分也可以不拆分,这里设置了参数比例进行限制。
2.2 需求拆分后的服务时长取值问题
节点的服务时长会影响车辆的行进时间,进而会影响与节点时间窗的匹配问题。一般来说,节点的服务时长与需求量成正比关系,在进行节点需求拆分后,新节点的需求量降低,其服务时长理应也降低。但从标准数据集来看,各需求节点的服务时长均采用同一数值。因此本文在代码实现过程中也采用固定值,不考虑新节点服务时长的变化。当然,如有需要,也可以设置单位货物的服务时长,根据拆分后节点的具体需求量设置相应的服务时长。
3. 求解结果
(1)收敛曲线
(2)车辆路径
(3)输出内容
4. 代码片段
(1)数据结构
import math
import random
import numpy as np
import copy
import xlsxwriter
import matplotlib.pyplot as plt
import csv
import time
# 数据结构:解
class Sol():
def __init__(self):
self.obj=None # 目标函数值
self.node_no_seq=[] # 解的编码
self.route_list=[] # 解的解码
self.timetable_list=[] # 车辆访问各点的时间
self.route_distance_list = None
# 数据结构:需求节点
class Node():
def __init__(self):
self.id=0 # 节点id
self.x_coord=0 # 节点平面横坐标
self.y_coord=0 # 节点平面纵坐标
self.demand=0 # 节点需求
self.start_time=0 # 节点开始服务时间
self.end_time=1440 # 节点结束服务时间
self.service_time=0 # 单次服务时长
self.vehicle_speed = 0 # 行驶速度
# 数据结构:车场节点
class Depot():
def __init__(self):
self.id=0 # 节点id
self.x_coord=0 # 节点平面横坐标
self.y_coord=0 # 节点平面纵坐标
self.start_time=0 # 节点开始服务时间
self.end_time=1440 # 节点结束服务时间
self.v_speed = 0 # 行驶速度
self.v_cap = 80 # 车辆容量
# 数据结构:全局参数
class Model():
def __init__(self):
self.best_sol=None # 全局最优解
self.sol_list=[] # 解的集合
self.demand_dict = {} # 需求节点集合
self.depot = None # 车场节点集合
self.demand_id_list = [] # 需求节点id集合
self.distance_matrix = {} # 距离矩阵
self.time_matrix = {} # 时间矩阵
self.number_of_demands = 0 # 需求点数量
self.demand_id_list_ = [] # 经先验需求分割后的节点集合
self.demand_dict_ = {} # 需求分割后的节点需求集合
self.distance_matrix_ = {} # 原始节点id间的距离矩阵
self.time_matrix_ = {} # 原始节点id间的时间矩阵
self.mapping = {} # 需求分割前后的节点对应关系
self.split_rate = 0.5 # 控制需求分割的比例(需求超出车辆容量的除外)
self.rand_d_max = 0.4 # 随机破坏最大破坏比例
self.rand_d_min = 0.1 # 随机破坏最小破坏比例
self.worst_d_min = 5 # 最坏值破坏最少破坏数量
self.worst_d_max = 20 # 最坏值破坏最多破坏数量
self.regret_n = 5 # 后悔值破坏数量
self.r1 = 30 # 一等得分值
self.r2 = 18 # 二等得分值
self.r3 = 12 # 三等得分值
self.rho = 0.6 # 权重衰减比例
self.d_weight = np.ones(2) * 10 # 破坏算子权重
self.d_select = np.zeros(2) # 破坏算子选择次数
self.d_score = np.zeros(2) # 破坏算子得分
self.d_history_select = np.zeros(2) # 破坏算子累计选择次数
self.d_history_score = np.zeros(2) # 破坏算子累计得分
self.r_weight = np.ones(3) * 10 # 修复算子权重
self.r_select = np.zeros(3) # 修复算子选择次数
self.r_score = np.zeros(3) # 修复算子得分
self.r_history_select = np.zeros(3) # 修复算子累计选择次数
self.r_history_score = np.zeros(3) # 修复算子累计得分
(2)距离矩阵
# 读取csv文件
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] = node
model.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:
depot = Depot()
depot.id = row['id']
depot.x_coord = float(row['x_coord'])
depot.y_coord = float(row['y_coord'])
depot.start_time=float(row['start_time'])
depot.end_time=float(row['end_time'])
depot.v_speed = float(row['v_speed'])
depot.v_cap = float(row['v_cap'])
model.depot = depot
# 初始化参数:计算距离矩阵时间矩阵
def calDistanceTimeMatrix(model):
for i in range(len(model.demand_id_list)):
from_node_id = model.demand_id_list[i]
for j in range(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] = dist
model.time_matrix[from_node_id,to_node_id] = math.ceil(dist/model.depot.v_speed)
dist = math.sqrt((model.demand_dict[from_node_id].x_coord - model.depot.x_coord) ** 2 +
(model.demand_dict[from_node_id].y_coord - model.depot.y_coord) ** 2)
model.distance_matrix[from_node_id, model.depot.id] = dist
model.distance_matrix[model.depot.id, from_node_id] = dist
model.time_matrix[from_node_id,model.depot.id] = math.ceil(dist/model.depot.v_speed)
model.time_matrix[model.depot.id,from_node_id] = math.ceil(dist/model.depot.v_speed)
(3)邻域搜索
# 随机破坏
def createRandomDestory(model):
d=random.uniform(model.rand_d_min,model.rand_d_max)
return random.sample(model.demand_id_list_,int(d*(len(model.demand_id_list_)-1)))
# 随机修复
def createRandomRepair(remove_list,model,sol):
unassigned_node_no_seq = remove_list
assigned_node_no_seq = [node_no for node_no in sol.node_no_seq if node_no not in remove_list]
# insert
for node_no in unassigned_node_no_seq:
index=random.randint(0,len(assigned_node_no_seq)-1)
assigned_node_no_seq.insert(index,node_no)
new_sol=Sol()
new_sol.node_no_seq=copy.deepcopy(assigned_node_no_seq)
new_sol.timetable_list, new_sol.obj, new_sol.route_distance,new_sol.route_list=calObj(assigned_node_no_seq,model)
return new_sol
参考
【1】 A novel approach to solve the split delivery vehicle routing problem