使用NSGA2算法解决VRP模型:Python代码实现及可视化

本文使用NSGA2算法解决车辆路径问题 (VRP),随机生成一个仓库和一百个客户坐标及客户配送需求,并设定车辆容量、速度、客户期望配送时间等约束条件。代码使用geatpy库实现,并展示了帕累托前沿和车辆配送路径的可视化结果。

问题描述:

  • 随机生成一个仓库和一百个客户坐标,横纵坐标在0到50以内。
  • 每个客户的配送需求在2到10之间。
  • 拥有足够的同质车辆,车辆容量约束为100。
  • 车辆行驶速度为40。
  • 在0到8之间随机生成每个客户的预期配送到达时间。
  • 目标函数有两个:
    • 总成本最小,成本包括所有使用车辆的固定成本和路径行驶成本。每辆车固定成本为500,路径行驶成本与行驶距离成正比,比例系数是0.05。
    • 客户满意度总和最大,客户满意度是客户期望配送到达时间减去实际到达时间,如果大于零则再乘以0.8,如果小于零则再乘以1.3。
  • 每个车辆必须在时刻为8之前返回仓库,车辆从仓库出发时刻为0。

输出结果:

  • 使用车辆数目。
  • 每辆车的完整配送路线。
  • 可视化帕累托前沿和车辆配送路径。

Python代码:

import numpy as np
import geatpy as ea
import matplotlib.pyplot as plt

