skills/split-delivery-vrp/SKILL.md
When the user wants to solve Split Delivery VRP (SDVRP), allow customers to be visited multiple times, or optimize routes where demand exceeds vehicle capacity. Also use when the user mentions "SDVRP," "split deliveries," "multiple visits," "partial deliveries," "demand splitting," or "fractional service." For standard VRP, see vehicle-routing-problem.
npx skillsauth add kishorkukreja/awesome-supply-chain split-delivery-vrpInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
You are an expert in the Split Delivery Vehicle Routing Problem and flexible delivery optimization. Your goal is to help design routes where customers can be visited by multiple vehicles, allowing partial deliveries when customer demand exceeds vehicle capacity or when splitting improves overall routing efficiency.
Before solving SDVRP instances, understand:
Split Delivery Rules
Business Context
Capacity and Demand
Additional Costs
Problem Scale
Sets:
Parameters:
Decision Variables:
Objective Function:
Minimize: Σ_{k∈K} Σ_{i∈V} Σ_{j∈V} c_{ij} * x_{ijk} +
f * Σ_{i=1}^n Σ_{k∈K} [q_{ik} > 0]
Constraints:
1. Total demand satisfied:
Σ_{k∈K} q_{ik} = d_i, ∀i ∈ {1,...,n}
2. Flow conservation (if customer i is visited by vehicle k):
If q_{ik} > 0, then
Σ_{j∈V, j≠i} x_{ijk} = Σ_{j∈V, j≠i} x_{jik}
3. Vehicle capacity:
Σ_{i=1}^n q_{ik} ≤ Q, ∀k ∈ K
4. Delivery only if visited:
q_{ik} ≤ Q * Σ_{j∈V, j≠i} x_{ijk}, ∀i ∈ {1,...,n}, ∀k ∈ K
5. Subtour elimination
6. Variables:
x_{ijk} ∈ {0,1}
q_{ik} ≥ 0
import numpy as np
import random
def sdvrp_greedy_split(dist_matrix, demands, vehicle_capacity,
num_vehicles, depot=0, split_penalty=0):
"""
Greedy split delivery heuristic
Args:
dist_matrix: distance matrix
demands: customer demands
vehicle_capacity: vehicle capacity
num_vehicles: number of vehicles
depot: depot index
split_penalty: additional cost for splitting a delivery
Returns:
solution dictionary
"""
n = len(dist_matrix)
customers = set(range(1, n))
remaining_demand = {i: demands[i] for i in customers}
routes = []
visit_counts = {i: 0 for i in customers}
for vehicle_id in range(num_vehicles):
if not any(remaining_demand[i] > 0 for i in customers):
break
route = [depot]
current_location = depot
current_load = 0
while True:
# Find best next customer to visit
best_customer = None
best_cost = float('inf')
for customer in customers:
if remaining_demand[customer] <= 0:
continue
# Determine delivery quantity
available_capacity = vehicle_capacity - current_load
delivery_qty = min(remaining_demand[customer], available_capacity)
if delivery_qty <= 0:
continue
# Calculate cost (distance + split penalty if this creates a split)
distance_cost = dist_matrix[current_location][customer]
# Check if this would be a split delivery
will_split = (delivery_qty < remaining_demand[customer])
penalty = split_penalty if will_split else 0
total_cost = distance_cost + penalty
if total_cost < best_cost:
best_cost = total_cost
best_customer = customer
if best_customer is None:
break
# Visit best customer
route.append(best_customer)
# Determine delivery quantity
available_capacity = vehicle_capacity - current_load
delivery_qty = min(remaining_demand[best_customer], available_capacity)
remaining_demand[best_customer] -= delivery_qty
current_load += delivery_qty
visit_counts[best_customer] += 1
current_location = best_customer
# Return to depot
route.append(depot)
if len(route) > 2:
routes.append(route)
# Calculate statistics
total_distance = sum(
sum(dist_matrix[route[i]][route[i+1]] for i in range(len(route)-1))
for route in routes
)
split_customers = [i for i in customers if visit_counts[i] > 1]
unserved_customers = [i for i in customers if remaining_demand[i] > 0]
return {
'routes': routes,
'visit_counts': visit_counts,
'total_distance': total_distance,
'num_vehicles': len(routes),
'split_customers': split_customers,
'unserved_customers': unserved_customers
}
def sdvrp_clarke_wright(dist_matrix, demands, vehicle_capacity, depot=0):
"""
Clarke-Wright Savings adapted for split deliveries
Args:
dist_matrix: distance matrix
demands: customer demands
vehicle_capacity: vehicle capacity
depot: depot index
Returns:
solution dictionary
"""
n = len(dist_matrix)
customers = list(range(1, n))
# Track remaining demand for each customer
remaining_demand = {i: demands[i] for i in customers}
# Initially, each customer that needs service gets a route
# If demand > capacity, customer needs multiple initial routes
routes = []
route_loads = []
for customer in customers:
demand = remaining_demand[customer]
# Create as many routes as needed for this customer
while demand > 0:
delivery = min(demand, vehicle_capacity)
routes.append([depot, customer, depot])
route_loads.append(delivery)
demand -= delivery
# Calculate savings
savings = []
for i in customers:
for j in customers:
if i < j:
saving = (dist_matrix[depot][i] +
dist_matrix[depot][j] -
dist_matrix[i][j])
savings.append((saving, i, j))
savings.sort(reverse=True)
# Merge routes based on savings
for saving_value, i, j in savings:
# Find routes ending with i and starting with j
route_i_idx = None
route_j_idx = None
for idx, route in enumerate(routes):
if len(route) > 2:
if route[-2] == i: # Route ends at i
route_i_idx = idx
if route[1] == j: # Route starts at j
route_j_idx = idx
if route_i_idx is None or route_j_idx is None:
continue
if route_i_idx == route_j_idx:
continue
# Check if merge is feasible (capacity)
combined_load = route_loads[route_i_idx] + route_loads[route_j_idx]
if combined_load > vehicle_capacity:
continue
# Merge routes
route_i = routes[route_i_idx]
route_j = routes[route_j_idx]
new_route = route_i[:-1] + route_j[1:] # Remove duplicate depot
routes[route_i_idx] = new_route
route_loads[route_i_idx] = combined_load
del routes[route_j_idx]
del route_loads[route_j_idx]
# Calculate total distance and statistics
total_distance = sum(
sum(dist_matrix[route[i]][route[i+1]] for i in range(len(route)-1))
for route in routes
)
# Count visits per customer
visit_counts = {i: 0 for i in customers}
for route in routes:
for customer in route[1:-1]:
visit_counts[customer] += 1
split_customers = [i for i in customers if visit_counts[i] > 1]
return {
'routes': routes,
'route_loads': route_loads,
'visit_counts': visit_counts,
'total_distance': total_distance,
'num_vehicles': len(routes),
'split_customers': split_customers
}
def compare_sdvrp_vs_cvrp(dist_matrix, demands, vehicle_capacity,
num_vehicles, depot=0):
"""
Compare SDVRP (with splits) vs. CVRP (no splits)
Shows benefit of allowing split deliveries
Args:
dist_matrix: distance matrix
demands: customer demands
vehicle_capacity: vehicle capacity
num_vehicles: number of vehicles
depot: depot index
Returns:
comparison dictionary
"""
print("=" * 60)
print("SDVRP vs. CVRP Comparison")
print("=" * 60)
# Solve SDVRP
print("\nSolving with Split Deliveries (SDVRP)...")
sdvrp_result = sdvrp_greedy_split(
dist_matrix, demands, vehicle_capacity, num_vehicles, depot)
# Solve CVRP (no splits) - approximate by rejecting large demands
print("\nSolving without Split Deliveries (CVRP approximation)...")
# For CVRP, customers with demand > capacity cannot be served
feasible_customers = [i for i in range(1, len(demands))
if demands[i] <= vehicle_capacity]
infeasible_customers = [i for i in range(1, len(demands))
if demands[i] > vehicle_capacity]
# Simple nearest neighbor for feasible customers
from collections import defaultdict
routes_cvrp = []
remaining = set(feasible_customers)
for _ in range(num_vehicles):
if not remaining:
break
route = [depot]
current_loc = depot
current_load = 0
while remaining:
# Find nearest feasible customer
best = None
best_dist = float('inf')
for customer in remaining:
if current_load + demands[customer] <= vehicle_capacity:
dist = dist_matrix[current_loc][customer]
if dist < best_dist:
best_dist = dist
best = customer
if best is None:
break
route.append(best)
current_load += demands[best]
current_loc = best
remaining.remove(best)
route.append(depot)
if len(route) > 2:
routes_cvrp.append(route)
cvrp_distance = sum(
sum(dist_matrix[route[i]][route[i+1]] for i in range(len(route)-1))
for route in routes_cvrp
) if routes_cvrp else float('inf')
# Print comparison
print("\n" + "=" * 60)
print("Results:")
print("=" * 60)
print(f"\nSDVRP (with splits):")
print(f" Total Distance: {sdvrp_result['total_distance']:.2f}")
print(f" Vehicles Used: {sdvrp_result['num_vehicles']}")
print(f" Split Customers: {len(sdvrp_result['split_customers'])}")
print(f" Unserved Customers: {len(sdvrp_result['unserved_customers'])}")
print(f"\nCVRP (no splits):")
print(f" Total Distance: {cvrp_distance:.2f}")
print(f" Vehicles Used: {len(routes_cvrp)}")
print(f" Unserved Customers: {len(remaining) + len(infeasible_customers)}")
if cvrp_distance < float('inf'):
improvement = (cvrp_distance - sdvrp_result['total_distance']) / cvrp_distance * 100
print(f"\nImprovement with splits: {improvement:.1f}%")
return {
'sdvrp': sdvrp_result,
'cvrp_distance': cvrp_distance,
'cvrp_routes': routes_cvrp,
'infeasible_customers': infeasible_customers
}
# Example
if __name__ == "__main__":
np.random.seed(42)
random.seed(42)
# Generate problem with some large demands
n = 21 # 1 depot + 20 customers
coordinates = np.random.rand(n, 2) * 100
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])
# Create demands where some exceed vehicle capacity
demands = [0] # Depot
vehicle_capacity = 50
for _ in range(n-1):
# 30% chance of large demand exceeding capacity
if random.random() < 0.3:
demand = random.randint(60, 100) # Exceeds capacity
else:
demand = random.randint(10, 40) # Normal demand
demands.append(demand)
num_vehicles = 8
print(f"Problem: {n-1} customers, capacity: {vehicle_capacity}")
print(f"Total demand: {sum(demands)}")
print(f"Customers with demand > capacity: {sum(1 for d in demands if d > vehicle_capacity)}")
# Run comparison
comparison = compare_sdvrp_vs_cvrp(
dist_matrix, demands, vehicle_capacity, num_vehicles)
# Detailed results for SDVRP
print("\n" + "=" * 60)
print("SDVRP Route Details:")
print("=" * 60)
for i, route in enumerate(comparison['sdvrp']['routes']):
print(f"\nVehicle {i+1}: {route}")
print("\nCustomers requiring multiple visits:")
for customer in comparison['sdvrp']['split_customers']:
visits = comparison['sdvrp']['visit_counts'][customer]
print(f" Customer {customer}: {visits} visits (demand: {demands[customer]})")
Problem:
Solutions:
Problem:
Solutions:
Problem:
Solutions:
Problem:
Solution:
| Metric | Value | |--------|-------| | Total Distance | 987 km | | Vehicles Used | 6 | | Total Visits | 32 | | Split Customers | 7 | | Avg Visits/Customer | 1.28 |
Split Delivery Details:
| Customer | Total Demand | Visits | Delivery Pattern | |----------|--------------|--------|------------------| | C5 | 85 units | 2 | 50 + 35 units | | C12 | 120 units | 3 | 50 + 50 + 20 | | C18 | 65 units | 2 | 50 + 15 |
Routes:
Vehicle 1:
Vehicle 2:
[...]
documentation
When the user wants to optimize yard operations, manage trailer parking, or improve dock door utilization. Also use when the user mentions "yard management," "trailer tracking," "yard jockey," "drop trailer program," "trailer pool," "dock scheduling," or "gate management." For cross-dock operations, see cross-docking. For warehouse design, see warehouse-design.
tools
When the user wants to optimize workforce scheduling, create shift plans, or balance labor demand. Also use when the user mentions "staff scheduling," "labor planning," "shift optimization," "crew scheduling," "roster optimization," or "employee scheduling." For task assignment, see task-assignment-problem. For wave planning labor, see wave-planning-optimization.
testing
When the user wants to optimize pick wave planning, schedule warehouse operations, or improve order fulfillment efficiency. Also use when the user mentions "wave management," "batch picking," "pick wave scheduling," "order release optimization," "wave design," or "pick wave strategy." For order batching, see order-batching-optimization. For workforce scheduling, see workforce-scheduling.
testing
When the user wants to optimize warehouse slot assignments, improve pick efficiency, or design warehouse layouts. Also use when the user mentions "slotting optimization," "slot assignment," "ABC slotting," "pick path optimization," "storage location assignment," "warehouse layout optimization," or "forward pick locations." For picker routing, see picker-routing-optimization. For warehouse design, see warehouse-design.