Diretório de artigos
I. Visão geral
1.1 problema VRP
O problema de roteamento de veículos (Vehicle Routing Problem, VRP) geralmente se refere a: para uma série de pontos de entrega e pontos de recebimento, organizar e chamar determinados veículos, organizar rotas de direção apropriadas, de modo que os veículos passem por eles de maneira ordenada e atendam aos requisitos especificados Sob certas restrições (por exemplo: demanda e entrega de mercadorias, tempo de entrega, limitação da capacidade do veículo, limitação da quilometragem, limitação do tempo de direção, etc.), esforce-se para atingir determinados objetivos (como a menor quilometragem total de veículos vazios, o total tempo de transporte, etc.) O custo é o menor, os veículos chegam em um determinado horário, o número de veículos utilizados é o menor, etc.).
A figura abaixo dá um exemplo de um VRP simples
1.2 Problema CVRP
O problema VRP mais básico é chamado de Problema de Roteamento de Veículo Capacitado (CVRP). No CVRP, as restrições de capacidade de cada veículo, as restrições de caminho dos veículos e as restrições de carga precisam ser consideradas
1.3 Problema VRPTW
Para considerar os requisitos de tempo de entrega, surgiu o problema de roteirização de veículos com janela de tempo (Vehicle Routing Problem with Time Window, VRPTW).
O VRPTW não só considera todas as restrições do CVRP, mas também precisa considerar as restrições da janela de tempo, ou seja, cada cliente corresponde a uma janela de tempo [ ei , li ] [e_i,l_i][ eeu,eueu] , entre os quaisei e_ieeusoma li l_ieueuRepresentam o tempo de chegada mais cedo e o tempo de chegada mais tarde do ponto, respectivamente. Ponto do cliente i ∈ V i \in Veu∈A demanda de V deve ser atendida dentro de sua janela de tempo
VRPTW provou ser um problema NP-difícil, e sua complexidade de solução aumenta acentuadamente com o aumento do tamanho do problema, tornando-o difícil de resolver. Até agora, os algoritmos mais eficientes e precisos para resolver o VRPTW são o algoritmo de precificação de agências e o algoritmo de precificação e corte de agências.
2. O modelo geral de VRPTW
O VRPTW pode ser modelado como um problema de programação inteira mista. Antes de fornecer o modelo matemático completo, as seguintes variáveis de decisão são introduzidas:
xij = { 1 , se na solução ótima, arco ( i , j ) é selecionado pelo veículo k 0 , outros sik = outros parâmetros envolvidos no modelo de tempo quando o veículo k chega em i são: tij significa que o veículo está no arco ( i , j ) é um número positivo suficientemente grande {x_i}_j=\begin{cases} 1\text{, se na solução ótima, o arco}\left( i,j \right) \text{é Veículo} k\text{selecionado}\\ 0\text{, outro}\\ \end{casos} \\ {s_i}_k=\text{veículo}k\text{hora de chegada}i\text{hora} \ \ \ text{Os outros parâmetros envolvidos no modelo são}: \\ {t_i}_j\text{representa o tempo de viagem do veículo no arco}\left( i,j \right) \text{} \\ M\text { é um número positivo suficientemente grande}xeuj={ 1 , se na solução ótima, o arco( eu ,j )selecionado por veículo k0 , outroseuk=O momento em que o veículo k chega em iOutros parâmetros envolvidos no modelo são:teujIndica que o veículo está no arco( eu ,j )tempo de condução emM é um número positivo suficientemente grande
Em relação ao valor de M, de fato, um número positivo muito grande pode ser obtido diretamente, mas para melhorar a eficiência da solução, as restrições são apertadas. Podemos usar o seguinte método de valor:
M = max { bi + tij − aj } , ∀ ( i , j ) ∈ AM=max\{b_i+t_{ij}-a_j\} , \forall (i,j)\in AM=ma x { beu+teu−aj} ,∀ ( eu ,j )∈A
Combinando as variáveis de decisão acima mencionadas e consultando a literatura (Desaulniers et al., 2006), o modelo padrão de VRPTW é dado da seguinte forma:
min ∑ k ∈ K ∑ i ∈ V ∑ i ∈ V cijxijks . t . ∑ k ∈ K ∑ j ∈ V xijk = 1 , ∀ i ∈ C ∑ j ∈ V x 0 jk = 1 , ∀ k ∈ K ∑ i ∈ V xihk − ∑ j ∈ V xhjk = 0 , ∀ h ∈ C , ∀ k ∈ K ∑ i ∈ V xi , n + 1 , k = 1 , ∀ k ∈ K ∑ i ∈ C qi ∑ j ∈ V xijk = 1 , ∀ k ∈ K sik + tij − M ( 1 − xijk ) ⩽ sjk , ∀ ( i , j ) ∈ A , ∀ k ∈ K ei ⩽ sik ⩽ li , ∀ i ∈ V , ∀ k ∈ K xijk ∈ { 0 , 1 } , ∀ ( i , j ) ∈ A , ∀ k ∈ K \min \sum_{k\in K}{\sum_{i\in V}{\sum_{i\in V}{ {c_i}_j{x_i }_{j_k}}}} \\ st \sum_{k \in K}{\sum_{j\in V}{ {x_i}_{j_k}=1 , \forall i\in C}} \\ \,\, \sum_{j\in V}{ { x_0} _{j_k}=1 , \forall k\in K} \\ \,\, \sum_{i\in V}{ { x_i}_{h_k}-\sum_{j\in V}{ {x_h}_{j_k}=0 , \forall h\in C,\forall k\in K}} \\ \,\, \sum_{i\in V}{x_{i,n+1,k} =1 , \forall k\in K} \\ \,\, \sum_{i\in C}{q_i\sum_{j\in V}{ {x_i}_{j_k}=1 , \forall k\ in K}} \\ \,\, {s_i}_k+{t_i}_j-M\left( 1-{x_i}_{j_k} \right) \leqslant {s_j}_k\,\,, \forall \left( i,j \right) \in A,\forall k\in K \\ \,\, e_i\leqslant {s_i}_k\leqslant l_i\,\,, \forall i\in V,\forall k\in K \\ \,\, {x_i}_{j_k}\in \left\{ 0,1 \right\} \,\,, \forall \left( i,j \right) \in A,\forall k\ tintamink ∈ K∑i ∈ V∑i ∈ V∑ceujxeujks . t .k ∈ K∑j ∈ V∑xeujk=1 ,∀ eu∈Cj ∈ V∑x0jk=1 ,∀ k∈ki ∈ V∑xeuhk−j ∈ V∑xhjk=0 ,∀h _∈C ,∀ k∈ki ∈ V∑xi , n + 1 , k=1 ,∀ k∈ki ∈ C∑qeuj ∈ V∑xeujk=1 ,∀ k∈kseuk+teuj−M( 1−xeujk)⩽sjk,∀( eu ,j )∈A ,∀ k∈keeu⩽seuk⩽eueu,∀ eu∈V ,∀ k∈kxeujk∈{ 0 ,1 },∀( eu ,j )∈A ,∀ k∈k
em:
- A função objetivo é minimizar o custo total de viagem (distância) de todos os veículos
- As restrições 1 a 4 garantem que cada carro deve partir do depósito, deixar esse ponto após passar por um ponto e, finalmente, retornar ao depósito
- A restrição 5 é a restrição de capacidade do veículo
- As restrições 6~7 são restrições de janela de tempo, que garantem que o horário de chegada do veículo a cada ponto cliente esteja dentro da janela de tempo. O ponto n+1 é um backup do ponto o, para maior comodidade de realização.
3. Python chama Gurobi para modelar e resolver
3.1 Conjunto de Dados Solomn
3.2 Código completo
Observe que no código a seguir, o arco iieu to arcojjO tempo necessário para j tij t_{ij}teue custo cij c_{ij}ceutudo como arco iieu to arcojjj a distância desejada para olhar
# -*- coding: utf-8 -*-#
# Author: WSKH
# Blog: wskh0929.blog.csdn.net
# Time: 2023/2/8 11:14
# 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 + 2
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 <= 10 + 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) + 1
data.customerNum = data.nodeNum - 2
# 回路
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.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)
for m in model.getVars():
split_arr = re.split(r"_", m.VarName)
if split_arr[0] == 'X' and m.x > 0.5:
solution.X[int(split_arr[1])][int(split_arr[2])][int(split_arr[3])] = m.x
elif split_arr[0] == 'S' and m.x > 0.5:
solution.S[int(split_arr[1])][int(split_arr[2])] = m.x
for k in range(data.vehicleNum):
i = 0
subRoute = []
subRoute.append(i)
finish = False
while not finish:
for j in range(data.nodeNum):
if solution.X[i][j][k] > 0.5:
subRoute.append(j)
i = j
if j == data.nodeNum - 1:
finish = True
if len(subRoute) >= 3:
subRoute[-1] = 0
solution.routes.append(subRoute)
solution.routeNum += 1
return solution
def plot_solution(solution, customer_num):
plt.xlabel("x")
plt.ylabel("y")
plt.title(f"{
data_type} : {
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:-1], data.corY[1:-1], c='black', 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')
plt.show()
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("此题无解")
if __name__ == '__main__':
# 哪个数据集
data_type = "c101"
# 数据集路径
data_path = f'../../data/solomn_data/{
data_type}.txt'
# 顾客个数设置(从上往下读取完 customerNum 个顾客为止,例如c101文件中有100个顾客点,
# 但是跑100个顾客点太耗时了,设置这个数是为了只选取一部分顾客点进行计算,用来快速测试算法)
# 如果想用完整的顾客点进行计算,设置为None即可
customerNum = 50
# 一个很大的正数
M = 10000000
# 读取数据
data = readData(data_path, customerNum)
# 输出相关数据
print("-" * 20, "Problem Information", '-' * 20)
print(f'Data Type: {
data_type}')
print(f'Node Num: {
data.nodeNum}')
print(f'Customer Num: {
data.customerNum}')
print(f'Vehicle Num: {
data.vehicleNum}')
print(f'Vehicle Capacity: {
data.capacity}')
# 建模求解
solve(data)
3.3 Exibição dos resultados da corrida
3.3.1 Caso de teste: c101.txt
definir customerNum = 20
-------------------- Problem Information --------------------
Data Type: c101
Node Num: 22
Customer Num: 20
Vehicle Num: 25
Vehicle Capacity: 200.0
-------------------- Solved Successfully --------------------
Solve Time: 0.2966279983520508 s
Total Travel Distance: 160.81590595966603
Route-1 : [0, 20, 13, 17, 18, 19, 15, 16, 14, 12, 0] , distance: 101.32767502613292 , load: 200.0
Route-2 : [0, 5, 3, 7, 8, 10, 11, 9, 6, 4, 2, 1, 0] , distance: 59.48823093353308 , load: 160.0
definir customerNum = 50
Data Type: c101
Node Num: 52
Customer Num: 50
Vehicle Num: 25
Vehicle Capacity: 200.0
-------------------- Solved Successfully --------------------
Solve Time: 4.383494138717651 s
Total Travel Distance: 363.2468004115909
Route-1 : [0, 5, 3, 7, 8, 10, 11, 9, 6, 4, 2, 1, 0] , distance: 59.48823093353308 , load: 160.0
Route-2 : [0, 32, 33, 31, 35, 37, 38, 39, 36, 34, 0] , distance: 97.2271627850669 , load: 200.0
Route-3 : [0, 43, 42, 41, 40, 44, 46, 45, 48, 50, 49, 47, 0] , distance: 59.843107259523165 , load: 140.0
Route-4 : [0, 20, 24, 25, 27, 29, 30, 28, 26, 23, 22, 21, 0] , distance: 50.80359030264955 , load: 170.0
Route-5 : [0, 13, 17, 18, 19, 15, 16, 14, 12, 0] , distance: 95.88470913081827 , load: 190.0
definir customerNum = Nenhum
-------------------- Problem Information --------------------
Data Type: c101
Node Num: 102
Customer Num: 100
Vehicle Num: 25
Vehicle Capacity: 200.0
-------------------- Solved Successfully --------------------
Solve Time: 272.5895857810974 s
Total Travel Distance: 828.9368669428341
Route-1 : [0, 20, 24, 25, 27, 29, 30, 28, 26, 23, 22, 21, 0] , distance: 50.80359030264955 , load: 170.0
Route-2 : [0, 57, 55, 54, 53, 56, 58, 60, 59, 0] , distance: 101.88256760196126 , load: 200.0
Route-3 : [0, 5, 3, 7, 8, 10, 11, 9, 6, 4, 2, 1, 75, 0] , distance: 59.618077542105574 , load: 180.0
Route-4 : [0, 98, 96, 95, 94, 92, 93, 97, 100, 99, 0] , distance: 95.94313062205805 , load: 190.0
Route-5 : [0, 81, 78, 76, 71, 70, 73, 77, 79, 80, 0] , distance: 127.29748041459519 , load: 150.0
Route-6 : [0, 32, 33, 31, 35, 37, 38, 39, 36, 34, 0] , distance: 97.2271627850669 , load: 200.0
Route-7 : [0, 43, 42, 41, 40, 44, 46, 45, 48, 51, 50, 52, 49, 47, 0] , distance: 64.80747449698114 , load: 160.0
Route-8 : [0, 90, 87, 86, 83, 82, 84, 85, 88, 89, 91, 0] , distance: 76.06956532288787 , load: 170.0
Route-9 : [0, 13, 17, 18, 19, 15, 16, 14, 12, 0] , distance: 95.88470913081827 , load: 190.0
Route-10 : [0, 67, 65, 63, 62, 74, 72, 61, 64, 68, 66, 69, 0] , distance: 59.403108723710105 , load: 200.0
3.3.2 Caso de teste: r101.txt
definir customerNum = 20
-------------------- Problem Information --------------------
Data Type: r101
Node Num: 22
Customer Num: 20
Vehicle Num: 25
Vehicle Capacity: 200.0
-------------------- Solved Successfully --------------------
Solve Time: 0.9535932540893555 s
Total Travel Distance: 463.69270291007086
Route-1 : [0, 9, 20, 1, 0] , distance: 74.91992978886165 , load: 35.0
Route-2 : [0, 12, 3, 4, 0] , distance: 76.18033988749895 , load: 51.0
Route-3 : [0, 2, 15, 13, 0] , distance: 62.180339887498945 , load: 38.0
Route-4 : [0, 5, 18, 8, 17, 0] , distance: 86.57837545317302 , load: 49.0
Route-5 : [0, 14, 16, 6, 0] , distance: 72.40405733948208 , load: 42.0
Route-6 : [0, 11, 19, 7, 10, 0] , distance: 91.42966055355615 , load: 50.0
definir customerNum = 50
-------------------- Problem Information --------------------
Data Type: r101
Node Num: 52
Customer Num: 50
Vehicle Num: 25
Vehicle Capacity: 200.0
-------------------- Solved Successfully --------------------
Solve Time: 4.6791017055511475 s
Total Travel Distance: 946.6603871872358
Route-1 : [0, 21, 40, 26, 0] , distance: 43.35023188854984 , load: 37.0
Route-2 : [0, 33, 29, 9, 34, 24, 25, 0] , distance: 139.4708769010923 , load: 59.0
Route-3 : [0, 39, 23, 41, 22, 4, 0] , distance: 99.11062351878482 , load: 102.0
Route-4 : [0, 28, 12, 3, 50, 0] , distance: 51.94121366484106 , load: 61.0
Route-5 : [0, 36, 47, 11, 19, 49, 10, 32, 1, 0] , distance: 154.4302586824376 , load: 140.0
Route-6 : [0, 42, 14, 44, 16, 38, 37, 17, 0] , distance: 131.9204195702968 , load: 88.0
Route-7 : [0, 2, 15, 43, 13, 0] , distance: 72.54724253800985 , load: 45.0
Route-8 : [0, 45, 8, 46, 48, 0] , distance: 84.49944230335126 , load: 62.0
Route-9 : [0, 5, 7, 18, 6, 0] , distance: 73.5917360311745 , load: 46.0
Route-10 : [0, 27, 31, 30, 20, 35, 0] , distance: 95.79834208869767 , load: 81.0
definir customerNum = 70
-------------------- Problem Information --------------------
Data Type: r101
Node Num: 72
Customer Num: 70
Vehicle Num: 25
Vehicle Capacity: 200.0
-------------------- Solved Successfully --------------------
Solve Time: 189.01783299446106 s
Total Travel Distance: 1182.9787814963945
Route-1 : [0, 63, 62, 11, 64, 49, 48, 0] , distance: 125.38755919928242 , load: 116.0
Route-2 : [0, 65, 66, 20, 32, 70, 0] , distance: 117.49399251197822 , load: 82.0
Route-3 : [0, 28, 12, 26, 0] , distance: 33.795507476994075 , load: 52.0
Route-4 : [0, 33, 29, 3, 50, 68, 0] , distance: 90.77710269056311 , load: 82.0
Route-5 : [0, 2, 15, 41, 22, 56, 4, 0] , distance: 88.90058825018636 , load: 63.0
Route-6 : [0, 27, 69, 31, 30, 51, 9, 34, 35, 1, 0] , distance: 111.48892006549234 , load: 128.0
Route-7 : [0, 45, 8, 46, 17, 60, 0] , distance: 93.91701945260407 , load: 31.0
Route-8 : [0, 59, 42, 14, 44, 38, 57, 43, 58, 0] , distance: 131.96251141349887 , load: 119.0
Route-9 : [0, 39, 23, 67, 55, 54, 24, 25, 0] , distance: 140.03829072128988 , load: 114.0
Route-10 : [0, 52, 18, 6, 0] , distance: 41.290161379846566 , load: 24.0
Route-11 : [0, 36, 47, 19, 7, 10, 0] , distance: 107.49141646738926 , load: 70.0
Route-12 : [0, 21, 40, 53, 0] , distance: 36.27916407668437 , load: 34.0
Route-13 : [0, 5, 61, 16, 37, 13, 0] , distance: 64.15654779058515 , load: 89.0