关于 route-optimization
面向配送、取货和现场服务运营的路线优化技能,帮助规划车辆路线并处理容量、时间窗和多仓等约束。内容涵盖经典 VRP 类型、启发式及实时插单场景。
主要功能
- 01识别 TSP 与多种 VRP 问题
- 02考虑容量、时间窗和班次约束
- 03介绍精确、启发式及元启发式方法
- 04规划多仓与取送货路线
- 05处理执行期间的实时订单变更
适用场景
- 01规划有送达时间窗的多车配送
- 02按车辆容量分配客户站点
- 03为多个仓库划分服务区域
- 04在路线执行中加入紧急订单
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ kishorkukreja/ awesome-supply-chain --skill route-optimization安装后在 Claude Code 中输入/route-optimization确认已加载。
详细介绍
路线优化
你是交通路线优化和车辆路径问题方面的专家。你的目标是帮助设计最优配送路线,以降低成本、缩短行驶距离、提高服务水平并最大限度地提升车队利用率。
初步评估
优化路线前,请先了解:
-
业务背景
- 属于哪种运营类型?(配送、取货、现场服务)
- 车队规模和车辆类型?
- 当前的路线规划方式?(手动、基础软件、高级软件)
- 主要痛点?(成本、延迟配送、驾驶员加班)
-
运营约束
- 配送时间窗?(硬约束/软约束)
- 车辆容量(重量、体积、托盘数)?
- 驾驶员班次时长和休息要求?
- 每个停靠点的服务时间?
- 路线最长持续时间?
-
网络特征
- 每天的停靠点数量?
- 仓库位置(单个/多个)?
- 地理覆盖范围?(城市、乡村、混合区域)
- 交通模式和注意事项?
- 通行限制(卡车路线、高度限制)?
-
服务要求
- 准时配送目标?
- 客户优先级或偏好?
- 特殊处理需求?
- 实时变更和动态请求?
路线优化框架
问题分类
1. 旅行商问题(TSP)
- 单辆车,访问所有地点一次
- 返回起点
- 最小化总距离/时间
- 适用场景:小规模配送、服务路线
2. 车辆路径问题(VRP)
- 多辆车从仓库出发
- 每位客户仅访问一次
- 容量约束
- 最小化车队总行驶距离/成本
3. 带时间窗的车辆路径问题(VRPTW)
- 客户有配送时间窗
- 硬约束(必须在时间窗内到达)
- 软约束(偏好要求,违反时需承担惩罚)
- 最常见的现实场景变体
4. 容量约束车辆路径问题(CVRP)
- 车辆容量限制(重量/体积)
- 路线中不能超出容量
- 可能需要往返运输
5. 多仓库车辆路径问题(MDVRP)
- 多个出发地点
- 将客户分配到各个仓库
- 优化仓库选择和路线
6. 带取货和配送的车辆路径问题(VRPPD)
- 在停靠点进行取货和配送
- 有先后顺序约束
- 同时进行或依次进行
7. 动态车辆路径问题
- 实时插入订单
- 在当天重新优化路线
- 处理紧急请求
优化方法
精确方法
分支定界法
- 保证得到最优解
- 仅适用于小规模问题(<50 个停靠点)
- 时间复杂度呈指数级
混合整数规划(MIP)
- 数学优化模型
- 商业求解器(Gurobi、CPLEX)
from pulp import *
import numpy as np
def vrp_mip_model(distances, demands, vehicle_capacity, num_vehicles):
"""
Basic VRP using Mixed-Integer Programming
Parameters:
- distances: NxN distance matrix
- demands: list of demands at each location
- vehicle_capacity: max capacity per vehicle
- num_vehicles: number of available vehicles
"""
n = len(distances) # includes depot at index 0
customers = range(1, n)
all_nodes = range(n)
vehicles = range(num_vehicles)
# Create problem
prob = LpProblem("VRP", LpMinimize)
# Decision variables
# x[i,j,k] = 1 if vehicle k travels from i to j
x = LpVariable.dicts("route",
[(i,j,k) for i in all_nodes
for j in all_nodes
for k in vehicles if i != j],
cat='Binary')
# u[i,k] = position of node i in route of vehicle k (for subtour elimination)
u = LpVariable.dicts("order",
[(i,k) for i in customers for k in vehicles],
lowBound=1,
upBound=n-1,
cat='Integer')
# Objective: minimize total distance
prob += lpSum([distances[i][j] * x[i,j,k]
for i in all_nodes
for j in all_nodes
for k in vehicles
if i != j])
# Constraints
# 1. Each customer visited exactly once
for j in customers:
prob += lpSum([x[i,j,k]
for i in all_nodes
for k in vehicles
if i != j]) == 1
# 2. Flow conservation: enter and leave each node
for k in vehicles:
for j in all_nodes:
prob += (lpSum([x[i,j,k] for i in all_nodes if i != j]) ==
lpSum([x[j,i,k] for i in all_nodes if i != j]))
# 3. Each vehicle starts from depot
for k in vehicles:
prob += lpSum([x[0,j,k] for j in customers]) <= 1
# 4. Each vehicle returns to depot
for k in vehicles:
prob += lpSum([x[i,0,k] for i in customers]) <= 1
# 5. Vehicle capacity constraint
for k in vehicles:
prob += lpSum([demands[j] * x[i,j,k]
for i in all_nodes
for j in customers
if i != j]) <= vehicle_capacity
# 6. Subtour elimination (Miller-Tucker-Zemlin)
for i in customers:
for j in customers:
for k in vehicles:
if i != j:
prob += u[i,k] - u[j,k] + n * x[i,j,k] <= n - 1
# Solve
prob.solve(PULP_CBC_CMD(msg=1))
# Extract routes
routes = extract_routes(x, vehicles, n)
return {
'status': LpStatus[prob.status],
'total_distance': value(prob.objective),
'routes': routes
}
def extract_routes(x, vehicles, n):
"""Extract route sequences from solution"""
routes = []
for k in vehicles:
route = [0] # start at depot
current = 0
while True:
for j in range(n):
if j != current and (current,j,k) in x:
if x[current,j,k].varValue > 0.5:
if j == 0: # back to depot
route.append(0)
if len(route) > 2: # non-empty route
routes.append(route)
current = -1
break
else:
route.append(j)
current = j
break
if current == -1 or current == 0:
break
return routes
启发式方法
最近邻法
- 从仓库出发,每次前往最近的未访问客户
- 速度快,但通常不是最优解(比最优解差 10-30%)
import numpy as np
def nearest_neighbor_vrp(distance_matrix, demands, capacity, depot=0):
"""
Nearest neighbor heuristic for VRP
Simple construction heuristic - builds routes by always
selecting the nearest unvisited customer
"""
n = len(distance_matrix)
unvisited = set(range(1, n)) # exclude depot
routes = []
while unvisited:
route = [depot]
current_load = 0
current_location = depot
while unvisited:
# Find nearest feasible customer
nearest = None
nearest_dist = float('inf')
for customer in unvisited:
if current_load + demands[customer] <= capacity:
dist = distance_matrix[current_location][customer]
if dist < nearest_dist:
nearest = customer
nearest_dist = dist
if nearest is None:
break # No feasible customer, return to depot
route.append(nearest)
current_load += demands[nearest]
current_location = nearest
unvisited.remove(nearest)
route.append(depot) # return to depot
routes.append(route)
total_distance = sum(calculate_route_distance(route, distance_matrix)
for route in routes)
return {
'routes': routes,
'total_distance': total_distance,
'num_vehicles': len(routes)
}
def calculate_route_distance(route, distance_matrix):
"""Calculate total distance of a route"""
distance = 0
for i in range(len(route) - 1):
distance += distance_matrix[route[i]][route[i+1]]
return distance
节约算法(Clarke-Wright)
- 根据合并路线带来的节约合并路线
- 行业标准启发式算法
- 兼顾速度和质量
def clarke_wright_savings(distance_matrix, demands, capacity):
"""
Clarke-Wright Savings Algorithm for VRP
Starts with individual routes to each customer,
then merges routes based on savings
"""
n = len(distance_matrix)
depot = 0
# Calculate savings for all customer pairs
savings = []
for i in range(1, n):
for j in range(i+1, n):
saving = (distance_matrix[depot][i] +
distance_matrix[depot][j] -
distance_matrix[i][j])
savings.append((saving, i, j))
# Sort savings in descending order
savings.sort(reverse=True, key=lambda x: x[0])
# Initialize: each customer has its own route
routes = {i: [depot, i, depot] for i in range(1, n)}
route_loads = {i: demands[i] for i in range(1, n)}
# Try to merge routes based on savings
for saving_value, i, j in savings:
# Find routes containing i and j
route_i = None
route_j = None
for route_id, route in routes.items():
if i in route:
route_i = route_id
if j in route:
route_j = route_id
# Can only merge if i and j are in different routes
if route_i != route_j:
route_i_obj = routes[route_i]
route_j_obj = routes[route_j]
# Check if merging is feasible
combined_load = route_loads[route_i] + route_loads[route_j]
if combined_load <= capacity:
# Check if i and j are at ends of their routes
i_at_end = (route_i_obj[1] == i or route_i_obj[-2] == i)
j_at_end = (route_j_obj[1] == j or route_j_obj[-2] == j)
if i_at_end and j_at_end:
# Merge routes
new_route = merge_routes(route_i_obj, route_j_obj, i, j)
# Update routes
routes[route_i] = new_route
route_loads[route_i] = combined_load
del routes[route_j]
del route_loads[route_j]
return {
'routes': list(routes.values()),
'total_distance': sum(calculate_route_distance(route, distance_matrix)
for route in routes.values()),
'num_vehicles': len(routes)
}
def merge_routes(route1, route2, i, j):
"""Merge two routes through nodes i and j"""
# Remove depot endpoints
r1 = route1[1:-1]
r2 = route2[1:-1]
# Orient routes so i and j connect properly
if r1[-1] == i and r2[0] == j:
merged = [0] + r1 + r2 + [0]
elif r1[-1] == i and r2[-1] == j:
merged = [0] + r1 + r2[::-1] + [0]
elif r1[0] == i and r2[0] == j:
merged = [0] + r1[::-1] + r2 + [0]
elif r1[0] == i and r2[-1] == j:
merged = [0] + r2 + r1 + [0]
else:
merged = [0] + r1 + r2 + [0] # fallback
return merged
扫描算法
- 按客户相对于仓库的角度排序
- 绕仓库扫描并构建路线
- 适用于客户集中分布的情况
import math
def sweep_algorithm(coordinates, demands, capacity, depot_idx=0):
"""
Sweep Algorithm for VRP
Sorts customers by polar angle from depot,
then builds routes by sweeping clockwise
"""
depot = coordinates[depot_idx]
n = len(coordinates)
# Calculate angles from depot
customers = []
for i in range(n):
if i != depot_idx:
dx = coordinates[i][0] - depot[0]
dy = coordinates[i][1] - depot[1]
angle = math.atan2(dy, dx)
customers.append((i, angle, demands[i]))
# Sort by angle
customers.sort(key=lambda x: x[1])
# Build routes by sweeping
routes = []
current_route = [depot_idx]
current_load = 0
for customer_id, angle, demand in customers:
if current_load + demand <= capacity:
current_route.append(customer_id)
current_load += demand
else:
# Start new route
current_route.append(depot_idx)
routes.append(current_route)
current_route = [depot_idx, customer_id]
current_load = demand
# Add final route
if len(current_route) > 1:
current_route.append(depot_idx)
routes.append(current_route)
return {
'routes': routes,
'num_vehicles': len(routes)
}
元启发式方法
模拟退火
- 以一定概率接受较差的解
- 跳出局部最优
- 逐步降低“温度”
import random
import math
def simulated_annealing_vrp(initial_solution, distance_matrix,
demands, capacity,
initial_temp=1000, cooling_rate=0.995,
iterations=10000):
"""
Simulated Annealing for VRP
Improves initial solution through iterative neighbor search
with probabilistic acceptance of worse solutions
"""
current_solution = initial_solution.copy()
current_cost = calculate_total_distance(current_solution, distance_matrix)
best_solution = current_solution.copy()
best_cost = current_cost
temperature = initial_temp
for iteration in range(iterations):
# Generate neighbor solution
neighbor = generate_neighbor(current_solution, demands, capacity)
neighbor_cost = calculate_total_distance(neighbor, distance_matrix)
# Calculate acceptance probability
delta = neighbor_cost - current_cost
if delta < 0:
# Better solution, always accept
accept = True
else:
# Worse solution, accept with probability
accept_prob = math.exp(-delta / temperature)
accept = random.random() < accept_prob
if accept:
current_solution = neighbor
current_cost = neighbor_cost
if current_cost < best_cost:
best_solution = current_solution.copy()
best_cost = current_cost
# Cool down
temperature *= cooling_rate
return {
'routes': best_solution,
'total_distance': best_cost
}
def generate_neighbor(routes, demands, capacity):
"""Generate neighbor solution using local search operators"""
neighbor = [route.copy() for route in routes]
operator = random.choice(['swap', 'relocate', '2-opt'])
if operator == 'swap' and len(neighbor) >= 2:
# Swap customers between two routes
route1_idx = random.randint(0, len(neighbor) - 1)
route2_idx = random.randint(0, len(neighbor) - 1)
while route2_idx == route1_idx:
route2_idx = random.randint(0, len(neighbor) - 1)
route1 = neighbor[route1_idx]
route2 = neighbor[route2_idx]
if len(route1) > 2 and len(route2) > 2:
pos1 = random.randint(1, len(route1) - 2)
pos2 = random.randint(1, len(route2) - 2)
route1[pos1], route2[pos2] = route2[pos2], route1[pos1]
elif operator == 'relocate':
# Move customer from one route to another
if len(neighbor) >= 2:
route1_idx = random.randint(0, len(neighbor) - 1)
route2_idx = random.randint(0, len(neighbor) - 1)
while route2_idx == route1_idx:
route2_idx = random.randint(0, len(neighbor) - 1)
route1 = neighbor[route1_idx]
route2 = neighbor[route2_idx]
if len(route1) > 2:
pos1 = random.randint(1, len(route1) - 2)
customer = route1.pop(pos1)
pos2 = random.randint(1, len(route2) - 1)
route2.insert(pos2, customer)
elif operator == '2-opt':
# 2-opt within a single route
route_idx = random.randint(0, len(neighbor) - 1)
route = neighbor[route_idx]
if len(route) > 4:
i = random.randint(1, len(route) - 3)
j = random.randint(i + 1, len(route) - 2)
route[i:j+1] = reversed(route[i:j+1])
return neighbor
def calculate_total_distance(routes, distance_matrix):
"""Calculate total distance across all routes"""
total = 0
for route in routes:
for i in range(len(route) - 1):
total += distance_matrix[route[i]][route[i+1]]
return total
遗传算法
- 基于种群的搜索
- 交叉和变异算子
- 适用于大型问题
蚁群优化(ACO)
- 以概率方式构建解
- 信息素路径引导搜索
- 可获得高质量解
大邻域搜索(LNS)
- 移除并修复解的部分内容
- 对大型问题非常有效
高级 VRP 变体
带时间窗的车辆路径问题(VRPTW)
def vrptw_earliest_start_heuristic(customers, vehicles, time_windows, service_times, travel_times):
"""
VRPTW heuristic: insert customers based on earliest feasible start time
Parameters:
- customers: list of customer IDs
- vehicles: list of vehicle objects
- time_windows: dict {customer_id: (earliest, latest)}
- service_times: dict {customer_id: service_duration}
- travel_times: function(from_loc, to_loc) -> travel_time
"""
routes = []
unserved = set(customers)
for vehicle in vehicles:
if not unserved:
break
route = [vehicle.depot]
current_time = 0
current_location = vehicle.depot
route_load = 0
while unserved:
# Find best customer to insert
best_customer = None
best_start_time = float('inf')
for customer in unserved:
# Check capacity
if route_load + customer.demand > vehicle.capacity:
continue
# Calculate arrival time
travel_time = travel_times(current_location, customer.location)
arrival_time = current_time + travel_time
# Check time window feasibility
earliest, latest = time_windows[customer.id]
if arrival_time > latest:
continue # Too late
start_time = max(arrival_time, earliest)
if start_time < best_start_time:
best_start_time = start_time
best_customer = customer
if best_customer is None:
break # No feasible customer
# Add customer to route
route.append(best_customer)
unserved.remove(best_customer)
travel_time = travel_times(current_location, best_customer.location)
arrival_time = current_time + travel_time
earliest, latest = time_windows[best_customer.id]
current_time = max(arrival_time, earliest) + service_times[best_customer.id]
current_location = best_customer.location
route_load += best_customer.demand
route.append(vehicle.depot)
routes.append(route)
return routes
多仓库车辆路径问题
def assign_customers_to_depots(customers, depots, distance_matrix):
"""
Assign customers to nearest depot for MDVRP
Returns clusters of customers for each depot
"""
depot_clusters = {depot: [] for depot in depots}
for customer in customers:
# Find nearest depot
nearest_depot = None
min_distance = float('inf')
for depot in depots:
dist = distance_matrix[customer][depot]
if dist < min_distance:
min_distance = dist
nearest_depot = depot
depot_clusters[nearest_depot].append(customer)
return depot_clusters
def solve_mdvrp(customers, depots, distance_matrix, demands, capacity):
"""
Solve Multi-Depot VRP
1. Assign customers to depots
2. Solve VRP for each depot independently
"""
# Assign customers to depots
clusters = assign_customers_to_depots(customers, depots, distance_matrix)
all_routes = []
total_distance = 0
for depot, depot_customers in clusters.items():
if not depot_customers:
continue
# Solve VRP for this depot
depot_solution = clarke_wright_savings(
distance_matrix,
demands,
capacity
)
all_routes.extend(depot_solution['routes'])
total_distance += depot_solution['total_distance']
return {
'routes': all_routes,
'total_distance': total_distance,
'depot_clusters': clusters
}
动态车辆路径问题(实时路线规划)
class DynamicVRPSolver:
"""
Dynamic VRP solver for real-time order insertions
Handles new orders arriving during route execution
"""
def __init__(self, initial_routes, distance_matrix):
self.routes = initial_routes
self.distance_matrix = distance_matrix
self.completed_stops = set()
def insert_urgent_order(self, new_customer, current_vehicle_positions):
"""
Insert urgent order into existing routes
Uses cheapest insertion heuristic
"""
best_route_idx = None
best_position = None
min_cost_increase = float('inf')
for route_idx, route in enumerate(self.routes):
# Get current position of vehicle on this route
current_pos = current_vehicle_positions.get(route_idx, 0)
# Try inserting at each position after current
for insert_pos in range(current_pos + 1, len(route)):
# Calculate cost increase
prev_customer = route[insert_pos - 1]
next_customer = route[insert_pos]
current_cost = self.distance_matrix[prev_customer][next_customer]
new_cost = (self.distance_matrix[prev_customer][new_customer] +
self.distance_matrix[new_customer][next_customer])
cost_increase = new_cost - current_cost
if cost_increase < min_cost_increase:
min_cost_increase = cost_increase
best_route_idx = route_idx
best_position = insert_pos
if best_route_idx is not None:
self.routes[best_route_idx].insert(best_position, new_customer)
return {
'success': True,
'route': best_route_idx,
'position': best_position,
'cost_increase': min_cost_increase
}
else:
# Need new route
self.routes.append([0, new_customer, 0])
return {
'success': True,
'route': len(self.routes) - 1,
'position': 1,
'new_route': True
}
def mark_completed(self, customer):
"""Mark customer as completed"""
self.completed_stops.add(customer)
def reoptimize_remaining(self):
"""Re-optimize remaining stops on all routes"""
for route in self.routes:
# Remove completed stops
route[:] = [stop for stop in route
if stop not in self.completed_stops or stop == 0]
# Remove empty routes
self.routes = [route for route in self.routes if len(route) > 2]
实际实现
完整的 VRP 求解器类
import numpy as np
from scipy.spatial.distance import cdist
import matplotlib.pyplot as plt
class VRPSolver:
"""
Comprehensive VRP solver with multiple algorithms
"""
def __init__(self, coordinates, demands, vehicle_capacity,
num_vehicles=None, depot_idx=0):
"""
Initialize VRP solver
Parameters:
- coordinates: list of (x, y) tuples for locations
- demands: list of demand at each location
- vehicle_capacity: maximum capacity per vehicle
- num_vehicles: max vehicles (None = unlimited)
- depot_idx: index of depot location (default 0)
"""
self.coordinates = np.array(coordinates)
self.demands = np.array(demands)
self.vehicle_capacity = vehicle_capacity
self.num_vehicles = num_vehicles
self.depot_idx = depot_idx
# Calculate distance matrix
self.distance_matrix = cdist(self.coordinates, self.coordinates,
metric='euclidean')
self.best_solution = None
self.best_cost = float('inf')
def solve(self, method='clarke_wright', **kwargs):
"""
Solve VRP using specified method
Methods:
- 'nearest_neighbor': Fast, simple heuristic
- 'clarke_wright': Savings algorithm
- 'sweep': Angular sweep from depot
- 'genetic': Genetic algorithm (slower, better quality)
"""
if method == 'nearest_neighbor':
solution = nearest_neighbor_vrp(
self.distance_matrix,
self.demands,
self.vehicle_capacity,
self.depot_idx
)
elif method == 'clarke_wright':
solution = clarke_wright_savings(
self.distance_matrix,
self.demands,
self.vehicle_capacity
)
elif method == 'sweep':
solution = sweep_algorithm(
self.coordinates,
self.demands,
self.vehicle_capacity,
self.depot_idx
)
else:
raise ValueError(f"Unknown method: {method}")
# Improve solution with local search
if kwargs.get('improve', True):
solution = self.local_search_improvement(solution['routes'])
self.best_solution = solution
self.best_cost = solution['total_distance']
return solution
def local_search_improvement(self, routes, iterations=1000):
"""
Improve solution using 2-opt and relocate operators
"""
current_routes = [route.copy() for route in routes]
current_cost = calculate_total_distance(current_routes, self.distance_matrix)
improved = True
iter_count = 0
while improved and iter_count < iterations:
improved = False
iter_count += 1
# Try 2-opt within each route
for route_idx, route in enumerate(current_routes):
if len(route) < 4:
continue
for i in range(1, len(route) - 2):
for j in range(i + 1, len(route) - 1):
# Try reversing segment
new_route = route.copy()
new_route[i:j+1] = reversed(route[i:j+1])
# Check if improvement
old_dist = calculate_route_distance(route, self.distance_matrix)
new_dist = calculate_route_distance(new_route, self.distance_matrix)
if new_dist < old_dist:
current_routes[route_idx] = new_route
current_cost = current_cost - old_dist + new_dist
improved = True
break
if improved:
break
return {
'routes': current_routes,
'total_distance': current_cost,
'num_vehicles': len(current_routes)
}
def visualize(self, solution=None, save_path=None):
"""
Visualize routes on a map
"""
if solution is None:
solution = self.best_solution
if solution is None:
raise ValueError("No solution to visualize")
plt.figure(figsize=(12, 8))
# Plot depot
depot = self.coordinates[self.depot_idx]
plt.plot(depot[0], depot[1], 'rs', markersize=15, label='Depot')
# Plot routes with different colors
colors = plt.cm.tab20(np.linspace(0, 1, len(solution['routes'])))
for idx, route in enumerate(solution['routes']):
route_coords = self.coordinates[route]
plt.plot(route_coords[:, 0], route_coords[:, 1],
'o-', color=colors[idx], linewidth=2, markersize=8,
label=f'Route {idx+1}')
plt.xlabel('X Coordinate')
plt.ylabel('Y Coordinate')
plt.title(f'VRP Solution - Total Distance: {solution["total_distance"]:.2f}')
plt.legend()
plt.grid(True, alpha=0.3)
if save_path:
plt.savefig(save_path, dpi=300, bbox_inches='tight')
plt.show()
def get_route_statistics(self, solution=None):
"""
Calculate detailed statistics for solution
"""
if solution is None:
solution = self.best_solution
stats = {
'num_vehicles': len(solution['routes']),
'total_distance': solution['total_distance'],
'routes': []
}
for idx, route in enumerate(solution['routes']):
route_load = sum(self.demands[i] for i in route if i != self.depot_idx)
route_distance = calculate_route_distance(route, self.distance_matrix)
num_stops = len([i for i in route if i != self.depot_idx])
stats['routes'].append({
'route_id': idx + 1,
'num_stops': num_stops,
'total_load': route_load,
'utilization': route_load / self.vehicle_capacity * 100,
'distance': route_distance,
'sequence': route
})
return stats
# Example usage
if __name__ == "__main__":
# Sample problem: 20 customers
np.random.seed(42)
num_customers = 20
coordinates = np.random.rand(num_customers + 1, 2) * 100
coordinates[0] = [50, 50] # Depot in center
demands = np.random.randint(5, 20, num_customers + 1)
demands[0] = 0 # Depot has no demand
vehicle_capacity = 50
# Solve
solver = VRPSolver(coordinates, demands, vehicle_capacity)
solution = solver.solve(method='clarke_wright', improve=True)
# Get statistics
stats = solver.get_route_statistics()
print(f"Solution found:")
print(f"Number of vehicles: {stats['num_vehicles']}")
print(f"Total distance: {stats['total_distance']:.2f}")
print("\nRoute details:")
for route_stat in stats['routes']:
print(f" Route {route_stat['route_id']}: "
f"{route_stat['num_stops']} stops, "
f"Load: {route_stat['total_load']} "
f"({route_stat['utilization']:.1f}% utilization), "
f"Distance: {route_stat['distance']:.2f}")
# Visualize
solver.visualize()
商业路线规划软件
主流平台
Omnitracs / Roadnet
- 路线优化的行业标准
- 实时追踪集成
- 动态重新规划路线
Descartes Route Planner
- 多仓库路径规划
- 时间窗优化
- 区域管理
WorkWave Route Manager
- 面向中小企业
- 司机移动应用
- GPS 追踪
OptimoRoute
- 基于云的路径规划
- 实时订单录入
- 司机移动应用
Route4Me
- 多停靠点路线优化
- 区域优化
- 价格实惠
Verizon Connect (Networkfleet)
- 远程信息处理集成
- 车队管理功能
Teletrac Navman
- 路线优化 + 远程信息处理
- 合规追踪
Python 库与工具
OR-Tools (Google)
from ortools.constraint_solver import routing_enums_pb2
from ortools.constraint_solver import pywrapcp
def solve_vrp_ortools(distance_matrix, num_vehicles, depot=0):
"""Solve VRP using Google OR-Tools"""
# Create routing index manager
manager = pywrapcp.RoutingIndexManager(
len(distance_matrix),
num_vehicles,
depot
)
# Create routing model
routing = pywrapcp.RoutingModel(manager)
# Create distance callback
def distance_callback(from_index, to_index):
from_node = manager.IndexToNode(from_index)
to_node = manager.IndexToNode(to_index)
return int(distance_matrix[from_node][to_node])
transit_callback_index = routing.RegisterTransitCallback(distance_callback)
routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)
# Set search parameters
search_parameters = pywrapcp.DefaultRoutingSearchParameters()
search_parameters.first_solution_strategy = (
routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC
)
# Solve
solution = routing.SolveWithParameters(search_parameters)
# Extract routes
routes = []
for vehicle_id in range(num_vehicles):
index = routing.Start(vehicle_id)
route = []
while not routing.IsEnd(index):
route.append(manager.IndexToNode(index))
index = solution.Value(routing.NextVar(index))
route.append(manager.IndexToNode(index))
routes.append(route)
return {
'routes': routes,
'total_distance': solution.ObjectiveValue()
}
Python VRP 库
python-tsp:TSP 求解器vrpy:使用列生成的 VRPscipy.optimize:通用优化networkx:图算法geopy:距离计算
常见挑战与解决方案
挑战:时间窗过于紧迫
问题:
- 不存在可行解
- 大量配送延迟
解决方案:
- 与客户协商,放宽时间窗
- 增加车辆
- 将较长的时间窗拆分为优选时间和可接受时间
- 使用带惩罚项的软时间窗
- 提前开始路线
- 考虑客户优先级(A/B/C)
挑战:路线不均衡
问题:
- 部分车辆负载过重,其他车辆利用不足
- 部分路线很长,其他路线很短
解决方案:
- 使用后优化均衡
- 设置每条路线的停靠点数量上下限约束
- 调整仓库服务区域
- 使用路线时长上限约束
- 按收入或优先级进行均衡
挑战:实时变化
问题:
- 当天有新订单送达
- 客户取消订单
- 交通延误
解决方案:
- 为动态订单预留运力(10-20%)
- 每小时进行动态重新优化
- 实施分级服务(当日送达溢价服务)
- 司机通信系统
- 通过地理围栏触发重新规划路线
挑战:司机执行情况
问题:
- 司机不遵循最优路线
- 未经授权停车休息
- 跳过停靠点
解决方案:
- 提供带逐向导航的司机移动应用
- 游戏化机制(效率评分、奖金)
- 异常报告与辅导
- 使用地理围栏进行 GPS 追踪
- 让司机参与规划(利用本地知识)
挑战:交通状况多变
问题:
- 静态路线未考虑交通状况
- 配送持续延迟
解决方案:
- 使用随时间变化的行驶时间
- 集成历史交通数据
- 实时交通 API(Google、HERE)
- 在时间窗中增加缓冲时间
- 当天动态重新规划路线
挑战:混合车队
问题:
- 车辆类型不同(卡车、厢式货车、自行车)
- 载货能力和成本不同
解决方案:
- 将其建模为异构车队 VRP
- 为每条路线分配车辆类型
- 按车辆类型计算每英里成本
- 优先使用较小或成本较低的车辆
- 考虑车辆与客户的兼容性
输出格式
路线优化报告
执行摘要:
- 路线总数:12
- 总距离:845 英里(与当前相比减少 15%)
- 总耗时:96 小时
- 车辆利用率:平均 87%
- 准时送达率:98%(当前为 89%)
路线详情:
| 路线 | 司机 | 停靠点 | 距离 | 时长 | 载重 | 利用率 | 出发时间 |
|---|---|---|---|---|---|---|---|
| R1 | Smith | 18 | 72 mi | 8.5 hrs | 1,850 lbs | 93% | 7:00 AM |
| R2 | Jones | 15 | 65 mi | 7.8 hrs | 1,720 lbs | 86% | 7:00 AM |
| R3 | Brown | 22 | 83 mi | 9.2 hrs | 1,980 lbs | 99% | 6:30 AM |
停靠顺序(路线 R1 示例):
- 仓库 - 7:00 AM
- 客户 A(123 Main St)- 7:25 AM(预计到达时间:7:20-7:40)
- 客户 B(456 Oak Ave)- 7:52 AM(预计到达时间:7:45-8:00)
- 客户 C(789 Elm St)- 8:18 AM(预计到达时间:8:00-8:30) ...
- 客户 R(321 Pine Rd)- 3:45 PM(预计到达时间:3:30-4:00)
- 返回仓库 - 4:15 PM
绩效指标:
| 指标 | 当前 | 优化后 | 改进幅度 |
|---|---|---|---|
| 总里程 | 995 | 845 | -15% |
| 工时 | 112 | 96 | -14% |
| 使用车辆数 | 14 | 12 | -14% |
| 平均每条路线停靠点数 | 15 | 18 | +20% |
| 燃油成本 | $1,990 | $1,690 | -15% |
| 延迟送达率 | 11% | 2% | -82% |
建议:
- 实施路线优化软件
- 为司机提供移动应用
- 建立动态重新优化流程
- 监控并调整时间窗
- 考虑在高峰日增加 1 辆车
要询问的问题
如果需要更多背景信息:
- 每天有多少个停靠点?有多少辆车?
- 是否有配送时间窗?是硬性约束还是软性约束?
- 车辆的载重上限是多少?
- 是单个仓库还是多个仓库?
- 是否有特殊约束(司机班次、通行限制)?
- 需要取件、配送,还是两者都需要?
- 需要实时变更还是静态规划?
- 当前的路线规划流程是什么?
相关技能
- vehicle-routing-problem:VRP 数学模型及其变体
- fleet-management:车辆规模、维护和车队运营
- last-mile-delivery:城市配送专用优化
- network-design:仓库选址和网络结构
- freight-optimization:长途运输优化
- capacitated-vrp:侧重容量的 VRP 建模
- vrp-time-windows:时间窗约束建模
- traveling-salesman-problem:单车辆路线规划
- pickup-delivery-problem:取件和配送路线规划
评论
支持 Markdown