求解VRPTW的自适应大规模领域搜索算法

最近在做一个基于物流配送的路径规划的问题,才了解到这个算法,也算是在做一个算法的入门吧。

智能配送计划本质是车辆路径规划(VRP)问题,是运筹中的经典问题。

  • 是指一定数量的客户,各自有不同数量需求,配送中心向客户提供货物,由一个车队负责分送货物,组织适当的行车路线
  • 目标:路程最短最小、成本最小、耗费时间最少,使用的车辆数
  • 约束:车辆载重、车辆满载限制、时间窗口、车辆服务客户数、车辆行驶的距离、最大可用车辆数等
  • 决策变量:车辆行驶路径 

算法主要设计两个大类

求解VRPTW的自适应大规模领域搜索算法_第1张图片

 

 设计算法的大概思路

  1.  数据预处理(包括对数据的提取、地址的经纬度转化和各个位置之间的距离矩阵、物品重量、体积的提取标准),梳理问题的数学特征。
  2. 构造初始解
  3. 构造破坏和修复函数
  4. 分别将订单加入到对应的车辆,并不断更新惩罚系数
  5. 达到迭代次数后,终止循环,输出对应的解

算法实现的效果:

求解VRPTW的自适应大规模领域搜索算法_第2张图片 

 算法具体实现代码:

# -*- 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()



你可能感兴趣的:(算法设计与分析,算法,自动驾驶,机器学习)