vrp-backhauls
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseVehicle Routing Problem with Backhauls (VRPB)
带回程的车辆路径问题(VRPB)
You are an expert in the Vehicle Routing Problem with Backhauls and reverse logistics optimization. Your goal is to help design efficient routes where vehicles make deliveries first (linehauls) and then pick up goods on the return trip (backhauls), maximizing vehicle utilization and minimizing empty miles.
您是带回程的车辆路径问题(VRPB)和逆向物流优化领域的专家。您的目标是帮助设计高效路线,让车辆先完成配送任务(干线运输,linehaul),再在返程途中完成取货任务(回程运输,backhaul),最大限度提高车辆利用率并减少空驶里程。
Initial Assessment
初始评估
Before solving VRPB instances, understand:
-
Backhaul Characteristics
- Strict sequence (all deliveries before all pickups)?
- Mixed linehauls and backhauls allowed?
- Can a customer have both delivery AND pickup?
-
Capacity Considerations
- Same vehicle capacity for deliveries and pickups?
- How does capacity work? (delivery reduces load, pickup increases)
- Can vehicle be fully loaded with pickups after emptying deliveries?
-
Customer Types
- Linehaul customers (delivery only)
- Backhaul customers (pickup only)
- Mixed customers (both delivery and pickup)
- Number of each type?
-
Business Context
- Return of empty containers/pallets?
- Reverse logistics (returns, recycling)?
- Supply redistribution between locations?
- Waste collection after deliveries?
-
Problem Scale
- Small (< 50 customers): Exact methods possible
- Medium (50-200): Advanced heuristics
- Large (200+): Metaheuristics required
在解决VRPB实例之前,需要了解以下信息:
-
回程运输特征
- 是否严格遵循先完成所有配送、再进行取货的顺序?
- 是否允许混合干线运输与回程运输任务?
- 同一客户是否可能同时有配送和取货需求?
-
容量考量
- 车辆的配送容量与取货容量是否相同?
- 容量规则是怎样的?(配送会减少负载,取货会增加负载)
- 完成配送清空车辆后,能否装满取货货物?
-
客户类型
- 干线运输客户(仅需配送)
- 回程运输客户(仅需取货)
- 混合需求客户(同时有配送和取货需求)
- 各类客户的数量是多少?
-
业务场景
- 是否涉及空容器/托盘的回收?
- 是否属于逆向物流(退货、回收)场景?
- 是否涉及不同地点之间的物资重新分配?
- 是否需要在配送完成后收集废弃物?
-
问题规模
- 小型(<50个客户):可使用精确解法
- 中型(50-200个客户):需使用高级启发式算法
- 大型(200+个客户):必须使用元启发式算法
Mathematical Formulation
数学模型
VRPB with Sequential Constraint
带顺序约束的VRPB
Sets:
- V = {0} ∪ L ∪ B: Nodes (0 = depot, L = linehaul, B = backhaul customers)
- K: Vehicles
Parameters:
- c_{ij}: Cost/distance from i to j
- d_i: Delivery quantity at linehaul customer i ∈ L
- p_j: Pickup quantity at backhaul customer j ∈ B
- Q: Vehicle capacity
Decision Variables:
- x_{ijk} ∈ {0,1}: 1 if vehicle k travels from i to j
- u_{ik} ∈ ℝ: Load on vehicle k after visiting node i
Objective:
Minimize: Σ_{k∈K} Σ_{i∈V} Σ_{j∈V} c_{ij} * x_{ijk}Constraints:
1. Each customer visited exactly once:
Σ_{k∈K} Σ_{i∈V, i≠j} x_{ijk} = 1, ∀j ∈ L ∪ B
2. Flow conservation:
Σ_{i∈V} x_{ihk} = Σ_{j∈V} x_{hjk}, ∀h ∈ V, ∀k ∈ K
3. Capacity constraint:
Σ_{i∈L} d_i * Σ_{j∈V} x_{ijk} ≤ Q, ∀k ∈ K (deliveries)
Σ_{j∈B} p_j * Σ_{i∈V} x_{ijjk} ≤ Q, ∀k ∈ K (pickups)
4. Load tracking:
Delivery phase: u_{jk} = u_{ik} - d_j (load decreases)
Pickup phase: u_{jk} = u_{ik} + p_j (load increases)
5. Precedence (all linehauls before backhauls):
If x_{ijk} = 1 and i ∈ L, j ∈ B, then
all linehaul customers must be visited before j
6. Subtour elimination
7. Binary variables:
x_{ijk} ∈ {0,1}集合:
- V = {0} ∪ L ∪ B:节点集合(0代表仓库,L代表干线运输客户,B代表回程运输客户)
- K:车辆集合
参数:
- c_{ij}:从节点i到节点j的成本/距离
- d_i:干线运输客户i ∈ L的配送量
- p_j:回程运输客户j ∈ B的取货量
- Q:车辆容量
决策变量:
- x_{ijk} ∈ {0,1}:若车辆k从i行驶到j则为1,否则为0
- u_{ik} ∈ ℝ:车辆k访问节点i后的负载量
目标函数:
Minimize: Σ_{k∈K} Σ_{i∈V} Σ_{j∈V} c_{ij} * x_{ijk}约束条件:
1. 每个客户仅被访问一次:
Σ_{k∈K} Σ_{i∈V, i≠j} x_{ijk} = 1, ∀j ∈ L ∪ B
2. 流量守恒:
Σ_{i∈V} x_{ihk} = Σ_{j∈V} x_{hjk}, ∀h ∈ V, ∀k ∈ K
3. 容量约束:
Σ_{i∈L} d_i * Σ_{j∈V} x_{ijk} ≤ Q, ∀k ∈ K (配送)
Σ_{j∈B} p_j * Σ_{i∈V} x_{ijjk} ≤ Q, ∀k ∈ K (取货)
4. 负载跟踪:
配送阶段:u_{jk} = u_{ik} - d_j(负载减少)
取货阶段:u_{jk} = u_{ik} + p_j(负载增加)
5. 优先级约束(所有干线运输任务先于回程运输任务):
若x_{ijk} = 1且i ∈ L, j ∈ B,则所有干线运输客户必须在访问j之前完成访问
6. 子回路消除
7. 二进制变量:
x_{ijk} ∈ {0,1}Classical Heuristics
经典启发式算法
1. Sequential Cluster-Route for VRPB
1. VRPB的顺序聚类-路线算法
python
import numpy as np
def vrpb_cluster_route(coordinates, linehaul_demands, backhaul_demands,
vehicle_capacity, num_vehicles, depot_idx=0):
"""
Cluster-then-route heuristic for VRPB
Phase 1: Cluster customers geographically
Phase 2: Within each cluster, sequence linehauls then backhauls
Phase 3: Optimize sequences
Args:
coordinates: all location coordinates
linehaul_demands: delivery demands (0 for backhaul-only customers)
backhaul_demands: pickup demands (0 for linehaul-only customers)
vehicle_capacity: vehicle capacity
num_vehicles: number of vehicles
depot_idx: depot index
Returns:
solution dictionary
"""
n = len(coordinates)
depot = coordinates[depot_idx]
# Identify customer types
linehaul_customers = [i for i in range(n)
if i != depot_idx and linehaul_demands[i] > 0]
backhaul_customers = [i for i in range(n)
if i != depot_idx and backhaul_demands[i] > 0]
print(f"Linehaul customers: {len(linehaul_customers)}")
print(f"Backhaul customers: {len(backhaul_customers)}")
# Distance matrix
dist_matrix = np.zeros((n, n))
for i in range(n):
for j in range(n):
dist_matrix[i][j] = np.linalg.norm(coordinates[i] - coordinates[j])
# Sweep algorithm to create clusters
import math
def polar_angle(point):
dx = point[0] - depot[0]
dy = point[1] - depot[1]
return math.atan2(dy, dx)
# Sort all customers by angle
all_customers = linehaul_customers + backhaul_customers
customer_angles = [(polar_angle(coordinates[c]), c) for c in all_customers]
customer_angles.sort()
# Build routes by sweeping
routes = []
current_route_linehauls = []
current_route_backhauls = []
current_linehaul_load = 0
current_backhaul_load = 0
for angle, customer in customer_angles:
is_linehaul = customer in linehaul_customers
if is_linehaul:
demand = linehaul_demands[customer]
# Check if adding this linehaul is feasible
if current_linehaul_load + demand <= vehicle_capacity:
current_route_linehauls.append(customer)
current_linehaul_load += demand
else:
# Start new route
if current_route_linehauls or current_route_backhauls:
routes.append({
'linehauls': current_route_linehauls,
'backhauls': current_route_backhauls
})
current_route_linehauls = [customer]
current_route_backhauls = []
current_linehaul_load = demand
current_backhaul_load = 0
else:
demand = backhaul_demands[customer]
# Check if adding this backhaul is feasible
if current_backhaul_load + demand <= vehicle_capacity:
current_route_backhauls.append(customer)
current_backhaul_load += demand
else:
# Check if we can start new route
if current_route_linehauls or current_route_backhauls:
routes.append({
'linehauls': current_route_linehauls,
'backhauls': current_route_backhauls
})
current_route_linehauls = []
current_route_backhauls = [customer]
current_linehaul_load = 0
current_backhaul_load = demand
# Add last route
if current_route_linehauls or current_route_backhauls:
routes.append({
'linehauls': current_route_linehauls,
'backhauls': current_route_backhauls
})
# Optimize each route with 2-opt (separately for linehauls and backhauls)
optimized_routes = []
for route in routes:
# Optimize linehaul sequence
if len(route['linehauls']) > 2:
linehaul_seq = [depot_idx] + route['linehauls']
linehaul_seq = two_opt_segment(linehaul_seq, dist_matrix)
route['linehauls'] = linehaul_seq[1:] # Remove depot
# Optimize backhaul sequence
if len(route['backhauls']) > 2:
backhaul_seq = route['backhauls'] + [depot_idx]
backhaul_seq = two_opt_segment(backhaul_seq, dist_matrix)
route['backhauls'] = backhaul_seq[:-1] # Remove depot
optimized_routes.append(route)
# Convert to full routes and calculate distance
full_routes = []
total_distance = 0
for route in optimized_routes:
full_route = [depot_idx]
full_route.extend(route['linehauls'])
full_route.extend(route['backhauls'])
full_route.append(depot_idx)
full_routes.append(full_route)
# Calculate distance
route_distance = sum(dist_matrix[full_route[i]][full_route[i+1]]
for i in range(len(full_route)-1))
total_distance += route_distance
return {
'routes': full_routes,
'route_details': optimized_routes,
'total_distance': total_distance,
'num_vehicles': len(full_routes)
}
def two_opt_segment(sequence, dist_matrix):
"""2-opt optimization for a sequence"""
improved = True
best = sequence.copy()
while improved:
improved = False
for i in range(len(best) - 2):
for j in range(i + 2, len(best)):
if j - i == 1:
continue
current_cost = (dist_matrix[best[i]][best[i+1]] +
dist_matrix[best[j-1]][best[j]])
new_cost = (dist_matrix[best[i]][best[j-1]] +
dist_matrix[best[i+1]][best[j]])
if new_cost < current_cost - 1e-10:
best[i+1:j] = reversed(best[i+1:j])
improved = True
break
if improved:
break
return bestpython
import numpy as np
def vrpb_cluster_route(coordinates, linehaul_demands, backhaul_demands,
vehicle_capacity, num_vehicles, depot_idx=0):
"""
Cluster-then-route heuristic for VRPB
Phase 1: Cluster customers geographically
Phase 2: Within each cluster, sequence linehauls then backhauls
Phase 3: Optimize sequences
Args:
coordinates: all location coordinates
linehaul_demands: delivery demands (0 for backhaul-only customers)
backhaul_demands: pickup demands (0 for linehaul-only customers)
vehicle_capacity: vehicle capacity
num_vehicles: number of vehicles
depot_idx: depot index
Returns:
solution dictionary
"""
n = len(coordinates)
depot = coordinates[depot_idx]
# Identify customer types
linehaul_customers = [i for i in range(n)
if i != depot_idx and linehaul_demands[i] > 0]
backhaul_customers = [i for i in range(n)
if i != depot_idx and backhaul_demands[i] > 0]
print(f"Linehaul customers: {len(linehaul_customers)}")
print(f"Backhaul customers: {len(backhaul_customers)}")
# Distance matrix
dist_matrix = np.zeros((n, n))
for i in range(n):
for j in range(n):
dist_matrix[i][j] = np.linalg.norm(coordinates[i] - coordinates[j])
# Sweep algorithm to create clusters
import math
def polar_angle(point):
dx = point[0] - depot[0]
dy = point[1] - depot[1]
return math.atan2(dy, dx)
# Sort all customers by angle
all_customers = linehaul_customers + backhaul_customers
customer_angles = [(polar_angle(coordinates[c]), c) for c in all_customers]
customer_angles.sort()
# Build routes by sweeping
routes = []
current_route_linehauls = []
current_route_backhauls = []
current_linehaul_load = 0
current_backhaul_load = 0
for angle, customer in customer_angles:
is_linehaul = customer in linehaul_customers
if is_linehaul:
demand = linehaul_demands[customer]
# Check if adding this linehaul is feasible
if current_linehaul_load + demand <= vehicle_capacity:
current_route_linehauls.append(customer)
current_linehaul_load += demand
else:
# Start new route
if current_route_linehauls or current_route_backhauls:
routes.append({
'linehauls': current_route_linehauls,
'backhauls': current_route_backhauls
})
current_route_linehauls = [customer]
current_route_backhauls = []
current_linehaul_load = demand
current_backhaul_load = 0
else:
demand = backhaul_demands[customer]
# Check if adding this backhaul is feasible
if current_backhaul_load + demand <= vehicle_capacity:
current_route_backhauls.append(customer)
current_backhaul_load += demand
else:
# Check if we can start new route
if current_route_linehauls or current_route_backhauls:
routes.append({
'linehauls': current_route_linehauls,
'backhauls': current_route_backhauls
})
current_route_linehauls = []
current_route_backhauls = [customer]
current_linehaul_load = 0
current_backhaul_load = demand
# Add last route
if current_route_linehauls or current_route_backhauls:
routes.append({
'linehauls': current_route_linehauls,
'backhauls': current_route_backhauls
})
# Optimize each route with 2-opt (separately for linehauls and backhauls)
optimized_routes = []
for route in routes:
# Optimize linehaul sequence
if len(route['linehauls']) > 2:
linehaul_seq = [depot_idx] + route['linehauls']
linehaul_seq = two_opt_segment(linehaul_seq, dist_matrix)
route['linehauls'] = linehaul_seq[1:] # Remove depot
# Optimize backhaul sequence
if len(route['backhauls']) > 2:
backhaul_seq = route['backhauls'] + [depot_idx]
backhaul_seq = two_opt_segment(backhaul_seq, dist_matrix)
route['backhauls'] = backhaul_seq[:-1] # Remove depot
optimized_routes.append(route)
# Convert to full routes and calculate distance
full_routes = []
total_distance = 0
for route in optimized_routes:
full_route = [depot_idx]
full_route.extend(route['linehauls'])
full_route.extend(route['backhauls'])
full_route.append(depot_idx)
full_routes.append(full_route)
# Calculate distance
route_distance = sum(dist_matrix[full_route[i]][full_route[i+1]]
for i in range(len(full_route)-1))
total_distance += route_distance
return {
'routes': full_routes,
'route_details': optimized_routes,
'total_distance': total_distance,
'num_vehicles': len(full_routes)
}
def two_opt_segment(sequence, dist_matrix):
"""2-opt optimization for a sequence"""
improved = True
best = sequence.copy()
while improved:
improved = False
for i in range(len(best) - 2):
for j in range(i + 2, len(best)):
if j - i == 1:
continue
current_cost = (dist_matrix[best[i]][best[i+1]] +
dist_matrix[best[j-1]][best[j]])
new_cost = (dist_matrix[best[i]][best[j-1]] +
dist_matrix[best[i+1]][best[j]])
if new_cost < current_cost - 1e-10:
best[i+1:j] = reversed(best[i+1:j])
improved = True
break
if improved:
break
return best2. VRPB with OR-Tools
2. 使用OR-Tools解决VRPB
python
from ortools.constraint_solver import routing_enums_pb2
from ortools.constraint_solver import pywrapcp
def solve_vrpb_ortools(coordinates, linehaul_demands, backhaul_demands,
vehicle_capacity, num_vehicles, depot=0,
time_limit=60):
"""
Solve VRPB using OR-Tools
Enforces that all linehauls are served before backhauls on each route
Args:
coordinates: location coordinates
linehaul_demands: delivery demands (0 if backhaul-only)
backhaul_demands: pickup demands (0 if linehaul-only)
vehicle_capacity: vehicle capacity
num_vehicles: number of vehicles
depot: depot index
time_limit: time limit
Returns:
solution dictionary
"""
import math
n = len(coordinates)
# Build distance matrix
dist_matrix = np.zeros((n, n))
for i in range(n):
for j in range(n):
dist_matrix[i][j] = math.sqrt(
(coordinates[i][0] - coordinates[j][0])**2 +
(coordinates[i][1] - coordinates[j][1])**2
)
# Identify customer types
linehaul_only = [i for i in range(n) if linehaul_demands[i] > 0
and backhaul_demands[i] == 0]
backhaul_only = [i for i in range(n) if backhaul_demands[i] > 0
and linehaul_demands[i] == 0]
mixed = [i for i in range(n) if linehaul_demands[i] > 0
and backhaul_demands[i] > 0]
# Create routing manager
manager = pywrapcp.RoutingIndexManager(n, num_vehicles, depot)
# Create routing model
routing = pywrapcp.RoutingModel(manager)
# Distance callback
def distance_callback(from_index, to_index):
from_node = manager.IndexToNode(from_index)
to_node = manager.IndexToNode(to_index)
return int(dist_matrix[from_node][to_node] * 100)
transit_callback_index = routing.RegisterTransitCallback(distance_callback)
routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)
# Add two capacity dimensions: one for deliveries, one for pickups
# Delivery capacity (starts full, decreases)
def delivery_demand_callback(from_index):
from_node = manager.IndexToNode(from_index)
return int(linehaul_demands[from_node])
delivery_callback_index = routing.RegisterUnaryTransitCallback(
delivery_demand_callback)
routing.AddDimension(
delivery_callback_index,
0, # no slack
int(vehicle_capacity), # maximum capacity
True, # start cumul to zero
'Delivery_Capacity')
# Pickup capacity (starts at 0, increases)
def pickup_demand_callback(from_index):
from_node = manager.IndexToNode(from_index)
return int(backhaul_demands[from_node])
pickup_callback_index = routing.RegisterUnaryTransitCallback(
pickup_demand_callback)
routing.AddDimension(
pickup_callback_index,
0, # no slack
int(vehicle_capacity), # maximum capacity
True, # start cumul to zero
'Pickup_Capacity')
# Add precedence constraints: linehauls before backhauls
# Use a counter dimension to enforce sequence
def counter_callback(from_index):
from_node = manager.IndexToNode(from_index)
# Linehaul customers get value 0, backhauls get value 1000
if from_node in backhaul_only:
return 1000
return 0
counter_callback_index = routing.RegisterUnaryTransitCallback(counter_callback)
routing.AddDimension(
counter_callback_index,
0, # no slack
10000, # large upper bound
True, # start cumul to zero
'Counter')
counter_dimension = routing.GetDimensionOrDie('Counter')
# Enforce that linehauls (counter ~0) come before backhauls (counter ~1000)
for backhaul_customer in backhaul_only:
index = manager.NodeToIndex(backhaul_customer)
counter_dimension.CumulVar(index).SetMin(500)
# Search parameters
search_parameters = pywrapcp.DefaultRoutingSearchParameters()
search_parameters.first_solution_strategy = (
routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)
search_parameters.local_search_metaheuristic = (
routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)
search_parameters.time_limit.seconds = time_limit
# Solve
solution = routing.SolveWithParameters(search_parameters)
if solution:
routes = []
total_distance = 0
for vehicle_id in range(num_vehicles):
index = routing.Start(vehicle_id)
route = []
linehaul_customers = []
backhaul_customers = []
while not routing.IsEnd(index):
node = manager.IndexToNode(index)
route.append(node)
if node in linehaul_only:
linehaul_customers.append(node)
elif node in backhaul_only:
backhaul_customers.append(node)
index = solution.Value(routing.NextVar(index))
route.append(manager.IndexToNode(index))
if len(route) > 2:
routes.append({
'route': route,
'linehauls': linehaul_customers,
'backhauls': backhaul_customers
})
# Calculate distance
route_distance = sum(dist_matrix[route[i]][route[i+1]]
for i in range(len(route)-1))
total_distance += route_distance
return {
'status': 'Optimal',
'routes': routes,
'total_distance': total_distance,
'num_vehicles': len(routes)
}
else:
return {
'status': 'No solution found',
'routes': None
}python
from ortools.constraint_solver import routing_enums_pb2
from ortools.constraint_solver import pywrapcp
def solve_vrpb_ortools(coordinates, linehaul_demands, backhaul_demands,
vehicle_capacity, num_vehicles, depot=0,
time_limit=60):
"""
Solve VRPB using OR-Tools
Enforces that all linehauls are served before backhauls on each route
Args:
coordinates: location coordinates
linehaul_demands: delivery demands (0 if backhaul-only)
backhaul_demands: pickup demands (0 if linehaul-only)
vehicle_capacity: vehicle capacity
num_vehicles: number of vehicles
depot: depot index
time_limit: time limit
Returns:
solution dictionary
"""
import math
n = len(coordinates)
# Build distance matrix
dist_matrix = np.zeros((n, n))
for i in range(n):
for j in range(n):
dist_matrix[i][j] = math.sqrt(
(coordinates[i][0] - coordinates[j][0])**2 +
(coordinates[i][1] - coordinates[j][1])**2
)
# Identify customer types
linehaul_only = [i for i in range(n) if linehaul_demands[i] > 0
and backhaul_demands[i] == 0]
backhaul_only = [i for i in range(n) if backhaul_demands[i] > 0
and linehaul_demands[i] == 0]
mixed = [i for i in range(n) if linehaul_demands[i] > 0
and backhaul_demands[i] > 0]
# Create routing manager
manager = pywrapcp.RoutingIndexManager(n, num_vehicles, depot)
# Create routing model
routing = pywrapcp.RoutingModel(manager)
# Distance callback
def distance_callback(from_index, to_index):
from_node = manager.IndexToNode(from_index)
to_node = manager.IndexToNode(to_index)
return int(dist_matrix[from_node][to_node] * 100)
transit_callback_index = routing.RegisterTransitCallback(distance_callback)
routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)
# Add two capacity dimensions: one for deliveries, one for pickups
# Delivery capacity (starts full, decreases)
def delivery_demand_callback(from_index):
from_node = manager.IndexToNode(from_index)
return int(linehaul_demands[from_node])
delivery_callback_index = routing.RegisterUnaryTransitCallback(
delivery_demand_callback)
routing.AddDimension(
delivery_callback_index,
0, # no slack
int(vehicle_capacity), # maximum capacity
True, # start cumul to zero
'Delivery_Capacity')
# Pickup capacity (starts at 0, increases)
def pickup_demand_callback(from_index):
from_node = manager.IndexToNode(from_index)
return int(backhaul_demands[from_node])
pickup_callback_index = routing.RegisterUnaryTransitCallback(
pickup_demand_callback)
routing.AddDimension(
pickup_callback_index,
0, # no slack
int(vehicle_capacity), # maximum capacity
True, # start cumul to zero
'Pickup_Capacity')
# Add precedence constraints: linehauls before backhauls
# Use a counter dimension to enforce sequence
def counter_callback(from_index):
from_node = manager.IndexToNode(from_index)
# Linehaul customers get value 0, backhauls get value 1000
if from_node in backhaul_only:
return 1000
return 0
counter_callback_index = routing.RegisterUnaryTransitCallback(counter_callback)
routing.AddDimension(
counter_callback_index,
0, # no slack
10000, # large upper bound
True, # start cumul to zero
'Counter')
counter_dimension = routing.GetDimensionOrDie('Counter')
# Enforce that linehauls (counter ~0) come before backhauls (counter ~1000)
for backhaul_customer in backhaul_only:
index = manager.NodeToIndex(backhaul_customer)
counter_dimension.CumulVar(index).SetMin(500)
# Search parameters
search_parameters = pywrapcp.DefaultRoutingSearchParameters()
search_parameters.first_solution_strategy = (
routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)
search_parameters.local_search_metaheuristic = (
routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)
search_parameters.time_limit.seconds = time_limit
# Solve
solution = routing.SolveWithParameters(search_parameters)
if solution:
routes = []
total_distance = 0
for vehicle_id in range(num_vehicles):
index = routing.Start(vehicle_id)
route = []
linehaul_customers = []
backhaul_customers = []
while not routing.IsEnd(index):
node = manager.IndexToNode(index)
route.append(node)
if node in linehaul_only:
linehaul_customers.append(node)
elif node in backhaul_only:
backhaul_customers.append(node)
index = solution.Value(routing.NextVar(index))
route.append(manager.IndexToNode(index))
if len(route) > 2:
routes.append({
'route': route,
'linehauls': linehaul_customers,
'backhauls': backhaul_customers
})
# Calculate distance
route_distance = sum(dist_matrix[route[i]][route[i+1]]
for i in range(len(route)-1))
total_distance += route_distance
return {
'status': 'Optimal',
'routes': routes,
'total_distance': total_distance,
'num_vehicles': len(routes)
}
else:
return {
'status': 'No solution found',
'routes': None
}Visualization
Visualization
def visualize_vrpb_solution(coordinates, routes, linehaul_demands,
backhaul_demands, save_path=None):
"""Visualize VRPB solution with linehauls and backhauls"""
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(12, 8))
colors = plt.cm.tab10(np.linspace(0, 1, len(routes)))
for idx, route_info in enumerate(routes):
route = route_info['route']
linehauls = route_info.get('linehauls', [])
backhauls = route_info.get('backhauls', [])
# Plot full route
route_coords = [coordinates[i] for i in route]
xs = [c[0] for c in route_coords]
ys = [c[1] for c in route_coords]
ax.plot(xs, ys, '-', color=colors[idx], linewidth=2, alpha=0.5)
# Mark linehaul customers (circles)
for customer in linehauls:
coord = coordinates[customer]
ax.plot(coord[0], coord[1], 'o', color=colors[idx],
markersize=12, markeredgecolor='black', markeredgewidth=1.5)
# Mark backhaul customers (triangles)
for customer in backhauls:
coord = coordinates[customer]
ax.plot(coord[0], coord[1], '^', color=colors[idx],
markersize=12, markeredgecolor='black', markeredgewidth=1.5)
# Plot depot
depot = coordinates[0]
ax.plot(depot[0], depot[1], 's', color='red', markersize=20,
label='Depot', markeredgecolor='black', markeredgewidth=2, zorder=10)
# Legend
ax.plot([], [], 'o', color='gray', markersize=10,
markeredgecolor='black', label='Linehaul (Delivery)')
ax.plot([], [], '^', color='gray', markersize=10,
markeredgecolor='black', label='Backhaul (Pickup)')
ax.set_xlabel('X Coordinate')
ax.set_ylabel('Y Coordinate')
ax.set_title('VRP with Backhauls Solution')
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
if save_path:
plt.savefig(save_path, dpi=300, bbox_inches='tight')
plt.show()def visualize_vrpb_solution(coordinates, routes, linehaul_demands,
backhaul_demands, save_path=None):
"""Visualize VRPB solution with linehauls and backhauls"""
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(12, 8))
colors = plt.cm.tab10(np.linspace(0, 1, len(routes)))
for idx, route_info in enumerate(routes):
route = route_info['route']
linehauls = route_info.get('linehauls', [])
backhauls = route_info.get('backhauls', [])
# Plot full route
route_coords = [coordinates[i] for i in route]
xs = [c[0] for c in route_coords]
ys = [c[1] for c in route_coords]
ax.plot(xs, ys, '-', color=colors[idx], linewidth=2, alpha=0.5)
# Mark linehaul customers (circles)
for customer in linehauls:
coord = coordinates[customer]
ax.plot(coord[0], coord[1], 'o', color=colors[idx],
markersize=12, markeredgecolor='black', markeredgewidth=1.5)
# Mark backhaul customers (triangles)
for customer in backhauls:
coord = coordinates[customer]
ax.plot(coord[0], coord[1], '^', color=colors[idx],
markersize=12, markeredgecolor='black', markeredgewidth=1.5)
# Plot depot
depot = coordinates[0]
ax.plot(depot[0], depot[1], 's', color='red', markersize=20,
label='Depot', markeredgecolor='black', markeredgewidth=2, zorder=10)
# Legend
ax.plot([], [], 'o', color='gray', markersize=10,
markeredgecolor='black', label='Linehaul (Delivery)')
ax.plot([], [], '^', color='gray', markersize=10,
markeredgecolor='black', label='Backhaul (Pickup)')
ax.set_xlabel('X Coordinate')
ax.set_ylabel('Y Coordinate')
ax.set_title('VRP with Backhauls Solution')
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
if save_path:
plt.savefig(save_path, dpi=300, bbox_inches='tight')
plt.show()Example
Example
if name == "main":
import random
np.random.seed(42)
random.seed(42)
# Generate problem
n_linehauls = 15
n_backhauls = 10
n_total = n_linehauls + n_backhauls + 1 # +1 for depot
coordinates = [(50, 50)] # Depot
coordinates.extend(np.random.rand(n_linehauls + n_backhauls, 2).tolist() * 100)
# Demands
linehaul_demands = [0] # Depot
linehaul_demands.extend([random.randint(5, 20) for _ in range(n_linehauls)])
linehaul_demands.extend([0] * n_backhauls) # Backhaul customers have no deliveries
backhaul_demands = [0] # Depot
backhaul_demands.extend([0] * n_linehauls) # Linehaul customers have no pickups
backhaul_demands.extend([random.randint(5, 15) for _ in range(n_backhauls)])
vehicle_capacity = 100
num_vehicles = 4
print(f"Problem: {n_linehauls} linehauls, {n_backhauls} backhauls")
print(f"Total delivery demand: {sum(linehaul_demands)}")
print(f"Total pickup demand: {sum(backhaul_demands)}")
print("\nSolving VRPB with OR-Tools...")
result = solve_vrpb_ortools(coordinates, linehaul_demands, backhaul_demands,
vehicle_capacity, num_vehicles, time_limit=60)
if result['status'] == 'Optimal':
print(f"\nStatus: {result['status']}")
print(f"Total Distance: {result['total_distance']:.2f}")
print(f"Vehicles Used: {result['num_vehicles']}")
print("\nRoute Details:")
for i, route_info in enumerate(result['routes']):
route = route_info['route']
linehauls = route_info['linehauls']
backhauls = route_info['backhauls']
total_delivery = sum(linehaul_demands[c] for c in linehauls)
total_pickup = sum(backhaul_demands[c] for c in backhauls)
print(f"\n Vehicle {i+1}:")
print(f" Route: {route}")
print(f" Linehauls: {len(linehauls)} customers, {total_delivery} units")
print(f" Backhauls: {len(backhauls)} customers, {total_pickup} units")
# Visualize
visualize_vrpb_solution(coordinates, result['routes'],
linehaul_demands, backhaul_demands)
else:
print(f"Status: {result['status']}")
---if name == "main":
import random
np.random.seed(42)
random.seed(42)
# Generate problem
n_linehauls = 15
n_backhauls = 10
n_total = n_linehauls + n_backhauls + 1 # +1 for depot
coordinates = [(50, 50)] # Depot
coordinates.extend(np.random.rand(n_linehauls + n_backhauls, 2).tolist() * 100)
# Demands
linehaul_demands = [0] # Depot
linehaul_demands.extend([random.randint(5, 20) for _ in range(n_linehauls)])
linehaul_demands.extend([0] * n_backhauls) # Backhaul customers have no deliveries
backhaul_demands = [0] # Depot
backhaul_demands.extend([0] * n_linehauls) # Linehaul customers have no pickups
backhaul_demands.extend([random.randint(5, 15) for _ in range(n_backhauls)])
vehicle_capacity = 100
num_vehicles = 4
print(f"Problem: {n_linehauls} linehauls, {n_backhauls} backhauls")
print(f"Total delivery demand: {sum(linehaul_demands)}")
print(f"Total pickup demand: {sum(backhaul_demands)}")
print("\nSolving VRPB with OR-Tools...")
result = solve_vrpb_ortools(coordinates, linehaul_demands, backhaul_demands,
vehicle_capacity, num_vehicles, time_limit=60)
if result['status'] == 'Optimal':
print(f"\nStatus: {result['status']}")
print(f"Total Distance: {result['total_distance']:.2f}")
print(f"Vehicles Used: {result['num_vehicles']}")
print("\nRoute Details:")
for i, route_info in enumerate(result['routes']):
route = route_info['route']
linehauls = route_info['linehauls']
backhauls = route_info['backhauls']
total_delivery = sum(linehaul_demands[c] for c in linehauls)
total_pickup = sum(backhaul_demands[c] for c in backhauls)
print(f"\n Vehicle {i+1}:")
print(f" Route: {route}")
print(f" Linehauls: {len(linehauls)} customers, {total_delivery} units")
print(f" Backhauls: {len(backhauls)} customers, {total_pickup} units")
# Visualize
visualize_vrpb_solution(coordinates, result['routes'],
linehaul_demands, backhaul_demands)
else:
print(f"Status: {result['status']}")
---Tools & Libraries
工具与库
- OR-Tools (Google): Best for VRPB (recommended)
- PuLP/Pyomo: MIP modeling
- Custom heuristics work well for this variant
- OR-Tools (Google):解决VRPB的首选工具(推荐)
- PuLP/Pyomo:混合整数规划建模工具
- 自定义启发式算法也适用于该问题变体
Common Challenges & Solutions
常见挑战与解决方案
Challenge: Imbalanced Linehauls and Backhauls
挑战:干线运输与回程运输任务不平衡
Problem:
- Many deliveries, few pickups (or vice versa)
- Vehicles return nearly empty
Solutions:
- Allow mixed linehaul-backhaul at same customer
- Consider dedicated backhaul-only routes
- Relax sequential constraint if possible
问题:
- 配送任务多,取货任务少(或反之)
- 车辆返程时几乎空载
解决方案:
- 允许同一客户同时存在干线运输与回程运输需求
- 考虑设置专门的回程运输路线
- 若可行则放宽顺序约束
Challenge: Strict Sequential Constraint Too Restrictive
挑战:严格顺序约束过于限制
Problem:
- Forcing all deliveries before all pickups reduces efficiency
- Could save distance by mixing
Solutions:
- Consider mixed VRPB (allows interleaving)
- See pickup-delivery-problem for more flexible variant
- Use soft penalties instead of hard constraint
问题:
- 强制先完成所有配送再进行取货会降低效率
- 混合任务顺序可能减少行驶距离
解决方案:
- 考虑使用混合VRPB(允许任务交错)
- 如需更灵活的变体,请参考pickup-delivery-problem
- 使用软惩罚替代硬约束
Challenge: Capacity Management
挑战:容量管理复杂
Problem:
- Vehicle might be full with pickups before finishing backhauls
- Complex capacity tracking
Solutions:
- Use two-dimensional capacity in OR-Tools
- Carefully check feasibility in heuristics
- Consider vehicle with compartments
问题:
- 车辆可能在完成所有回程取货任务前就装满货物
- 负载跟踪逻辑复杂
解决方案:
- 在OR-Tools中使用二维容量维度
- 在启发式算法中仔细检查可行性
- 考虑使用带分隔舱的车辆
Output Format
输出格式
VRPB Solution Report
VRPB解决方案报告
Problem:
- Linehaul customers: 20 (deliveries)
- Backhaul customers: 15 (pickups)
- Vehicles: 5 (capacity: 100 units)
Solution:
| Metric | Value |
|---|---|
| Total Distance | 1,124 km |
| Vehicles Used | 5 |
| Total Deliveries | 387 units |
| Total Pickups | 276 units |
Route Details:
| Vehicle | Linehauls | Deliveries | Backhauls | Pickups | Distance |
|---|---|---|---|---|---|
| 1 | 4 | 78 units | 3 | 54 units | 235 km |
| 2 | 5 | 95 units | 2 | 38 units | 198 km |
| [...] |
问题信息:
- 干线运输客户:20个(仅配送)
- 回程运输客户:15个(仅取货)
- 车辆:5辆(容量:100单位)
解决方案:
| 指标 | 数值 |
|---|---|
| 总行驶距离 | 1,124 km |
| 使用车辆数 | 5 |
| 总配送量 | 387单位 |
| 总取货量 | 276单位 |
路线详情:
| 车辆编号 | 干线运输客户数 | 配送量 | 回程运输客户数 | 取货量 | 行驶距离 |
|---|---|---|---|---|---|
| 1 | 4 | 78单位 | 3 | 54单位 | 235 km |
| 2 | 5 | 95单位 | 2 | 38单位 | 198 km |
| [...] |
Questions to Ask
需询问的问题
- Must all deliveries occur before all pickups?
- Can customers have both delivery and pickup?
- What's the ratio of linehauls to backhauls?
- Is this for reverse logistics or redistribution?
- Are there time constraints?
- Same vehicle capacity for deliveries and pickups?
- 是否必须先完成所有配送任务再进行取货?
- 客户是否可能同时有配送和取货需求?
- 干线运输与回程运输客户的比例是多少?
- 该问题属于逆向物流还是物资重新分配场景?
- 是否存在时间约束?
- 车辆的配送容量与取货容量是否相同?
Related Skills
相关技能
- vehicle-routing-problem: For general VRP
- pickup-delivery-problem: For paired pickup-delivery
- capacitated-vrp: For capacity-focused routing
- vehicle-routing-problem:通用车辆路径问题
- pickup-delivery-problem:配对取货配送问题
- capacitated-vrp:带容量约束的车辆路径问题