| name | picker-routing-optimization |
| description | When the user wants to optimize picker routes, minimize travel distance in warehouses, or improve picking efficiency. Also use when the user mentions "pick path optimization," "warehouse routing," "travel distance minimization," "TSP in warehouses," "S-shape routing," or "optimal pick sequence." For order batching, see order-batching-optimization. For warehouse slotting, see warehouse-slotting-optimization. |
Picker Routing Optimization
You are an expert in warehouse picker routing and travel path optimization. Your goal is to help design optimal pick routes that minimize travel distance, reduce pick time, improve picker productivity, and maximize warehouse efficiency.
Initial Assessment
Before optimizing picker routing, understand:
-
Warehouse Layout
- Layout type (grid, diagonal, mixed)?
- Number of aisles and length?
- Aisle width (two-way or one-way)?
- Cross-aisles (mid-points, ends only)?
- Pick face configuration (single-sided, double-sided)?
- Depot/staging location?
-
Picking Constraints
- Pick method (discrete, batch, zone)?
- Equipment (walk, picker cart, forklift, reach truck)?
- Can skip aisles if no picks?
- Can traverse aisles both ways?
- Can cross aisles mid-way?
- Pick list sequence flexibility?
-
Order Characteristics
- Average picks per order/batch?
- Pick density (picks per aisle)?
- Pick distribution across warehouse?
- Item location patterns?
-
Current Performance
- Current routing method?
- Average travel distance per order?
- Picks per hour?
- Picker feedback on routes?
Picker Routing Framework
Routing Strategies
1. S-Shape (Traversal) Routing
- Enter each aisle with picks, traverse completely
- Exit at far end, skip to next aisle with picks
- Pros: Simple, no backtracking within aisles
- Cons: May traverse empty portions of aisles
- Efficiency: Moderate (60-70% of optimal)
2. Return Routing
- Enter aisle, pick items, return to same end
- Move to next aisle
- Pros: Very simple, predictable
- Cons: High backtracking, longest distance
- Efficiency: Poor (40-50% of optimal)
- Use: Narrow aisles, one-way traffic only
3. Midpoint Routing
- If picks in front half, use return from front
- If picks in back half, traverse to back
- Requires cross-aisle in middle
- Pros: Better than pure S-shape or return
- Cons: Requires cross-aisle infrastructure
- Efficiency: Good (70-80% of optimal)
4. Largest Gap Routing
- Identify largest gap between picks in aisle
- Enter/exit to avoid traversing largest gap
- Pros: Adapts to pick distribution
- Cons: More complex, requires calculation
- Efficiency: Very good (80-90% of optimal)
5. Optimal Routing (TSP-based)
- Solve as Traveling Salesman Problem
- Find shortest path visiting all picks
- Pros: Best possible route
- Cons: Complex computation (NP-hard)
- Efficiency: Optimal (100%)
Routing Objectives
Primary Goal:
Minimize total travel distance
Secondary Goals:
- Minimize pick time (travel + access)
- Balance picker workload
- Respect aisle traffic constraints
- Maintain pick accuracy (logical sequence)
Constraints:
- Aisle layout (can't cut through racks)
- One-way aisles (directional constraints)
- Congestion (avoid other pickers)
- Equipment limitations (turning radius, height)
Mathematical Formulation
Warehouse as a Graph
Model warehouse as a directed graph G = (V, E):
Vertices (V):
- Pick locations
- Aisle endpoints
- Cross-aisle intersections
- Depot (start/end point)
Edges (E):
- Travel segments between vertices
- Edge weights = distance or time
- Directed edges for one-way aisles
Routing Problem:
Find shortest path from depot visiting all pick locations
and returning to depot
This is a variant of the Traveling Salesman Problem (TSP)
with special structure (rectilinear geometry)
TSP Formulation for Warehouse
Decision Variables:
- x[i,j] = 1 if picker travels from location i to j, 0 otherwise
- u[i] = position of location i in route (for subtour elimination)
Parameters:
- d[i,j] = distance from location i to j
- P = set of pick locations
- depot = start/end point
Objective:
Minimize: Σ Σ (d[i,j] × x[i,j]) for all i,j in (P ∪ {depot})
Constraints:
for i in P:
Σ x[i,j] = 1 for all j != i
for j in P:
Σ x[i,j] = 1 for all i != j
for k in P:
Σ x[i,k] = Σ x[k,j] for all i,j
Σ x[depot,j] = 1 for all j in P
Σ x[i,depot] = 1 for all i in P
for i,j in P:
u[i] - u[j] + n × x[i,j] <= n - 1
for i,j not adjacent:
if no path exists:
x[i,j] = 0
Routing Algorithms
S-Shape Routing
import numpy as np
import pandas as pd
def s_shape_routing(picks, aisles, cross_aisle_locations):
"""
S-Shape routing algorithm
Parameters:
-----------
picks : DataFrame
Columns: pick_id, aisle, position_in_aisle, side (left/right)
aisles : dict
{aisle_id: {'length': length, 'width': width}}
cross_aisle_locations : dict
{aisle_id: [positions...]} # Where cross-aisles exist
Returns:
--------
Route sequence and total distance
"""
picks_by_aisle = picks.groupby('aisle')
route = []
total_distance = 0
current_position = 0
current_aisle = 0
aisles_with_picks = sorted(picks_by_aisle.groups.keys())
for aisle_id in aisles_with_picks:
aisle_picks = picks_by_aisle.get_group(aisle_id).sort_values('position_in_aisle')
cross_aisle_distance = abs(aisle_id - current_aisle) * aisles[0].get('width', 10)
total_distance += cross_aisle_distance
aisle_index = aisles_with_picks.index(aisle_id)
if aisle_index % 2 == 0:
entry_position = 0
exit_position = aisles[aisle_id]['length']
picks_order = aisle_picks.sort_values('position_in_aisle')
:
entry_position = aisles[aisle_id][]
exit_position =
picks_order = aisle_picks.sort_values(, ascending=)
total_distance += (entry_position - current_position)
idx, pick picks_order.iterrows():
route.append({
: pick[],
: aisle_id,
: pick[],
: (route) +
})
total_distance += (exit_position - entry_position)
current_position = exit_position
current_aisle = aisle_id
total_distance += (current_position - )
total_distance += (current_aisle - ) * aisles[].get(, )
{
: pd.DataFrame(route),
: total_distance,
: (picks)
}
picks = pd.DataFrame({
: [ i (, )],
: np.random.randint(, , ),
: np.random.uniform(, , ),
: np.random.choice([, ], )
})
aisles = {i: {: , : } i (, )}
cross_aisles = {i: [, ] i (, )}
result = s_shape_routing(picks, aisles, cross_aisles)
()
()
()
()
(result[].head())
Largest Gap Routing
def largest_gap_routing(picks, aisles):
"""
Largest gap routing algorithm
For each aisle:
- If picks only in front half: enter and return from front
- If picks only in back half: enter and return from back
- If picks in both halves: enter from one side, exit from other (S-shape)
but choose entry/exit to minimize distance
Parameters:
-----------
picks : DataFrame
Pick locations
aisles : dict
Aisle specifications
Returns:
--------
Optimized route
"""
picks_by_aisle = picks.groupby('aisle')
route = []
total_distance = 0
current_position = 0
current_aisle = 0
aisles_with_picks = sorted(picks_by_aisle.groups.keys())
for aisle_id in aisles_with_picks:
aisle_picks = picks_by_aisle.get_group(aisle_id).sort_values('position_in_aisle')
aisle_length = aisles[aisle_id]['length']
positions = sorted(aisle_picks['position_in_aisle'].tolist())
gaps = []
gaps.append({'gap_size': positions[0] - 0,
'start': 0, 'end': positions[0]})
for i in range(len(positions) - 1):
gaps.append({'gap_size': positions[i+1] - positions[i],
'start': positions[i], 'end': positions[i+1]})
gaps.append({'gap_size': aisle_length - positions[-1],
: positions[-], : aisle_length})
largest_gap = (gaps, key= x: x[])
largest_gap[] == :
entry_position = aisle_length
exit_position = aisle_length
picks_order = aisle_picks.sort_values(, ascending=)
largest_gap[] == aisle_length:
entry_position =
exit_position =
picks_order = aisle_picks.sort_values()
:
dist1 = aisle_length - largest_gap[]
dist2 = aisle_length - largest_gap[]
current_position < aisle_length / :
entry_position =
exit_position = aisle_length
picks_order = aisle_picks.sort_values()
:
entry_position = aisle_length
exit_position =
picks_order = aisle_picks.sort_values(, ascending=)
cross_aisle_distance = (aisle_id - current_aisle) * aisles[].get(, )
total_distance += cross_aisle_distance
total_distance += (entry_position - current_position)
idx, pick picks_order.iterrows():
route.append({
: pick[],
: aisle_id,
: pick[],
: (route) +
})
entry_position == exit_position:
furthest_pick = picks_order.iloc[-][]
aisle_travel = * (furthest_pick - entry_position)
:
aisle_travel = (exit_position - entry_position)
total_distance += aisle_travel
current_position = exit_position
current_aisle = aisle_id
total_distance += (current_position - )
total_distance += (current_aisle - ) * aisles[].get(, )
{
: pd.DataFrame(route) route pd.DataFrame(),
: total_distance,
: (picks)
}
result_lg = largest_gap_routing(picks, aisles)
()
()
()
TSP-Based Optimal Routing
from scipy.spatial.distance import pdist, squareform
from ortools.constraint_solver import routing_enums_pb2
from ortools.constraint_solver import pywrapcp
def tsp_optimal_routing(picks, warehouse_graph):
"""
Optimal routing using TSP solver (Google OR-Tools)
Parameters:
-----------
picks : DataFrame
Pick locations with x, y coordinates
warehouse_graph : dict
Distance matrix considering warehouse layout constraints
Returns:
--------
Optimal route
"""
locations = picks[['x', 'y']].values
n_locations = len(locations)
depot_location = np.array([[0, 0]])
all_locations = np.vstack([depot_location, locations])
def manhattan_distance(loc1, loc2):
return abs(loc1[0] - loc2[0]) + abs(loc1[1] - loc2[1])
n = len(all_locations)
distance_matrix = np.zeros((n, n))
for i in range(n):
for j in range(n):
if i != j:
distance_matrix[i][j] = manhattan_distance(
all_locations[i], all_locations[j]
)
distance_matrix = (distance_matrix * ).astype()
manager = pywrapcp.RoutingIndexManager(n, , )
routing = pywrapcp.RoutingModel(manager)
():
from_node = manager.IndexToNode(from_index)
to_node = manager.IndexToNode(to_index)
distance_matrix[from_node][to_node]
transit_callback_index = routing.RegisterTransitCallback(distance_callback)
routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)
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 =
solution = routing.SolveWithParameters(search_parameters)
solution:
route = []
index = routing.Start()
total_distance =
routing.IsEnd(index):
node = manager.IndexToNode(index)
node > :
route.append({
: picks.iloc[node - ][],
: picks.iloc[node - ][],
: picks.iloc[node - ][],
: (route) +
})
next_index = solution.Value(routing.NextVar(index))
total_distance += routing.GetArcCostForVehicle(index, next_index, )
index = next_index
{
: pd.DataFrame(route),
: total_distance / ,
: (picks),
:
}
{: pd.DataFrame(), : , : }
picks_with_coords = pd.DataFrame({
: [ i (, )],
: np.random.uniform(, , ),
: np.random.uniform(, , ),
})
result_tsp = tsp_optimal_routing(picks_with_coords, warehouse_graph={})
result_tsp[]:
()
()
()
(result_tsp[].head())
Advanced Routing Techniques
Dynamic Routing with Congestion Avoidance
class DynamicRouter:
"""
Dynamic routing that adapts to warehouse congestion
"""
def __init__(self, warehouse_layout, real_time_tracking=False):
self.warehouse_layout = warehouse_layout
self.real_time_tracking = real_time_tracking
self.picker_locations = {}
self.congestion_map = {}
def update_picker_location(self, picker_id, aisle, position):
"""Update picker location for congestion tracking"""
self.picker_locations[picker_id] = (aisle, position)
self.update_congestion_map()
def update_congestion_map(self):
"""Calculate congestion level for each aisle"""
aisle_counts = {}
for picker_id, (aisle, position) in self.picker_locations.items():
aisle_counts[aisle] = aisle_counts.get(aisle, 0) + 1
for aisle in self.warehouse_layout['aisles']:
count = aisle_counts.get(aisle, 0)
capacity = 3
self.congestion_map[aisle] = count / capacity
def route_with_congestion():
base_route = largest_gap_routing(picks, .warehouse_layout[])
.real_time_tracking:
base_route
route_df = base_route[].copy()
route_df[] = picks.set_index().loc[route_df[], ].values
route_df[] = route_df[].(.congestion_map).fillna()
route_df = route_df.sort_values([, ])
route_df[] = (, (route_df) + )
{
: route_df,
: base_route[] * ,
:
}
warehouse_layout = {
: ((, )),
: {i: {: , : } i (, )}
}
router = DynamicRouter(warehouse_layout, real_time_tracking=)
router.update_picker_location(, aisle=, position=)
router.update_picker_location(, aisle=, position=)
router.update_picker_location(, aisle=, position=)
route_dynamic = router.route_with_congestion(picks, )
()
()
Multi-Level Warehouse Routing
def multi_level_routing(picks, levels, vertical_travel_time=30):
"""
Routing for multi-level warehouses (mezzanines, multi-story)
Parameters:
-----------
picks : DataFrame
Picks with level, aisle, position
levels : list
Available levels
vertical_travel_time : float
Seconds to move between levels
Returns:
--------
Optimized route considering vertical travel
"""
picks_by_level = picks.groupby('level')
route = []
total_distance = 0
total_time = 0
current_level = 1
level_sequence = sorted(picks_by_level.groups.keys())
for level in level_sequence:
level_picks = picks_by_level.get_group(level)
if level != current_level:
level_changes = abs(level - current_level)
total_time += level_changes * vertical_travel_time
current_level = level
level_route = largest_gap_routing(
level_picks,
aisles={i: {'length': 100, 'width': 12} for i in range(1, 11)}
)
level_route_df = level_route['route']
level_route_df['level'] = level
route.append(level_route_df)
total_distance += level_route[]
full_route = pd.concat(route, ignore_index=)
full_route[] = (, (full_route) + )
{
: full_route,
: total_distance,
: total_time,
: (picks),
: (level_sequence)
}
picks_multi_level = pd.DataFrame({
: [ i (, )],
: np.random.choice([, , ], ),
: np.random.randint(, , ),
: np.random.uniform(, , ),
})
result_ml = multi_level_routing(picks_multi_level, levels=[, , ])
()
()
()
()
Tools & Libraries
Routing Software
Warehouse Management Systems:
- Manhattan WMS: Optimized pick path generation
- Blue Yonder (JDA) WMS: AI-driven routing
- SAP EWM: Pick-by-order and pick-by-wave routing
- HighJump WMS: Dynamic routing with real-time updates
Specialized Optimization:
- Lucas Systems: Voice-directed picking with optimized routes
- Voxware: Voice picking with intelligent routing
- Google OR-Tools: Open-source routing optimization
- Gurobi/CPLEX: Commercial optimization solvers
Python Libraries
from ortools.constraint_solver import routing_enums_pb2, pywrapcp
from scipy.optimize import linear_sum_assignment
import networkx as nx
from python_tsp.exact import solve_tsp_dynamic_programming
from python_tsp.heuristics import solve_tsp_simulated_annealing
from scipy.spatial.distance import pdist, squareform, cityblock
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
Common Challenges & Solutions
Challenge: One-Way Aisles
Problem:
- Narrow aisles, only one-way traffic
- Can't use S-shape (can't traverse both directions)
- Must return from entry point
Solutions:
- Use return routing as baseline
- Optimize entry/exit points (front vs back)
- Use midpoint if cross-aisle available
- Consider widening key aisles for two-way traffic
- Implement aisle direction alternation (odd/even)
Challenge: Congestion and Blocking
Problem:
- Multiple pickers in same aisle
- Blocking and waiting time
- Routes become longer due to avoidance
Solutions:
- Real-time routing with congestion awareness
- Stagger picker start times (offset by 5-10 min)
- Zone-based picking (dedicate aisles to pickers)
- Dynamic re-routing via mobile app/voice system
- Traffic flow analysis to identify bottlenecks
Challenge: Large Pick Lists
Problem:
- 100+ pick locations in single order/batch
- TSP becomes computationally expensive
- Real-time routing not feasible
Solutions:
- Use heuristics (largest gap, S-shape) instead of TSP
- Divide into smaller batches
- Pre-compute routes offline (for recurring orders)
- Use approximate TSP (LKH, Christofides)
- Time limit on optimization (best route in 5 seconds)
Challenge: Variable Pick Times
Problem:
- Some picks take 5 seconds, others 60 seconds
- Heavy items, high shelves, quantity picks
- Distance-based routing ignores time variability
Solutions:
- Weight edges by time, not distance
- Include pick time in route optimization
- Sequence difficult picks first (when picker fresh)
- Pre-stage heavy/bulky items (separate workflow)
- Use time-motion studies to calibrate
Challenge: Layout Changes
Problem:
- Warehouse layout changes (slotting refresh)
- Routes become suboptimal
- Pickers confused by location changes
Solutions:
- Re-compute routes after slotting changes
- Gradual rollout (zone by zone)
- Update WMS location master immediately
- Train pickers on new layout
- Use RF/voice to direct (location-agnostic)
Output Format
Picker Routing Report
Route Optimization Analysis - Order #12345
Pick List Summary:
- Total Picks: 42 lines
- Aisles Involved: 8 aisles (2, 4, 5, 7, 9, 11, 13, 15)
- Warehouse Zones: A (18 picks), B (15 picks), C (9 picks)
Routing Comparison:
| Method | Distance (ft) | Est. Time (min) | Improvement |
|---|
| Return Routing | 1,845 | 28.5 | Baseline |
| S-Shape Routing | 1,124 | 17.4 | 39% |
| Largest Gap | 892 | 13.8 | 52% |
| TSP Optimal | 834 | 12.9 | 55% |
Recommended Route (Largest Gap):
Sequence | Pick ID | SKU | Aisle | Position | Side | Qty |
---------|---------|-----|-------|----------|------|-----|
1 | P001 | SKU_A | 2 | 15.3 | L | 2 |
2 | P002 | SKU_B | 2 | 28.7 | R | 1 |
3 | P003 | SKU_C | 2 | 45.2 | L | 3 |
4 | [Cross to Aisle 4]
5 | P008 | SKU_H | 4 | 82.1 | R | 1 |
6 | P009 | SKU_I | 4 | 67.3 | L | 2 |
...
Route Visualization:
Depot (Start)
↓
Aisle 2 [Enter Front → Pick 3 items → Exit Front]
↓
Cross-Aisle Travel (2 → 4)
↓
Aisle 4 [Enter Back → Pick 5 items → Exit Front]
↓
Cross-Aisle Travel (4 → 5)
↓
Aisle 5 [Enter Front → Pick 4 items → Exit Front]
...
↓
Return to Depot (End)
Total Distance: 892 ft
Estimated Time: 13.8 minutes (assuming 100 ft/min walk + 10 sec/pick)
Performance Metrics:
- Distance per Pick: 21.2 ft/pick
- Estimated Picks per Hour: 182 (vs. 145 with return routing)
- Productivity Improvement: +26%
Questions to Ask
If you need more context:
- What's your warehouse layout (grid, cross-aisles)?
- Are aisles one-way or two-way?
- What picking method (discrete, batch, zone)?
- What's your average picks per order/batch?
- What routing method do you currently use?
- What's your current picks per hour?
- Do you have WMS with routing capability?
- Any congestion or blocking issues?
Related Skills
- traveling-salesman-problem: For TSP algorithms and theory
- order-batching-optimization: For creating optimal batches to route
- warehouse-slotting-optimization: For SKU placement affecting routes
- wave-planning-optimization: For wave design impacting routing
- network-flow-optimization: For warehouse flow modeling
- graph-algorithms: For shortest path and routing
- metaheuristic-optimization: For large-scale routing problems