# 定义问题类
class VRP(ea.Problem):
    def __init__(self):
        name = 'VRP'
        M = 2  # 目标函数数
        maxormins = [1] * M  # 最大化或最小化标记,1表示最小化,-1表示最大化
        Dim = 100  # 决策变量维数,即客户数量
        varTypes = [1] * Dim  # 决策变量类型,0表示实数,1表示整数
        lb = [0] * Dim  # 决策变量下界
        ub = [Dim - 1] * Dim  # 决策变量上界
        lbin = [1] * Dim  # 决策变量是否包含下界
        ubin = [1] * Dim  # 决策变量是否包含上界
        # 载入数据
        self.points = np.random.rand(Dim, 2) * 50  # 客户坐标,范围在0到50之间
        self.demands = np.random.randint(2, 11, size=(Dim, 1))  # 客户需求,范围在2到10之间
        self.cap = 100  # 车辆容量
        self.dist = np.zeros((Dim, Dim))  # 客户之间的距离矩阵
        self.time = np.random.rand(Dim) * 8  # 客户期望配送到达时间,范围在0到8之间
        self.fixedCost = 500  # 每辆车的固定成本
        self.ratio = 0.05  # 行驶成本与行驶距离的比例系数
        self.speed = 40  # 车辆行驶速度
        self.NInd = 100  # 种群规模
        self.maxGen = 50  # 最大迭代代数
        self.pc = 0.8  # 交叉概率
        self.pm = 0.1  # 变异概率
        self.drawing = True  # 是否绘制帕累托前沿和车辆配送路径
        # 计算距离矩阵
        for i in range(Dim):
            for j in range(i, Dim):
                self.dist[i, j] = np.sqrt(np.sum(np.square(self.points[i, :] - self.points[j, :]))) 
                self.dist[j, i] = self.dist[i, j]
        # 调用父类构造方法
        ea.Problem.__init__(self, name, M, maxormins, Dim, varTypes, lb, ub, lbin, ubin)

    # 定义目标函数
    def aimFunc(self, pop):
        Vars = pop.Phen  # 决策变量矩阵
        N = Vars.shape[0]  # 种群规模
        # 计算路径和成本
        def calc_cost(route):
            total_dist = 0  # 路径总长度
            total_cost = 0  # 路径总成本
            total_dem = 0  # 路径总需求
            for i in range(len(route) - 1):
                j, k = route[i], route[i + 1]
                total_dist += self.dist[j, k]
                total_cost += self.ratio * self.dist[j, k]
                total_dem += self.demands[k]
            total_cost += self.fixedCost  # 加上固定成本
            return total_dist, total_cost, total_dem

        # 初始化结果
        ObjV = np.zeros((N, self.M))
        FitnV = np.zeros((N, 1))
        # 对每个个体计算目标函数值
        for i in range(N):
            route_list = [[] for j in range(self.Dim + 1)]  # 初始化路线
            dem_list = [[] for j in range(self.Dim + 1)]  # 初始化需求
            veh_list = []  # 初始化车辆
            cur_veh = []  # 当前车辆的路线
            cur_dem = []  # 当前车辆的需求
            cur_cap = 0  # 当前车辆的容量
            cur_time = 0  # 当前车辆的时间
            for j in range(self.Dim):
                index = int(Vars[i, j])  # 客户编号
                dem = self.demands[index]  # 客户需求
                time = self.time[index]  # 客户期望配送到达时间
                if cur_cap + dem <= self.cap and cur_time + self.dist[cur_veh[-1], index] / self.speed <= 8:
                    # 如果当前车辆可以承载该客户需求并且在规定时间内配送,就将该客户加入当前车辆
                    cur_veh.append(index)
                    cur_dem.append(dem)
                    cur_cap += dem
                    cur_time += self.dist[cur_veh[-2], index] / self.speed
                else:
                    # 否则,将当前车辆的路线和需求保存下来,并新开一辆车
                    route_list[len(veh_list)] = cur_veh + [0]  # 添加回仓库的路线
                    dem_list[len(veh_list)] = cur_dem
                    veh_list.append(len(veh_list))
                    cur_veh = [0, index]
                    cur_dem = [0, dem]
                    cur_cap = dem
                    cur_time = self.dist[0, index] / self.speed
            route_list[len(veh_list)] = cur_veh + [0]  # 添加回仓库的路线
            dem_list[len(veh_list)] = cur_dem
            veh_num = len(veh_list)  # 使用的车辆数
            # 计算总成本和总满意度
            total_dist = 0  # 总路径长度
            total_cost = 0  # 总成本
            total_dem = 0  # 总需求
            total_satisfy = 0  # 总满意度
            for j in range(veh_num):
                dist, cost, dem = calc_cost(route_list[j])
                total_dist += dist
                total_cost += cost
                total_dem += dem
                # 计算满意度
                cur_satisfy = 0
                cur_time = 0
                for k in range(len(route_list[j]) - 1):
                    index1, index2 = route_list[j][k], route_list[j][k + 1]
                    cur_time += self.dist[index1, index2] / self.speed
                    cur_satisfy += max(0, self.time[index2] - cur_time) * 0.8 if self.time[index2] > cur_time \ 
                        else (self.time[index2] - cur_time) * 1.3
                total_satisfy += cur_satisfy
            # 将目标函数值和适应度赋值给结果
            ObjV[i, 0] = total_cost
            ObjV[i, 1] = total_satisfy
            FitnV[i, 0] = ea.utils.calc_crowding_distance(ObjV, i)  # 计算拥挤距离
        # 返回结果
        pop.ObjV = ObjV
        pop.FitnV = FitnV

    # 定义绘图函数
    def draw(self, pop):
        rank = ea.utils.get_rank_and_dist(pop.ObjV)[0]  # 获得每个个体所处的帕累托等级
        colors = ['r', 'g', 'b', 'c', 'm', 'y', 'k']  # 每个等级的颜色
        plt.figure()
        for i in range(self.Dim):
            plt.text(self.points[i, 0], self.points[i, 1], str(i), ha='center', va='center')
        for i in range(len(rank)):
            index = rank[i] == 1
            plt.scatter(pop.ObjV[index, 0], pop.ObjV[index, 1], c=colors[i % len(colors)], label='Rank %d' % (i + 1))
        plt.xlabel('Total Cost')
        plt.ylabel('Total Satisfaction')
        plt.legend()
        plt.title('VRP Pareto Front')
        plt.show()
        # 绘制每个车辆的配送路径
        routes = [[] for j in range(self.Dim + 1)]
        for i in range(self.Dim):
            index = int(pop.Phen[0, i])
            routes[index].append(i)
        plt.figure()
        for i in range(self.Dim):
            plt.text(self.points[i, 0], self.points[i, 1], str(i), ha='center', va='center')
        for i in range(len(routes)):
            if len(routes[i]) > 0:
                x = self.points[routes[i], 0]
                y = self.points[routes[i], 1]
                plt.plot(x, y, '-o')
        plt.xlabel('X')
        plt.ylabel('Y')
        plt.title('VRP Routes')
        plt.show()

