【进阶六】Python实现SDVRPTW常见求解算法——遗传算法(GA)

基于python语言,采用经典蚁群算法(ACO)对 带硬时间窗的需求拆分车辆路径规划问题(SDVRPTW) 进行求解。

目录

  • 往期优质资源
  • [1. 适用场景](#1. 适用场景)
    • [2.1 需求拆分](#2.1 需求拆分)
    • [2.2 需求拆分后的服务时长取值问题](#2.2 需求拆分后的服务时长取值问题)
  • [3. 求解结果](#3. 求解结果)
  • [4. 代码片段](#4. 代码片段)

往期优质资源

经过一年多的创作,目前已经成熟的代码列举如下,如有需求可私信联系,表明需要的 **问题与算法**,原创不宜,有偿获取。

VRP问题 GA ACO ALNS DE DPSO QDPSO TS SA
CVRP
VRPTW
MDVRP
MDHVRP
MDHVRPTW
SDVRP
SDVRPTW

1. 适用场景

  • 求解SDVRPTW
  • 车辆类型单一
  • 车辆容量小于部分需求节点需求
  • 单一车辆基地
  • 带硬时间窗

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)数据结构

python 复制代码
# 数据结构:解
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.popsize = 100 # 种群规模
        self.alpha = 2 # 信息启发式因子
        self.beta = 3 # 期望启发式因子
        self.Q = 100 # 信息素总量
        self.rho = 0.5 # 信息素挥发因子
        self.tau = {} # 弧信息素集合
        self.tau0 = 100 # 路径初始信息素

(2)距离矩阵

python 复制代码
# 初始化参数:计算距离矩阵时间矩阵
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)邻域搜索

python 复制代码
# 蚂蚁移动
def movePosition(model):
    sol_list=[]
    local_sol=Sol()
    local_sol.obj=float('inf')
    for _ in range(model.popsize):
        #随机初始化蚂蚁为止
        node_no_seq=[random.randint(0,len(model.demand_id_list_)-1)]
        all_node_no_seq=copy.deepcopy(model.demand_id_list_)
        all_node_no_seq.remove(node_no_seq[-1])
        #确定下一个访问节点
        while len(all_node_no_seq)>0:
            next_node_no=searchNextNode(model,node_no_seq[-1],all_node_no_seq)
            node_no_seq.append(next_node_no)
            all_node_no_seq.remove(next_node_no)
        sol=Sol()
        sol.node_no_seq=node_no_seq
        sol.timetable_list,sol.obj, sol.route_distance,sol.route_list = calObj(node_no_seq,model)
        sol_list.append(sol)
        if sol.obj < local_sol.obj:
            local_sol = copy.deepcopy(sol)
    model.sol_list=copy.deepcopy(sol_list)
    if local_sol.obj<model.best_sol.obj:
        model.best_sol=copy.deepcopy(local_sol)
# 搜索下一移动节点
def searchNextNode(model,current_node_no,SE_List):
    prob=np.zeros(len(SE_List))
    for i,node_no in enumerate(SE_List):
        eta=1/model.distance_matrix_[current_node_no,node_no] if model.distance_matrix_[current_node_no,node_no] else 0.0001
        tau=model.tau[current_node_no,node_no]
        prob[i]=((eta**model.alpha)*(tau**model.beta))
    #采用轮盘法选择下一个访问节点
    cumsumprob=(prob/sum(prob)).cumsum()
    cumsumprob -= np.random.rand()
    return SE_List[list(cumsumprob >= 0).index(True)]

参考

【1】 A novel approach to solve the split delivery vehicle routing problem

相关推荐
FL16238631297 分钟前
基于yolov11的打电话玩手机检测系统python源码+pytorch模型+评估指标曲线+精美GUI界面
python·yolo·智能手机
动能小子ohhh16 分钟前
Python中的客户端和服务端交互的基本内容
开发语言·python
朝九晚五ฺ26 分钟前
【算法学习】递归、搜索与回溯算法(一)
数据结构·学习·算法·深度优先
hongjianMa29 分钟前
【论文阅读】Joint Deep Modeling of Users and Items Using Reviews for Recommendation
论文阅读·python·深度学习·卷积神经网络·推荐系统·推荐算法·多模态
张书名37 分钟前
Leetcode刷题记录29——矩阵置零
算法·leetcode·矩阵
描绘一抹色1 小时前
力扣hot100 (除自身以外数组的乘积)
java·算法·leetcode
自由的风.1 小时前
贪心算法解决会议安排问题
算法·贪心算法·c#
Best_Me071 小时前
FiLo++的框架图介绍
人工智能·算法·机器学习·职场和发展·蓝桥杯
序属秋秋秋1 小时前
《数据结构初阶》【顺序栈 + 链式队列 + 循环队列】
c语言·数据结构·c++·笔记·算法
明月看潮生1 小时前
青少年编程与数学 02-018 C++数据结构与算法 26课题、数据压缩算法
c++·算法·青少年编程·编程与数学