基于python语言,采用经典遗传算法(GA)对 需求拆分车辆路径规划问题(SDVRP) 进行求解。
目录
- 往期优质资源
- 1. 适用场景
- 2. 代码调整
- 3. 求解结果
- 4. 代码片段
- 参考
往期优质资源
经过一年多的创作,目前已经成熟的代码列举如下,如有需求可私信联系,表明需要的 **问题与算法**,原创不宜,有偿获取。
VRP问题 | GA | ACO | ALNS | DE | DPSO | QDPSO | TS | SA |
---|---|---|---|---|---|---|---|---|
CVRP | √ | √ | √ | √ | √ | √ | √ | √ |
VRPTW | √ | √ | √ | √ | √ | √ | √ | √ |
MDVRP | √ | √ | √ | √ | √ | √ | √ | √ |
MDHVRP | √ | √ | √ | √ | √ | √ | √ | √ |
MDHVRPTW | √ | √ | √ | √ | √ | √ | √ | √ |
SDVRP | √ |
1. 适用场景
- 求解CVRP
- 车辆类型单一
- 车辆容量小于部分需求节点需求
- 单一车辆基地
2. 代码调整
与CVRP问题相比,SDVRP问题允许客户需求大于车辆容量。为了使得每个客户的需求得到满足,必须派遣一辆或多辆车辆对客户进行服务,也就是需要对客户的需求进行拆分。关于如何进行拆分一般有两种方式:
- 先验拆分策略:提前制定策略对客户的需求(尤其是大于车辆容量的客户需求)进行分解,将SDVRP问题转化为CVRP问题
- 过程拆分策略:在车辆服务过程中对客户需求进行动态拆分
本文采用文献[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 }
在实现过程中,对于需求超过车辆容量的客户必须进行需求拆分,而对于未超过车辆容量的客户可以拆分也可以不拆分,这里设置了参数比例进行限制。
3. 求解结果
(1)收敛曲线
(2)车辆路径
4. 代码片段
(1)数据结构
# 数据结构:解
class Sol():def __init__(self):self.node_no_seq = None # 节点id有序排列self.obj = None # 目标函数self.fitness = None # 适应度self.route_list = None # 车辆路径集合self.route_distance_list = None # 车辆路径长度集合
# 数据结构:网络节点
class Node():def __init__(self):self.id = 0 # 节点idself.x_coord = 0 # 节点平面横坐标self.y_coord = 0 # 节点平面纵坐标self.demand = 0 # 节点需求
# 数据结构:全局参数
class Model():def __init__(self):self.best_sol = None # 全局最优解self.sol_list = [] # 解的集合self.depot = None # 车场节点self.number_of_demands = 0 # 需求节点数量self.demand_dict = {} # 原始节点需求集合self.demand_id_list = [] # 原始节点id集合self.distance_matrix = {} # 原始节点id间的距离矩阵self.demand_id_list_ = [] # 经先验需求分割后的节点集合self.demand_dict_ = {} # 需求分割后的节点需求集合self.distance_matrix_ = {} # 原始节点id间的距离矩阵self.mapping = {} # 需求分割前后的节点对应关系self.vehicle_cap = 80 # 车辆最大容量self.pc = 0.5 # 交叉概率self.pm = 0.2 # 变异概率self.n_select = 80 # 种群选择数量self.popsize = 100 # 种群规模self.split_rate = 0.5 # 控制需求分割的比例(需求超出车辆容量的除外)
(2)距离矩阵
# 初始化参数
def cal_distance_matrix(model):for i in model.demand_id_list:for j in model.demand_id_list:d=math.sqrt((model.demand_dict[i].x_coord-model.demand_dict[j].x_coord)**2+(model.demand_dict[i].y_coord-model.demand_dict[j].y_coord)**2)model.distance_matrix[i,j]=ddist = math.sqrt((model.demand_dict[i].x_coord - model.depot.x_coord) ** 2 + (model.demand_dict[i].y_coord - model.depot.y_coord) ** 2)model.distance_matrix[i, model.depot.id] = distmodel.distance_matrix[model.depot.id, i] = dist
(3)邻域搜索
# 二元锦标赛
def select_sol(model):sol_list=copy.deepcopy(model.sol_list)model.sol_list=[]for _ 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].fitnessf2_fit=sol_list[f2_index].fitnessif f1_fit<f2_fit:model.sol_list.append(sol_list[f2_index])else:model.sol_list.append(sol_list[f1_index])
# OX交叉
def cross_sol(model):sol_list=copy.deepcopy(model.sol_list)model.sol_list=[]while True:[f1_index,f2_index] = random.sample(range(len(sol_list)),2)f1 = copy.deepcopy(sol_list[f1_index])f2 = copy.deepcopy(sol_list[f2_index])if random.random() <= model.pc:cro1_index = random.randint(0,model.number_of_demands-1)cro2_index = random.randint(cro1_index,model.number_of_demands-1)new_c1_f = []new_c1_m=f1.node_no_seq[cro1_index:cro2_index+1]new_c1_b = []new_c2_f = []new_c2_m=f2.node_no_seq[cro1_index:cro2_index+1]new_c2_b = []for index in range(model.number_of_demands):if len(new_c1_f)<cro1_index:if f2.node_no_seq[index] not in new_c1_m:new_c1_f.append(f2.node_no_seq[index])else:if f2.node_no_seq[index] not in new_c1_m:new_c1_b.append(f2.node_no_seq[index])for index in range(model.number_of_demands):if len(new_c2_f)<cro1_index:if f1.node_no_seq[index] not in new_c2_m:new_c2_f.append(f1.node_no_seq[index])else:if f1.node_no_seq[index] not in new_c2_m:new_c2_b.append(f1.node_no_seq[index])new_c1=copy.deepcopy(new_c1_f)new_c1.extend(new_c1_m)new_c1.extend(new_c1_b)f1.node_no_seq=new_c1new_c2=copy.deepcopy(new_c2_f)new_c2.extend(new_c2_m)new_c2.extend(new_c2_b)f2.node_no_seq=new_c2model.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
# 变异
def mu_sol(model):sol_list=copy.deepcopy(model.sol_list)model.sol_list=[]while True:f1_index = random.randint(0, len(sol_list) - 1)f1 = copy.deepcopy(sol_list[f1_index])m1_index=random.randint(0,model.number_of_demands-1)m2_index=random.randint(0,model.number_of_demands-1)if m1_index!=m2_index:if random.random() <= model.pm:node1=f1.node_no_seq[m1_index]f1.node_no_seq[m1_index]=f1.node_no_seq[m2_index]f1.node_no_seq[m2_index]=node1model.sol_list.append(copy.deepcopy(f1))if len(model.sol_list)>model.popsize:break
参考
【1】 A novel approach to solve the split delivery vehicle routing problem