diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..8eeb0e2 Binary files /dev/null and b/.DS_Store differ diff --git "a/\346\262\210\351\235\231\350\277\234-2023202210156-\351\253\230\347\272\247\347\256\227\346\263\225/.DS_Store" "b/\346\262\210\351\235\231\350\277\234-2023202210156-\351\253\230\347\272\247\347\256\227\346\263\225/.DS_Store" new file mode 100644 index 0000000..72328e7 Binary files /dev/null and "b/\346\262\210\351\235\231\350\277\234-2023202210156-\351\253\230\347\272\247\347\256\227\346\263\225/.DS_Store" differ diff --git "a/\346\262\210\351\235\231\350\277\234-2023202210156-\351\253\230\347\272\247\347\256\227\346\263\225/VRPTW\357\274\210\346\272\220\347\240\201\357\274\211/.DS_Store" "b/\346\262\210\351\235\231\350\277\234-2023202210156-\351\253\230\347\272\247\347\256\227\346\263\225/VRPTW\357\274\210\346\272\220\347\240\201\357\274\211/.DS_Store" new file mode 100644 index 0000000..48f42b5 Binary files /dev/null and "b/\346\262\210\351\235\231\350\277\234-2023202210156-\351\253\230\347\272\247\347\256\227\346\263\225/VRPTW\357\274\210\346\272\220\347\240\201\357\274\211/.DS_Store" differ diff --git "a/\346\262\210\351\235\231\350\277\234-2023202210156-\351\253\230\347\272\247\347\256\227\346\263\225/VRPTW\357\274\210\346\272\220\347\240\201\357\274\211/.idea/.gitignore" "b/\346\262\210\351\235\231\350\277\234-2023202210156-\351\253\230\347\272\247\347\256\227\346\263\225/VRPTW\357\274\210\346\272\220\347\240\201\357\274\211/.idea/.gitignore" new file mode 100644 index 0000000..35410ca --- /dev/null +++ "b/\346\262\210\351\235\231\350\277\234-2023202210156-\351\253\230\347\272\247\347\256\227\346\263\225/VRPTW\357\274\210\346\272\220\347\240\201\357\274\211/.idea/.gitignore" @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git "a/\346\262\210\351\235\231\350\277\234-2023202210156-\351\253\230\347\272\247\347\256\227\346\263\225/VRPTW\357\274\210\346\272\220\347\240\201\357\274\211/.idea/.name" "b/\346\262\210\351\235\231\350\277\234-2023202210156-\351\253\230\347\272\247\347\256\227\346\263\225/VRPTW\357\274\210\346\272\220\347\240\201\357\274\211/.idea/.name" new file mode 100644 index 0000000..a645928 --- /dev/null +++ "b/\346\262\210\351\235\231\350\277\234-2023202210156-\351\253\230\347\272\247\347\256\227\346\263\225/VRPTW\357\274\210\346\272\220\347\240\201\357\274\211/.idea/.name" @@ -0,0 +1 @@ +GA.py \ No newline at end of file diff --git "a/\346\262\210\351\235\231\350\277\234-2023202210156-\351\253\230\347\272\247\347\256\227\346\263\225/VRPTW\357\274\210\346\272\220\347\240\201\357\274\211/.idea/VRPTW.iml" "b/\346\262\210\351\235\231\350\277\234-2023202210156-\351\253\230\347\272\247\347\256\227\346\263\225/VRPTW\357\274\210\346\272\220\347\240\201\357\274\211/.idea/VRPTW.iml" new file mode 100644 index 0000000..77aa2d9 --- /dev/null +++ "b/\346\262\210\351\235\231\350\277\234-2023202210156-\351\253\230\347\272\247\347\256\227\346\263\225/VRPTW\357\274\210\346\272\220\347\240\201\357\274\211/.idea/VRPTW.iml" @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git "a/\346\262\210\351\235\231\350\277\234-2023202210156-\351\253\230\347\272\247\347\256\227\346\263\225/VRPTW\357\274\210\346\272\220\347\240\201\357\274\211/.idea/inspectionProfiles/profiles_settings.xml" "b/\346\262\210\351\235\231\350\277\234-2023202210156-\351\253\230\347\272\247\347\256\227\346\263\225/VRPTW\357\274\210\346\272\220\347\240\201\357\274\211/.idea/inspectionProfiles/profiles_settings.xml" new file mode 100644 index 0000000..105ce2d --- /dev/null +++ "b/\346\262\210\351\235\231\350\277\234-2023202210156-\351\253\230\347\272\247\347\256\227\346\263\225/VRPTW\357\274\210\346\272\220\347\240\201\357\274\211/.idea/inspectionProfiles/profiles_settings.xml" @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git "a/\346\262\210\351\235\231\350\277\234-2023202210156-\351\253\230\347\272\247\347\256\227\346\263\225/VRPTW\357\274\210\346\272\220\347\240\201\357\274\211/.idea/misc.xml" "b/\346\262\210\351\235\231\350\277\234-2023202210156-\351\253\230\347\272\247\347\256\227\346\263\225/VRPTW\357\274\210\346\272\220\347\240\201\357\274\211/.idea/misc.xml" new file mode 100644 index 0000000..f08d37a --- /dev/null +++ "b/\346\262\210\351\235\231\350\277\234-2023202210156-\351\253\230\347\272\247\347\256\227\346\263\225/VRPTW\357\274\210\346\272\220\347\240\201\357\274\211/.idea/misc.xml" @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git "a/\346\262\210\351\235\231\350\277\234-2023202210156-\351\253\230\347\272\247\347\256\227\346\263\225/VRPTW\357\274\210\346\272\220\347\240\201\357\274\211/.idea/modules.xml" "b/\346\262\210\351\235\231\350\277\234-2023202210156-\351\253\230\347\272\247\347\256\227\346\263\225/VRPTW\357\274\210\346\272\220\347\240\201\357\274\211/.idea/modules.xml" new file mode 100644 index 0000000..d123705 --- /dev/null +++ "b/\346\262\210\351\235\231\350\277\234-2023202210156-\351\253\230\347\272\247\347\256\227\346\263\225/VRPTW\357\274\210\346\272\220\347\240\201\357\274\211/.idea/modules.xml" @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git "a/\346\262\210\351\235\231\350\277\234-2023202210156-\351\253\230\347\272\247\347\256\227\346\263\225/VRPTW\357\274\210\346\272\220\347\240\201\357\274\211/.idea/other.xml" "b/\346\262\210\351\235\231\350\277\234-2023202210156-\351\253\230\347\272\247\347\256\227\346\263\225/VRPTW\357\274\210\346\272\220\347\240\201\357\274\211/.idea/other.xml" new file mode 100644 index 0000000..2e75c2e --- /dev/null +++ "b/\346\262\210\351\235\231\350\277\234-2023202210156-\351\253\230\347\272\247\347\256\227\346\263\225/VRPTW\357\274\210\346\272\220\347\240\201\357\274\211/.idea/other.xml" @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git "a/\346\262\210\351\235\231\350\277\234-2023202210156-\351\253\230\347\272\247\347\256\227\346\263\225/VRPTW\357\274\210\346\272\220\347\240\201\357\274\211/GA.py" "b/\346\262\210\351\235\231\350\277\234-2023202210156-\351\253\230\347\272\247\347\256\227\346\263\225/VRPTW\357\274\210\346\272\220\347\240\201\357\274\211/GA.py" new file mode 100644 index 0000000..b0988f6 --- /dev/null +++ "b/\346\262\210\351\235\231\350\277\234-2023202210156-\351\253\230\347\272\247\347\256\227\346\263\225/VRPTW\357\274\210\346\272\220\347\240\201\357\274\211/GA.py" @@ -0,0 +1,180 @@ +import random +import matplotlib.pyplot as plt +import numpy as np +from collections import defaultdict, deque +from deap import base, creator, tools, algorithms + +# 假设设置 +centersNum = 1 # 配送中心数量 +customerNum = 5 # 卸货点数量 +ordersMax = 5 # 每个时间段最大订单数 +timeInterval = 60 # 时间间隔(分钟) +deliveryMax = 20 # 无人机飞行距离限制(公里) +loadMax = 3 # 无人机最大负载(物品数) +speed = 60 # 无人机速度(公里/小时) + +# 遗传算法设置 +POP_SIZE = 50 # 种群大小 +GEN = 30 # 迭代代数 +CX_PB = 0.7 # 交叉概率 +MUT_PB = 0.2 # 变异概率 + + +# 生成随机位置 +def generatePosition(count, size1, size2): + return [(random.randint(size1, size2), random.randint(size1, size2)) for _ in range(count)] + +# 位置距离计算 +def distance(pos1, pos2): + return ((pos1[0] - pos2[0]) ** 2 + (pos1[1] - pos2[1]) ** 2) ** 0.5 + +# 生成的位置 +centers = generatePosition(centersNum, 3, 7) +customers = generatePosition(customerNum, 0, 10) + + +# 订单类 +class Order: + def __init__(self, priority, customerId, generateTime): + self.priority = priority # 1: 一般, 2: 较紧急, 3: 紧急 + self.customerId = customerId + self.generateTime = generateTime + self.deadline = self.setDeadline(generateTime, priority) + self.processed = False + + def setDeadline(self, generateTime, priority): + if priority == 1: + return generateTime + 180 # 一般,3小时内送达 + elif priority == 2: + return generateTime + 90 # 较紧急,1.5小时内送达 + else: + return generateTime + 30 # 紧急,0.5小时内送达 + +# 生成订单 +def generateOrders(time): + orderNum = random.randint(0, ordersMax) + return [Order(random.randint(1, 3), random.randint(0, customerNum - 1), time) for _ + in range(orderNum)] + +def createRoute(individual, center, customers): + # 根据遗传算法个体生成路径 + route = [center] + [customers[i] for i in individual] + [center] + return route + +def evalRoute(individual, center, customers): + # 计算路径的总距离 + route = createRoute(individual, center, customers) + total_distance = sum(distance(route[i], route[i + 1]) for i in range(len(route) - 1)) + return (total_distance,) + +def setupGA(center, customerIds, customers): + creator.create("FitnessMin", base.Fitness, weights=(-1.0,)) + creator.create("Individual", list, fitness=creator.FitnessMin) + + toolbox = base.Toolbox() + # 确保随机抽样的元素数量不超过列表大小 + sample_size = min(len(customerIds), len(customerIds)) + toolbox.register("indices", random.sample, range(len(customerIds)), sample_size) + toolbox.register("individual", tools.initIterate, creator.Individual, toolbox.indices) + toolbox.register("population", tools.initRepeat, list, toolbox.individual) + toolbox.register("evaluate", evalRoute, center=center, customers=customers) + toolbox.register("mate", tools.cxOrdered) + toolbox.register("mutate", tools.mutShuffleIndexes, indpb=0.05) + toolbox.register("select", tools.selTournament, tournsize=3) + + return toolbox + +def optimizeRoutes(center, orders, customers): + customerIds = [order.customerId for order in orders if not order.processed] + if len(customerIds) < 2: # 如果小于2个订单,则无法进行有效的路径规划 + return [center] + [customers[idx] for idx in customerIds] + [center] + + toolbox = setupGA(center, customerIds, customers) + pop = toolbox.population(n=POP_SIZE) + hof = tools.HallOfFame(1, similar=np.array_equal) + + stats = tools.Statistics(lambda ind: ind.fitness.values) + stats.register("min", np.min) + stats.register("avg", np.mean) + + algorithms.eaSimple(pop, toolbox, cxpb=CX_PB, mutpb=MUT_PB, ngen=GEN, stats=stats, halloffame=hof, verbose=False) + + bestRoute = createRoute(hof[0], center, customers) + for idx in hof[0]: + orders[idx].processed = True + return bestRoute + + +def selectDrones(orders, centers, customers): + paths = [] + orders_by_center = defaultdict(list) + + for order in orders: + if order.processed: + continue + customerPos = customers[order.customerId] + nearest_center = min(centers, key=lambda x: distance(x, customerPos)) + orders_by_center[nearest_center].append(order) + + for center, center_orders in orders_by_center.items(): + if center_orders: + best_path = optimizeRoutes(center, center_orders, customers) + paths.append(best_path) + return paths + +# 绘图 +def plot(centers, customers, paths): + plt.figure(figsize=(10, 8)) + colors = plt.cm.viridis(np.linspace(0, 1, len(paths))) + + center_plotted, drop_plotted = False, False + + for x, y in centers: + if not center_plotted: + plt.scatter(x, y, c='blue', alpha=1, marker=',', linewidths=3) + center_plotted = True + else: + plt.scatter(x, y, c='blue', alpha=1, marker=',', linewidths=3) + + for x, y in customers: + if not drop_plotted: + plt.scatter(x, y, c='red', alpha=1, marker='o', linewidths=3) + drop_plotted = True + else: + plt.scatter(x, y, c='red', alpha=1, marker='o', linewidths=3) + + if paths: # 确保路径列表不为空 + for idx, path in enumerate(paths): + # 期望路径格式:路径是一个包含起点、途径点和终点的列表 + print(f"Path {idx + 1}: {path}") + plt.plot([p[0] for p in path], [p[1] for p in path], + color=colors[idx], linestyle='--') + + plt.legend(loc='best') + plt.title('Drone Delivery Routing') + plt.grid(False) + plt.show() + +# 使用改进的路径选择逻辑 +def simulation(duration): + orders = [] + time = 0 + paths = [] + + while time < duration: + new_orders = generateOrders(time) + orders.extend(new_orders) + + new_paths = selectDrones(orders, centers, customers) + paths.extend(new_paths) + + plot(centers, customers, paths) + plt.pause(1) + + time += timeInterval + + plt.show() + + +# 运行模拟 +simulation(240) diff --git "a/\346\262\210\351\235\231\350\277\234-2023202210156-\351\253\230\347\272\247\347\256\227\346\263\225/VRPTW\357\274\210\346\272\220\347\240\201\357\274\211/LP.py" "b/\346\262\210\351\235\231\350\277\234-2023202210156-\351\253\230\347\272\247\347\256\227\346\263\225/VRPTW\357\274\210\346\272\220\347\240\201\357\274\211/LP.py" new file mode 100644 index 0000000..c991c57 --- /dev/null +++ "b/\346\262\210\351\235\231\350\277\234-2023202210156-\351\253\230\347\272\247\347\256\227\346\263\225/VRPTW\357\274\210\346\272\220\347\240\201\357\274\211/LP.py" @@ -0,0 +1,672 @@ +# Description: Python 调用 Gurobi 建模求解 VRPTW 问题 +import time +import matplotlib.pyplot as plt +import numpy as np +from gurobipy import * + + +class Data: + customerNum = 0 + nodeNum = 0 + vehicleNum = 0 + capacity = 0 + corX = [] + corY = [] + demand = [] + serviceTime = [] + readyTime = [] + dueTime = [] + distanceMatrix = [[]] + + +def readData(path, customerNum): + data = Data() + data.customerNum = customerNum + if customerNum is not None: + data.nodeNum = customerNum + 6 + + with open(path, 'r') as f: + lines = f.readlines() + count = 0 + for line in lines: + count += 1 + if count == 5: + line = line[:-1] + s = re.split(r" +", line) + data.vehicleNum = int(s[1]) + data.capacity = float(s[2]) + elif count >= 10 and (customerNum is None or count <= 12 + customerNum): + line = line[:-1] + s = re.split(r" +", line) + data.corX.append(float(s[2])) + data.corY.append(float(s[3])) + data.demand.append(float(s[4])) + data.readyTime.append(float(s[5])) + data.dueTime.append(float(s[6])) + data.serviceTime.append(float(s[7])) + data.nodeNum = len(data.corX) + 3 + + data.customerNum = data.nodeNum - 6 + + + # 回路 + data.corX.append(data.corX[0]) + data.corY.append(data.corY[0]) + data.demand.append(data.demand[0]) + data.readyTime.append(data.readyTime[0]) + data.dueTime.append(data.dueTime[0]) + data.serviceTime.append(data.serviceTime[0]) + data.corX.append(data.corX[1]) + data.corY.append(data.corY[1]) + data.demand.append(data.demand[1]) + data.readyTime.append(data.readyTime[1]) + data.dueTime.append(data.dueTime[1]) + data.serviceTime.append(data.serviceTime[1]) + data.corX.append(data.corX[2]) + data.corY.append(data.corY[2]) + data.demand.append(data.demand[2]) + data.readyTime.append(data.readyTime[2]) + data.dueTime.append(data.dueTime[2]) + data.serviceTime.append(data.serviceTime[2]) + + # 计算距离矩阵 + data.distanceMatrix = np.zeros((data.nodeNum, data.nodeNum)) + for i in range(data.nodeNum): + for j in range(i + 1, data.nodeNum): + distance = math.sqrt((data.corX[i] - data.corX[j]) ** 2 + (data.corY[i] - data.corY[j]) ** 2) + data.distanceMatrix[i][j] = data.distanceMatrix[j][i] = distance + return data + + +class Solution: + ObjVal = 0 + X = [[]] + S = [[]] + routes = [[]] + routeNum = 0 + + def __init__(self, data, model): + self.ObjVal = model.ObjVal + # X_ijk + self.X = [[([0] * data.vehicleNum) for _ in range(data.nodeNum)] for _ in range(data.nodeNum)] + # S_ik + self.S = [([0] * data.vehicleNum) for _ in range(data.nodeNum)] + # routes + self.routes = [] + + +def getSolution(data, model): + solution = Solution(data, model) # 初始化 Solution 对象 + + for m in model.getVars(): # 遍历所有变量 + split_arr = re.split(r"_", m.VarName) # 分割变量名 + if split_arr[0] == 'X' and m.x > 0.5: # 检查是否为 X_ijk 变量且其值大于 0.5 + solution.X[int(split_arr[1])][int(split_arr[2])][int(split_arr[3])] = m.x # 更新 solution 中的 X 变量值 + elif split_arr[0] == 'S' and m.x > 0.5: # 检查是否为 S_ik 变量且其值大于 0.5 + solution.S[int(split_arr[1])][int(split_arr[2])] = m.x # 更新 solution 中的 S 变量值 + for k in range(data.vehicleNum): # 遍历每辆车 + i = 0 # 初始节点(通常开始节点是0) + subRoute = [] # 用于存储当前车辆路径的子路线 + subRoute.append(i) # 将起点加入路径 + finish = False # 设置结束标志,初始为False,表示路径尚未完成 + + while not finish: # 当路径未完成时,继续循环 + for j in range(data.nodeNum): # 遍历所有节点,寻找下一个线路 + if solution.X[i][j][k] > 0.5: # 如果车辆从i到j,是最优解中的一条路径 + subRoute.append(j) # 将节点j加入路径 + i = j # 更新当前节点为j + if j == data.nodeNum - 1: # 如果到达终点节点,路径结束 + finish = True + + if len(subRoute) >= 3: # 确保路径至少经过了两个节点(起点和所有访问的节点) + subRoute[-1] = 0 # 将子路径的最后一个节点变为起点(假设回到出发点) + solution.routes.append(subRoute) # 将该子路径加入solution.routes + solution.routeNum += 1 # 更新路径数 + return solution +def getSolution0(data, model): + solution = Solution(data, model) # 初始化 Solution 对象 + + for m in model.getVars(): # 遍历所有变量 + split_arr = re.split(r"_", m.VarName) # 分割变量名 + if split_arr[0] == 'X' and m.x > 0.5: # 检查是否为 X_ijk 变量且其值大于 0.5 + solution.X[int(split_arr[1])][int(split_arr[2])][int(split_arr[3])] = m.x # 更新 solution 中的 X 变量值 + elif split_arr[0] == 'S' and m.x > 0.5: # 检查是否为 S_ik 变量且其值大于 0.5 + solution.S[int(split_arr[1])][int(split_arr[2])] = m.x # 更新 solution 中的 S 变量值 + for k in range(data.vehicleNum): # 遍历每辆车 + i = 0 # 初始节点(通常开始节点是0) + subRoute = [] # 用于存储当前车辆路径的子路线 + subRoute.append(i) # 将起点加入路径 + finish = False # 设置结束标志,初始为False,表示路径尚未完成 + + while not finish: # 当路径未完成时,继续循环 + for j in r0: # 遍历所有节点,寻找下一个线路 + if solution.X[i][j][k] > 0.5: # 如果车辆从i到j,是最优解中的一条路径 + subRoute.append(j) # 将节点j加入路径 + i = j # 更新当前节点为j + if j == r0[-1]: # 如果到达终点节点,路径结束 + finish = True + + if len(subRoute) >= 3: # 确保路径至少经过了两个节点(起点和所有访问的节点) + subRoute[-1] = 0 # 将子路径的最后一个节点变为起点(假设回到出发点) + solution.routes.append(subRoute) # 将该子路径加入solution.routes + solution.routeNum += 1 # 更新路径数 + return solution +def getSolution1(data, model): + solution = Solution(data, model) # 初始化 Solution 对象 + + for m in model.getVars(): # 遍历所有变量 + split_arr = re.split(r"_", m.VarName) # 分割变量名 + if split_arr[0] == 'X' and m.x > 0.5: # 检查是否为 X_ijk 变量且其值大于 0.5 + solution.X[int(split_arr[1])][int(split_arr[2])][int(split_arr[3])] = m.x # 更新 solution 中的 X 变量值 + elif split_arr[0] == 'S' and m.x > 0.5: # 检查是否为 S_ik 变量且其值大于 0.5 + solution.S[int(split_arr[1])][int(split_arr[2])] = m.x # 更新 solution 中的 S 变量值 + for k in range(data.vehicleNum): # 遍历每辆车 + i = 1 # 初始节点(通常开始节点是0) + subRoute = [] # 用于存储当前车辆路径的子路线 + subRoute.append(i) # 将起点加入路径 + finish = False # 设置结束标志,初始为False,表示路径尚未完成 + + while not finish: # 当路径未完成时,继续循环 + for j in r1: # 遍历所有节点,寻找下一个线路 + if solution.X[i][j][k] > 0.5: # 如果车辆从i到j,是最优解中的一条路径 + subRoute.append(j) # 将节点j加入路径 + i = j # 更新当前节点为j + if j == r1[-1]: # 如果到达终点节点,路径结束 + finish = True + + if len(subRoute) >= 3: # 确保路径至少经过了两个节点(起点和所有访问的节点) + subRoute[-1] = 1 # 将子路径的最后一个节点变为起点(假设回到出发点) + solution.routes.append(subRoute) # 将该子路径加入solution.routes + solution.routeNum += 1 # 更新路径数 + return solution + +def getSolution2(data, model): + solution = Solution(data, model) # 初始化 Solution 对象 + + for m in model.getVars(): # 遍历所有变量 + split_arr = re.split(r"_", m.VarName) # 分割变量名 + if split_arr[0] == 'X' and m.x > 0.5: # 检查是否为 X_ijk 变量且其值大于 0.5 + solution.X[int(split_arr[1])][int(split_arr[2])][int(split_arr[3])] = m.x # 更新 solution 中的 X 变量值 + elif split_arr[0] == 'S' and m.x > 0.5: # 检查是否为 S_ik 变量且其值大于 0.5 + solution.S[int(split_arr[1])][int(split_arr[2])] = m.x # 更新 solution 中的 S 变量值 + for k in range(data.vehicleNum): # 遍历每辆车 + i = 2 # 初始节点(通常开始节点是0) + subRoute = [] # 用于存储当前车辆路径的子路线 + subRoute.append(i) # 将起点加入路径 + finish = False # 设置结束标志,初始为False,表示路径尚未完成 + + while not finish: # 当路径未完成时,继续循环 + for j in r2: # 遍历所有节点,寻找下一个线路 + if solution.X[i][j][k] > 0.5: # 如果车辆从i到j,是最优解中的一条路径 + subRoute.append(j) # 将节点j加入路径 + i = j # 更新当前节点为j + if j == r2[-1]: # 如果到达终点节点,路径结束 + finish = True + + if len(subRoute) >= 3: # 确保路径至少经过了两个节点(起点和所有访问的节点) + subRoute[-1] = 2 # 将子路径的最后一个节点变为起点(假设回到出发点) + solution.routes.append(subRoute) # 将该子路径加入solution.routes + solution.routeNum += 1 # 更新路径数 + return solution +def plot_solution(solution, customer_num): + # 设置绘图框的标题和坐标标签 + plt.xlabel("x") + plt.ylabel("y") + plt.title(f"{customer_num} Customers") # 标题显示数据类型和顾客数量 + + # 绘制起点 + plt.scatter(data.corX[0], data.corY[0], c='blue', alpha=1, marker=',', linewidths=3, label='depot') # 起点 + plt.scatter(data.corX[1], data.corY[1], c='blue', alpha=1, marker=',', linewidths=3, label='depot') # 起点 + plt.scatter(data.corX[2], data.corY[2], c='blue', alpha=1, marker=',', linewidths=3, label='depot') # 起点 + + # 绘制客户点 + plt.scatter(data.corX[3:-3], data.corY[3:-3], c='red', alpha=1, marker='o', linewidths=3, + label='customer') # 普通站点 + + # 遍历每个路径,并绘制路径上的每一段弧 + for k in range(solution.routeNum): + for i in range(len(solution.routes[k]) - 1): + a = solution.routes[k][i] + b = solution.routes[k][i + 1] + x = [data.corX[a], data.corX[b]] + y = [data.corY[a], data.corY[b]] + plt.plot(x, y, 'k', linewidth=1) # 使用黑色线绘制路径上的线段 + + # 绘制图表 + plt.grid(False) # 隐藏网格线 + # plt.legend(loc='best') # 添加图例 + + + +def print_solution(solution, data): + for index, subRoute in enumerate(solution.routes): + distance = 0 + load = 0 + for i in range(len(subRoute) - 1): + distance += data.distanceMatrix[subRoute[i]][subRoute[i + 1]] + load += data.demand[subRoute[i]] + print(f"Route-{index + 1} : {subRoute} , distance: {distance} , load: {load}") + + +def solve(data): + # 声明模型 + model = Model("VRPTW") + # 模型设置 + # 关闭输出 + model.setParam('OutputFlag', 0) + # 定义变量 + X = [[[[] for _ in range(data.vehicleNum)] for _ in range(data.nodeNum)] for _ in range(data.nodeNum)] + S = [[[] for _ in range(data.vehicleNum)] for _ in range(data.nodeNum)] + for i in range(data.nodeNum): + for k in range(data.vehicleNum): + S[i][k] = model.addVar(data.readyTime[i], data.dueTime[i], vtype=GRB.CONTINUOUS, name=f'S_{i}_{k}') + for j in range(data.nodeNum): + X[i][j][k] = model.addVar(vtype=GRB.BINARY, name=f"X_{i}_{j}_{k}") + # 目标函数 + obj = LinExpr(0) + for i in range(data.nodeNum): + for j in range(data.nodeNum): + if i != j: + for k in range(data.vehicleNum): + obj.addTerms(data.distanceMatrix[i][j], X[i][j][k]) + model.setObjective(obj, GRB.MINIMIZE) + # 约束1:车辆只能从一个点到另一个点 + for i in range(1, data.nodeNum - 1): + expr = LinExpr(0) + for j in range(data.nodeNum): + if i != j: + for k in range(data.vehicleNum): + if i != 0 and i != data.nodeNum - 1: + expr.addTerms(1, X[i][j][k]) + model.addConstr(expr == 1) + # 约束2:车辆必须从仓库出发 + for k in range(data.vehicleNum): + expr = LinExpr(0) + for j in range(1, data.nodeNum): + expr.addTerms(1, X[0][j][k]) + model.addConstr(expr == 1) + # 约束3:车辆经过一个点就必须离开一个点 + for k in range(data.vehicleNum): + for h in range(1, data.nodeNum - 1): + expr1 = LinExpr(0) + expr2 = LinExpr(0) + for i in range(data.nodeNum): + if h != i: + expr1.addTerms(1, X[i][h][k]) + for j in range(data.nodeNum): + if h != j: + expr2.addTerms(1, X[h][j][k]) + model.addConstr(expr1 == expr2) + # 约束4:车辆最终返回仓库 + for k in range(data.vehicleNum): + expr = LinExpr(0) + for i in range(data.nodeNum - 1): + expr.addTerms(1, X[i][data.nodeNum - 1][k]) + model.addConstr(expr == 1) + # 约束5:车辆容量约束 + for k in range(data.vehicleNum): + expr = LinExpr(0) + for i in range(1, data.nodeNum - 1): + for j in range(data.nodeNum): + if i != 0 and i != data.nodeNum - 1 and i != j: + expr.addTerms(data.demand[i], X[i][j][k]) + model.addConstr(expr <= data.capacity) + # 约束6:时间窗约束 + for k in range(data.vehicleNum): + for i in range(data.nodeNum): + for j in range(data.nodeNum): + if i != j: + model.addConstr(S[i][k] + data.distanceMatrix[i][j] - S[j][k] <= M - M * X[i][j][k]) + # 记录求解开始时间 + start_time = time.time() + # 求解 + model.optimize() + if model.status == GRB.OPTIMAL: + print("-" * 20, "Solved Successfully", '-' * 20) + # 输出求解总用时 + print(f"Solve Time: {time.time() - start_time} s") + print(f"Total Travel Distance: {model.ObjVal}") + solution = getSolution(data, model) + plot_solution(solution, data.customerNum) + print_solution(solution, data) + else: + print("此题无解") +def solve0(data): + # 声明模型 + model = Model("VRPTW0") + # 模型设置 + # 关闭输出 + model.setParam('OutputFlag', 0) + # 定义变量 + X = [[[[] for _ in range(data.vehicleNum)] for _ in range(data.nodeNum)] for _ in range(data.nodeNum)] + S = [[[] for _ in range(data.vehicleNum)] for _ in range(data.nodeNum)] + for i in r0: + for k in range(data.vehicleNum): + S[i][k] = model.addVar(data.readyTime[i], data.dueTime[i], vtype=GRB.CONTINUOUS, name=f'S_{i}_{k}') + for j in r0: + X[i][j][k] = model.addVar(vtype=GRB.BINARY, name=f"X_{i}_{j}_{k}") + # 目标函数 + obj = LinExpr(0) + for i in r0: + for j in r0: + if i != j: + for k in range(data.vehicleNum): + obj.addTerms(data.distanceMatrix[i][j], X[i][j][k]) + model.setObjective(obj, GRB.MINIMIZE) + # 约束1:车辆只能从一个点到另一个点 + for i in r0[1:-1]: + expr = LinExpr(0) + for j in r0: + if i != j: + for k in range(data.vehicleNum): + if i != 0 and i != len(r0) - 1: + expr.addTerms(1, X[i][j][k]) + model.addConstr(expr == 1) + # 约束2:车辆必须从仓库出发 + for k in range(data.vehicleNum): + expr = LinExpr(0) + for j in r0[1:]: + expr.addTerms(1, X[0][j][k]) + model.addConstr(expr == 1) + # 约束3:车辆经过一个点就必须离开一个点 + for k in range(data.vehicleNum): + for h in r0[1:-1]: + expr1 = LinExpr(0) + expr2 = LinExpr(0) + for i in r0: + if h != i: + expr1.addTerms(1, X[i][h][k]) + for j in r0: + if h != j: + expr2.addTerms(1, X[h][j][k]) + model.addConstr(expr1 == expr2) + # 约束4:车辆最终返回仓库 + for k in range(data.vehicleNum): + expr = LinExpr(0) + for i in r0[:-1]: + expr.addTerms(1, X[i][r0[-1]][k]) + model.addConstr(expr == 1) + # 约束5:车辆容量约束 + for k in range(data.vehicleNum): + expr = LinExpr(0) + for i in r0[1:-1]: + for j in r0: + if i != 0 and i != data.nodeNum - 1 and i != j: + expr.addTerms(data.demand[i], X[i][j][k]) + model.addConstr(expr <= data.capacity) + # 约束6:时间窗约束 + for k in range(data.vehicleNum): + for i in r0: + for j in r0: + if i != j: + model.addConstr(S[i][k] + data.distanceMatrix[i][j] - S[j][k] <= M - M * X[i][j][k]) + # 约束7: + # 为每个车辆k添加约束条件 + for k in range(data.vehicleNum): + for i in r0: + for j in r0: + if data.distanceMatrix[i][j] < 20: + # 只有当弧ij的长度小于20时,才需要添加约束 + model.addConstr(X[i][j][k] <= 1) + else: + # 如果弧ij的长度大于或等于20,车辆k不能选择这条弧 + model.addConstr(X[i][j][k] == 0) + + # 记录求解开始时间 + start_time = time.time() + # 求解 + model.optimize() + if model.status == GRB.OPTIMAL: + print("-" * 20, "配送中心0 Solved Successfully", '-' * 20) + # 输出求解总用时 + print(f"Solve Time: {time.time() - start_time} s") + print(f"Total Travel Distance: {model.ObjVal}") + solution = getSolution0(data, model) + print(solution.routes) + plot_solution(solution, data.customerNum) + print_solution(solution, data) + else: + print("此题无解") +def solve1(data): + # 声明模型 + model = Model("VRPTW1") + # 模型设置 + # 关闭输出 + model.setParam('OutputFlag', 0) + # 定义变量 + X = [[[[] for _ in range(data.vehicleNum)] for _ in range(data.nodeNum)] for _ in range(data.nodeNum)] + S = [[[] for _ in range(data.vehicleNum)] for _ in range(data.nodeNum)] + for i in r1: + for k in range(data.vehicleNum): + S[i][k] = model.addVar(data.readyTime[i], data.dueTime[i], vtype=GRB.CONTINUOUS, name=f'S_{i}_{k}') + for j in r1: + X[i][j][k] = model.addVar(vtype=GRB.BINARY, name=f"X_{i}_{j}_{k}") + # 目标函数 + obj = LinExpr(0) + for i in r1: + for j in r1: + if i != j: + for k in range(data.vehicleNum): + obj.addTerms(data.distanceMatrix[i][j], X[i][j][k]) + model.setObjective(obj, GRB.MINIMIZE) + # 约束1:车辆只能从一个点到另一个点 + for i in r1[1:-1]: + expr = LinExpr(0) + for j in r1: + if i != j: + for k in range(data.vehicleNum): + if i != 0 and i != len(r1) - 1: + expr.addTerms(1, X[i][j][k]) + model.addConstr(expr == 1) + # 约束2:车辆必须从仓库出发 + for k in range(data.vehicleNum): + expr = LinExpr(0) + for j in r1[1:]: + expr.addTerms(1, X[1][j][k]) + model.addConstr(expr == 1) + # 约束3:车辆经过一个点就必须离开一个点 + for k in range(data.vehicleNum): + for h in r1[1:-1]: + expr1 = LinExpr(0) + expr2 = LinExpr(0) + for i in r1: + if h != i: + expr1.addTerms(1, X[i][h][k]) + for j in r1: + if h != j: + expr2.addTerms(1, X[h][j][k]) + model.addConstr(expr1 == expr2) + # 约束4:车辆最终返回仓库 + for k in range(data.vehicleNum): + expr = LinExpr(0) + for i in r1[:-1]: + expr.addTerms(1, X[i][r1[-1]][k]) + model.addConstr(expr == 1) + # 约束5:车辆容量约束 + for k in range(data.vehicleNum): + expr = LinExpr(0) + for i in r1[1:-1]: + for j in r1: + if i != 0 and i != data.nodeNum - 1 and i != j: + expr.addTerms(data.demand[i], X[i][j][k]) + model.addConstr(expr <= data.capacity) + # 约束6:时间窗约束 + for k in range(data.vehicleNum): + for i in r1: + for j in r1: + if i != j: + model.addConstr(S[i][k] + data.distanceMatrix[i][j] - S[j][k] <= M - M * X[i][j][k]) + # 约束7: + # 为每个车辆k添加约束条件 + for k in range(data.vehicleNum): + for i in r1: + for j in r1: + if data.distanceMatrix[i][j] < 20: + # 只有当弧ij的长度小于20时,才需要添加约束 + model.addConstr(X[i][j][k] <= 1) + else: + # 如果弧ij的长度大于或等于20,车辆k不能选择这条弧 + model.addConstr(X[i][j][k] == 0) + # 记录求解开始时间 + start_time = time.time() + # 求解 + model.optimize() + if model.status == GRB.OPTIMAL: + print("-" * 20, "配送中心1 Solved Successfully", '-' * 20) + # 输出求解总用时 + print(f"Solve Time: {time.time() - start_time} s") + print(f"Total Travel Distance: {model.ObjVal}") + solution = getSolution1(data, model) + print(solution.routes) + plot_solution(solution, data.customerNum) + print_solution(solution, data) + else: + print("此题无解") +def solve2(data): + # 声明模型 + model = Model("VRPTW2") + # 模型设置 + # 关闭输出 + model.setParam('OutputFlag', 0) + # 定义变量 + X = [[[[] for _ in range(data.vehicleNum)] for _ in range(data.nodeNum)] for _ in range(data.nodeNum)] + S = [[[] for _ in range(data.vehicleNum)] for _ in range(data.nodeNum)] + for i in r2: + for k in range(data.vehicleNum): + S[i][k] = model.addVar(data.readyTime[i], data.dueTime[i], vtype=GRB.CONTINUOUS, name=f'S_{i}_{k}') + for j in r2: + X[i][j][k] = model.addVar(vtype=GRB.BINARY, name=f"X_{i}_{j}_{k}") + # 目标函数 + obj = LinExpr(0) + for i in r2: + for j in r2: + if i != j: + for k in range(data.vehicleNum): + obj.addTerms(data.distanceMatrix[i][j], X[i][j][k]) + model.setObjective(obj, GRB.MINIMIZE) + # 约束1:车辆只能从一个点到另一个点 + for i in r2[1:-1]: + expr = LinExpr(0) + for j in r2: + if i != j: + for k in range(data.vehicleNum): + if i != 0 and i != len(r2) - 1: + expr.addTerms(1, X[i][j][k]) + model.addConstr(expr == 1) + # 约束2:车辆必须从仓库出发 + for k in range(data.vehicleNum): + expr = LinExpr(0) + for j in r2[1:]: + expr.addTerms(1, X[2][j][k]) + model.addConstr(expr == 1) + # 约束3:车辆经过一个点就必须离开一个点 + for k in range(data.vehicleNum): + for h in r2[1:-1]: + expr1 = LinExpr(0) + expr2 = LinExpr(0) + for i in r2: + if h != i: + expr1.addTerms(1, X[i][h][k]) + for j in r2: + if h != j: + expr2.addTerms(1, X[h][j][k]) + model.addConstr(expr1 == expr2) + # 约束4:车辆最终返回仓库 + for k in range(data.vehicleNum): + expr = LinExpr(0) + for i in r2[:-1]: + expr.addTerms(1, X[i][r2[-1]][k]) + model.addConstr(expr == 1) + # 约束5:车辆容量约束 + for k in range(data.vehicleNum): + expr = LinExpr(0) + for i in r2[1:-1]: + for j in r2: + if i != 0 and i != data.nodeNum - 1 and i != j: + expr.addTerms(data.demand[i], X[i][j][k]) + model.addConstr(expr <= data.capacity) + # 约束6:时间窗约束 + for k in range(data.vehicleNum): + for i in r2: + for j in r2: + if i != j: + model.addConstr(S[i][k] + data.distanceMatrix[i][j] - S[j][k] <= M - M * X[i][j][k]) + # 约束7: + # 为每个车辆k添加约束条件 + for k in range(data.vehicleNum): + for i in r2: + for j in r2: + if data.distanceMatrix[i][j] < 20: + # 只有当弧ij的长度小于20时,才需要添加约束 + model.addConstr(X[i][j][k] <= 1) + else: + # 如果弧ij的长度大于或等于20,车辆k不能选择这条弧 + model.addConstr(X[i][j][k] == 0) + + # 记录求解开始时间 + start_time = time.time() + # 求解 + model.optimize() + if model.status == GRB.OPTIMAL: + print("-" * 20, "配送中心2 Solved Successfully", '-' * 20) + # 输出求解总用时 + print(f"Solve Time: {time.time() - start_time} s") + print(f"Total Travel Distance: {model.ObjVal}") + solution = getSolution2(data, model) + print(solution.routes) + plot_solution(solution, data.customerNum) + print_solution(solution, data) + else: + print("此题无解") + + + + + +if __name__ == '__main__': + r0 = [] + r1 = [] + r2 = [] + + # 数据集路径 + data_path = '../spots(问题实例数据).txt' + # 顾客个数设置(从上往下读取完 customerNum 个顾客为止,例如c101文件中有100个顾客点, + # 但是跑100个顾客点太耗时了,设置这个数是为了只选取一部分顾客点进行计算,用来快速测试算法) + # 如果想用完整的顾客点进行计算,设置为None即可 + customerNum = 24 + + + # 一个很大的正数 + M = 10000000 + # 读取数据 + data = readData(data_path, customerNum) + + r0.append(0) + r1.append(1) + r2.append(2) + for i in range(customerNum): + if data.distanceMatrix[0][3+i]<=data.distanceMatrix[1][3+i] and data.distanceMatrix[0][3+i]<=data.distanceMatrix[2][3+i]: + r0.append(3+i) + elif data.distanceMatrix[1][3+i]<=data.distanceMatrix[0][3+i] and data.distanceMatrix[1][3+i]<=data.distanceMatrix[2][3+i]: + r1.append(3+i) + elif data.distanceMatrix[2][3+i]<=data.distanceMatrix[0][3+i] and data.distanceMatrix[2][3+i]<=data.distanceMatrix[1][3+i]: + r2.append(3+i) + r0.append(data.nodeNum-3) + r1.append(data.nodeNum-2) + r2.append(data.nodeNum-1) + print(f"配送中心0:{r0}") + print(f"配送中心1:{r1}") + print(f"配送中心2:{r2}") + + + # 输出相关数据 + print("-" * 20, "Problem Information", '-' * 20) + print(f'Node Num: {data.nodeNum}') + print(f'Customer Num: {data.customerNum}') + print(f'Vehicle Num: {data.vehicleNum}') + print(f'Vehicle Capacity: {data.capacity}') + # 建模求解 + solve0(data) + solve1(data) + solve2(data) + plt.show() # 显示图表 diff --git "a/\346\262\210\351\235\231\350\277\234-2023202210156-\351\253\230\347\272\247\347\256\227\346\263\225/spots\357\274\210\351\227\256\351\242\230\345\256\236\344\276\213\346\225\260\346\215\256\357\274\211.txt" "b/\346\262\210\351\235\231\350\277\234-2023202210156-\351\253\230\347\272\247\347\256\227\346\263\225/spots\357\274\210\351\227\256\351\242\230\345\256\236\344\276\213\346\225\260\346\215\256\357\274\211.txt" new file mode 100644 index 0000000..b1e43f9 --- /dev/null +++ "b/\346\262\210\351\235\231\350\277\234-2023202210156-\351\253\230\347\272\247\347\256\227\346\263\225/spots\357\274\210\351\227\256\351\242\230\345\256\236\344\276\213\346\225\260\346\215\256\357\274\211.txt" @@ -0,0 +1,36 @@ + + +VEHICLE +NUMBER CAPACITY + 100 500 + +CUSTOMER +CUST NO. XCOORD. YCOORD. DEMAND READY TIME DUE DATE SERVICE TIME + + 0 40 50 0 0 360 0 + 1 20 40 0 0 360 0 + 2 37 20 0 0 360 0 + 3 40 45 13 0 180 0 + 4 55 20 19 0 180 0 + 5 15 30 26 0 90 0 + 6 25 30 3 0 90 0 + 7 20 50 5 0 180 0 + 8 10 43 9 0 30 0 + 9 45 60 16 0 90 0 + 10 30 60 16 0 90 0 + 11 35 65 12 0 90 0 + 12 50 50 19 0 90 0 + 13 30 25 23 0 180 0 + 14 40 10 20 0 180 0 + 15 30 5 8 0 180 0 + 16 10 20 19 0 30 0 + 17 5 30 2 0 30 0 + 18 30 40 12 0 90 0 + 19 15 60 17 0 90 0 + 20 45 65 9 0 180 0 + 21 45 22 11 0 180 0 + 22 45 10 18 0 90 0 + 23 55 5 29 0 90 0 + 24 40 35 3 0 180 0 + 25 65 20 6 0 30 0 + 26 45 30 17 0 90 0 \ No newline at end of file diff --git "a/\346\262\210\351\235\231\350\277\234-2023202210156-\351\253\230\347\272\247\347\256\227\346\263\225/\346\262\210\351\235\231\350\277\234-2023202210156-\351\253\230\347\272\247\347\256\227\346\263\225.pdf" "b/\346\262\210\351\235\231\350\277\234-2023202210156-\351\253\230\347\272\247\347\256\227\346\263\225/\346\262\210\351\235\231\350\277\234-2023202210156-\351\253\230\347\272\247\347\256\227\346\263\225.pdf" new file mode 100644 index 0000000..81affe9 Binary files /dev/null and "b/\346\262\210\351\235\231\350\277\234-2023202210156-\351\253\230\347\272\247\347\256\227\346\263\225/\346\262\210\351\235\231\350\277\234-2023202210156-\351\253\230\347\272\247\347\256\227\346\263\225.pdf" differ