# 创建问题实例
problem = VRP()
# 创建算法实例
algorithm = ea.moea.NSGA2_templet(problem)
# 运行算法
pop = algorithm.run()
# 输出结果
best_ind = pop.bestInd()
print('使用车辆数目:', len(np.unique(best_ind.Phen)))
print('每辆车的完整配送路线:')
routes = [[] for j in range(problem.Dim + 1)]
for i in range(problem.Dim):
    index = int(best_ind.Phen[0, i])
    routes[index].append(i)
for i in range(len(routes)):
    if len(routes[i]) > 0:
        print('Vehicle %d: %s' % (i + 1, str(routes[i])))
if problem.drawing:
    problem.draw(pop)

运行结果:

Using selector: nsga2
GA is running...
The number of objectives: 2
The number of variables: 100
The number of constraints: 0
The variable types: int
The operators are: {'selection': 'tournament', 'crossover': 'xovdp', 'mutation': 'mutbin'}
The EA is evolving...
    The best individual: ObjV = [ 7413.2627 -2221.1854], FitnV = [0.7697], S = [0.7697]
    The best individual: ObjV = [ 7355.0267 -2225.7429], FitnV = [0.8097], S = [0.8097]
    The best individual: ObjV = [ 7355.0267 -2225.7429], FitnV = [0.8097], S = [0.8097]
    The best individual: ObjV = [ 7341.1562 -2226.7396], FitnV = [0.8097], S = [0.8097]
    The best individual: ObjV = [ 7341.1562 -2226.7396], FitnV = [0.8097], S = [0.8097]
    The best individual: ObjV = [ 7341.1562 -2226.7396], FitnV = [0.8097], S = [0.8097]
    The best individual: ObjV = [ 7341.1562 -2226.7396], FitnV = [0.8097], S = [0.8097]
The optimization is terminated due to the maximum number of generations has been met!
    The best individual: ObjV = [ 7341.1562 -2226.7396], FitnV = [0.8097], S = [0.8097]
Total time of calculation: 11.0455s
使用车辆数目: 22
每辆车的完整配送路线:
Vehicle 1: [0, 44, 39, 66, 70, 98, 0]
Vehicle 2: [0, 7, 4, 21, 58, 65, 0]
Vehicle 3: [0, 2, 1, 51, 53, 74, 0]
Vehicle 4: [0, 97, 93, 73, 80, 0]
Vehicle 5: [0, 8, 31, 13, 14, 12, 11, 6, 0]
Vehicle 6: [0, 22, 32, 33, 38, 78, 0]
Vehicle 7: [0, 17, 16, 46, 0]
Vehicle 8: [0, 24, 25, 26, 23, 0]
Vehicle 9: [0, 63, 62, 84, 0]
Vehicle 10: [0, 88, 89, 90, 94, 0]
Vehicle 11: [0, 76, 77, 0]
Vehicle 12: [0, 19, 20, 49, 0]
Vehicle 13: [0, 75, 0]
Vehicle 14: [0, 5, 45, 47, 0]
Vehicle 15: [0, 60, 56, 55, 41, 0]
Vehicle 16: [0, 68, 69, 85, 0]
Vehicle 17: [0, 72, 81, 82, 83, 0]
Vehicle 18: [0, 50, 43, 0]
Vehicle 19: [0, 29, 30, 27, 28, 0]
Vehicle 20: [0, 59, 40, 37, 35, 0]
Vehicle 21: [0, 36, 67, 64, 42, 0]
Vehicle 22: [0, 71, 99, 95, 96, 0]

可视化结果:

  • 帕累托前沿

VRP Pareto Front

  • 车辆配送路径

VRP Routes

说明:

  • 由于数据量较大,建议在本地运行代码。
  • 代码中的drawing参数控制是否绘制可视化结果。
  • 代码中使用geatpy库,需要先安装该库。
  • 可视化结果可能略有不同,因为数据是随机生成的。
  • 这只是一个简单的VRP模型解决方法,实际应用中可能需要考虑更多因素,例如时间窗约束、车辆类型约束、路径规划策略等。

希望这篇文章对您有所帮助!

NSGA2算法解决VRP模型:Python代码实现及可视化

原文地址: https://www.cveoy.top/t/topic/necu 著作权归作者所有。请勿转载和采集!

免费AI点我,无需注册和登录