mcpField
P
picker-routing-optimization

picker-routing-optimization

github.com/kishorkukreja其他8449 次安装3 周前更新
代码仓库

关于 picker-routing-optimization

面向仓库运营团队的技能,用于评估布局与拣选约束、比较拣货路线方法并制定路线建议。内容涵盖图与 TSP 建模、路由算法、拥堵及多层仓库。

主要功能

  1. 01仓库布局与拣选评估
  2. 02S 形、返回、中点和最大间隙路线
  3. 03基于 TSP 的路线建模
  4. 04拥堵感知与多层路由
  5. 05路线比较与报告

适用场景

  1. 01为仓库布局选择路线方法
  2. 02比较路线距离和预计时间
  3. 03规划单向巷道及拥堵路线
  4. 04审查多层仓库路线

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/kishorkukreja/awesome-supply-chain --skill picker-routing-optimization

安装后在 Claude Code 中输入/picker-routing-optimization确认已加载。

详细介绍

拣货员路径优化

你是仓库拣货员路径规划和行走路线优化方面的专家。你的目标是帮助设计最优拣货路线,以尽量缩短行走距离、减少拣货时间、提高拣货员的工作效率,并最大限度地提升仓库效率。

初步评估

在优化拣货员路径前,请先了解:

  1. 仓库布局

    • 布局类型(网格、对角线、混合)?
    • 通道数量和长度?
    • 通道宽度(双向还是单向)?
    • 横向通道(中间位置、仅两端)?
    • 拣货位配置(单侧、双侧)?
    • 仓库区/暂存区位置?
  2. 拣货限制

    • 拣货方式(单订单、批次、分区)?
    • 设备(步行、拣货车、叉车、前移式叉车)?
    • 没有拣货任务时,是否可以跳过通道?
    • 是否可以双向穿行通道?
    • 是否可以在通道中途横向穿行?
    • 拣货清单的顺序是否可以调整?
  3. 订单特征

    • 每个订单/批次的平均拣货数量?
    • 拣货密度(每条通道的拣货数量)?
    • 拣货任务在仓库内的分布情况?
    • 商品位置模式?
  4. 当前绩效

    • 当前采用哪种路径规划方法?
    • 每个订单的平均行走距离?
    • 每小时拣货量?
    • 拣货员对路线的反馈?

拣货员路径规划框架

路径规划策略

1. S 形(穿行)路径规划

  • 进入有拣货任务的每条通道,并穿行整条通道
  • 从远端离开,前往下一条有拣货任务的通道
  • 优点:简单,无需在通道内折返
  • 缺点:可能需要穿行没有拣货任务的通道区段
  • 效率:中等(最优路线的 60-70%)

2. 折返路径规划

  • 进入通道,拣取商品,然后从同一端返回
  • 移动到下一条通道
  • 优点:非常简单、路线可预测
  • 缺点:需要大量折返,行走距离最长
  • 效率:较差(最优路线的 40-50%)
  • 适用场景:窄通道、仅允许单向通行

3. 中点路径规划

  • 若拣货任务位于前半段,则从前端折返
  • 若拣货任务位于后半段,则穿行至后端
  • 需要在中间设置横向通道
  • 优点:优于纯 S 形或折返路径规划
  • 缺点:需要横向通道设施
  • 效率:良好(最优路线的 70-80%)

4. 最大间隙路径规划

  • 找出通道内拣货点之间最大的间隙
  • 选择进出路线,避免穿行该最大间隙
  • 优点:可根据拣货点分布调整路线
  • 缺点:更复杂,需要进行计算
  • 效率:非常好(最优路线的 80-90%)

5. 最优路径规划(基于 TSP)

  • 将问题建模为旅行商问题
  • 找出遍历所有拣货点的最短路径
  • 优点:可得出最佳路线
  • 缺点:计算复杂(NP-hard)
  • 效率:最优(100%)
路径规划目标
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)

数学建模

将仓库建模为图

将仓库建模为有向图 G = (V, E):

顶点(V):

  • 拣货位置
  • 通道端点
  • 横向通道交叉点
  • 仓库区(起点/终点)

边(E):

  • 顶点之间的行走路段
  • 边权重 = 距离或时间
  • 单向通道使用有向边

路径规划问题:

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 建模

决策变量:

  • x[i,j] = 若拣货员从位置 i 前往位置 j,则为 1;否则为 0
  • u[i] = 位置 i 在路线中的顺序(用于消除子回路)

参数:

  • d[i,j] = 从位置 i 到位置 j 的距离
  • P = 拣货位置集合
  • depot = 起点/终点

目标:

Minimize: Σ Σ (d[i,j] × x[i,j])  for all i,j in (P ∪ {depot})

约束:

# 1. Leave each location exactly once (except depot twice - start and end)
for i in P:
    Σ x[i,j] = 1  for all j != i

# 2. Enter each location exactly once
for j in P:
    Σ x[i,j] = 1  for all i != j

# 3. Flow conservation
for k in P:
    Σ x[i,k] = Σ x[k,j]  for all i,j

# 4. Start and end at depot
Σ x[depot,j] = 1  for all j in P
Σ x[i,depot] = 1  for all i in P

# 5. Subtour elimination (Miller-Tucker-Zemlin)
for i,j in P:
    u[i] - u[j] + n × x[i,j] <= n - 1

# 6. Aisle constraints (can't pass through racks)
for i,j not adjacent:
    if no path exists:
        x[i,j] = 0

路径规划算法

S 形路径规划
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
    """

    # Group picks by aisle
    picks_by_aisle = picks.groupby('aisle')

    route = []
    total_distance = 0
    current_position = 0  # Start at position 0 (front of warehouse)
    current_aisle = 0  # Start at aisle 0

    # Get aisles with picks (sorted)
    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')

        # Move to aisle (cross-aisle travel)
        cross_aisle_distance = abs(aisle_id - current_aisle) * aisles[0].get('width', 10)
        total_distance += cross_aisle_distance

        # Determine entry point (front or back)
        # For S-shape: alternate front and back entry
        aisle_index = aisles_with_picks.index(aisle_id)

        if aisle_index % 2 == 0:
            # Enter from front, traverse to back
            entry_position = 0
            exit_position = aisles[aisle_id]['length']
            picks_order = aisle_picks.sort_values('position_in_aisle')
        else:
            # Enter from back, traverse to front
            entry_position = aisles[aisle_id]['length']
            exit_position = 0
            picks_order = aisle_picks.sort_values('position_in_aisle', ascending=False)

        # Move to entry point
        total_distance += abs(entry_position - current_position)

        # Traverse aisle and pick items
        for idx, pick in picks_order.iterrows():
            route.append({
                'pick_id': pick['pick_id'],
                'aisle': aisle_id,
                'position': pick['position_in_aisle'],
                'sequence': len(route) + 1
            })

        # Add traversal distance
        total_distance += abs(exit_position - entry_position)

        current_position = exit_position
        current_aisle = aisle_id

    # Return to depot
    total_distance += abs(current_position - 0)
    total_distance += abs(current_aisle - 0) * aisles[0].get('width', 10)

    return {
        'route': pd.DataFrame(route),
        'total_distance': total_distance,
        'num_picks': len(picks)
    }


# Example usage
picks = pd.DataFrame({
    'pick_id': [f'P{i:03d}' for i in range(1, 21)],
    'aisle': np.random.randint(1, 11, 20),  # 10 aisles
    'position_in_aisle': np.random.uniform(0, 100, 20),  # 100 ft aisles
    'side': np.random.choice(['left', 'right'], 20)
})

aisles = {i: {'length': 100, 'width': 12} for i in range(1, 11)}
cross_aisles = {i: [0, 100] for i in range(1, 11)}  # Front and back only

result = s_shape_routing(picks, aisles, cross_aisles)
print(f"S-Shape Routing:")
print(f"Total Distance: {result['total_distance']:.2f} ft")
print(f"Number of Picks: {result['num_picks']}")
print(f"\nFirst 5 picks in sequence:")
print(result['route'].head())
最大间隙路径规划
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']

        # Find largest gap between consecutive picks
        positions = sorted(aisle_picks['position_in_aisle'].tolist())

        # Add aisle endpoints to gap calculation
        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],
                    'start': positions[-1], 'end': aisle_length})

        largest_gap = max(gaps, key=lambda x: x['gap_size'])

        # Determine routing strategy based on largest gap location
        if largest_gap['start'] == 0:
            # Largest gap at front, enter from back
            entry_position = aisle_length
            exit_position = aisle_length
            picks_order = aisle_picks.sort_values('position_in_aisle', ascending=False)

        elif largest_gap['end'] == aisle_length:
            # Largest gap at back, enter from front
            entry_position = 0
            exit_position = 0
            picks_order = aisle_picks.sort_values('position_in_aisle')

        else:
            # Largest gap in middle, traverse around it
            # Enter from front, exit from back (or vice versa)
            # Choose direction that minimizes total travel

            # Option 1: Front to back
            dist1 = aisle_length - largest_gap['gap_size']

            # Option 2: Back to front
            dist2 = aisle_length - largest_gap['gap_size']

            # Both same for middle gaps, so use S-shape logic
            if current_position < aisle_length / 2:
                entry_position = 0
                exit_position = aisle_length
                picks_order = aisle_picks.sort_values('position_in_aisle')
            else:
                entry_position = aisle_length
                exit_position = 0
                picks_order = aisle_picks.sort_values('position_in_aisle', ascending=False)

        # Move to aisle
        cross_aisle_distance = abs(aisle_id - current_aisle) * aisles[0].get('width', 10)
        total_distance += cross_aisle_distance

        # Move to entry point
        total_distance += abs(entry_position - current_position)

        # Pick items
        for idx, pick in picks_order.iterrows():
            route.append({
                'pick_id': pick['pick_id'],
                'aisle': aisle_id,
                'position': pick['position_in_aisle'],
                'sequence': len(route) + 1
            })

        # Calculate actual travel distance in aisle (excluding largest gap if avoided)
        if entry_position == exit_position:
            # Return routing
            furthest_pick = picks_order.iloc[-1]['position_in_aisle']
            aisle_travel = 2 * abs(furthest_pick - entry_position)
        else:
            # Traversal routing (minus largest gap if skipped)
            aisle_travel = abs(exit_position - entry_position)

        total_distance += aisle_travel

        current_position = exit_position
        current_aisle = aisle_id

    # Return to depot
    total_distance += abs(current_position - 0)
    total_distance += abs(current_aisle - 0) * aisles[0].get('width', 10)

    return {
        'route': pd.DataFrame(route) if route else pd.DataFrame(),
        'total_distance': total_distance,
        'num_picks': len(picks)
    }


result_lg = largest_gap_routing(picks, aisles)
print(f"\nLargest Gap Routing:")
print(f"Total Distance: {result_lg['total_distance']:.2f} ft")
print(f"Improvement vs S-Shape: {(result['total_distance'] - result_lg['total_distance']) / result['total_distance'] * 100:.1f}%")
基于 TSP 的最优路径规划
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
    """

    # Create distance matrix
    # In real warehouse, distance is constrained by aisles (not Euclidean)
    # Use warehouse_graph or calculate Manhattan distance

    locations = picks[['x', 'y']].values
    n_locations = len(locations)

    # Add depot (0, 0)
    depot_location = np.array([[0, 0]])
    all_locations = np.vstack([depot_location, locations])

    # Calculate distance matrix (Manhattan distance for warehouse)
    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]
                )

    # Convert to integer (OR-Tools requirement)
    distance_matrix = (distance_matrix * 100).astype(int)

    # Create routing model
    manager = pywrapcp.RoutingIndexManager(n, 1, 0)  # n locations, 1 vehicle, depot=0
    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 distance_matrix[from_node][to_node]

    transit_callback_index = routing.RegisterTransitCallback(distance_callback)
    routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)

    # 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 = 10

    # Solve
    solution = routing.SolveWithParameters(search_parameters)

    if solution:
        # Extract route
        route = []
        index = routing.Start(0)
        total_distance = 0

        while not routing.IsEnd(index):
            node = manager.IndexToNode(index)
            if node > 0:  # Skip depot at start
                route.append({
                    'pick_id': picks.iloc[node - 1]['pick_id'],
                    'x': picks.iloc[node - 1]['x'],
                    'y': picks.iloc[node - 1]['y'],
                    'sequence': len(route) + 1
                })

            next_index = solution.Value(routing.NextVar(index))
            total_distance += routing.GetArcCostForVehicle(index, next_index, 0)
            index = next_index

        return {
            'route': pd.DataFrame(route),
            'total_distance': total_distance / 100,  # Convert back from integer
            'num_picks': len(picks),
            'optimal': True
        }

    return {'route': pd.DataFrame(), 'total_distance': 0, 'optimal': False}


# Example with coordinates
picks_with_coords = pd.DataFrame({
    'pick_id': [f'P{i:03d}' for i in range(1, 21)],
    'x': np.random.uniform(0, 100, 20),  # x coordinate (across aisles)
    'y': np.random.uniform(0, 100, 20),  # y coordinate (down aisle)
})

# Note: In real implementation, would use actual warehouse graph
# that respects aisle structure

result_tsp = tsp_optimal_routing(picks_with_coords, warehouse_graph={})
if result_tsp['optimal']:
    print(f"\nTSP Optimal Routing:")
    print(f"Total Distance: {result_tsp['total_distance']:.2f} ft")
    print(f"First 5 picks:")
    print(result_tsp['route'].head())

高级路径规划技术

避开拥堵的动态路径规划
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 = {}  # {picker_id: (aisle, position)}
        self.congestion_map = {}  # {aisle: congestion_level}

    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

        # Congestion level: number of pickers / aisle capacity
        for aisle in self.warehouse_layout['aisles']:
            count = aisle_counts.get(aisle, 0)
            capacity = 3  # Assume max 3 pickers per aisle comfortably
            self.congestion_map[aisle] = count / capacity

    def route_with_congestion(self, picks, picker_id):
        """
        Calculate route considering current congestion

        Penalize aisles with high congestion
        """

        # Start with base routing algorithm (e.g., largest gap)
        base_route = largest_gap_routing(picks, self.warehouse_layout['aisles'])

        if not self.real_time_tracking:
            return base_route

        # Adjust route based on congestion
        # Re-sequence to visit less congested aisles first

        route_df = base_route['route'].copy()
        route_df['aisle'] = picks.set_index('pick_id').loc[route_df['pick_id'], 'aisle'].values
        route_df['congestion'] = route_df['aisle'].map(self.congestion_map).fillna(0)

        # Sort picks within each priority group by congestion
        route_df = route_df.sort_values(['congestion', 'aisle'])
        route_df['sequence'] = range(1, len(route_df) + 1)

        return {
            'route': route_df,
            'total_distance': base_route['total_distance'] * 1.05,  # Slight penalty for re-sequencing
            'congestion_adjusted': True
        }


# Example usage
warehouse_layout = {
    'aisles': list(range(1, 11)),
    'aisle_specs': {i: {'length': 100, 'width': 12} for i in range(1, 11)}
}

router = DynamicRouter(warehouse_layout, real_time_tracking=True)

# Simulate other pickers
router.update_picker_location('picker_1', aisle=3, position=50)
router.update_picker_location('picker_2', aisle=3, position=30)
router.update_picker_location('picker_3', aisle=7, position=60)

# Route new picker
route_dynamic = router.route_with_congestion(picks, 'picker_4')
print("\nDynamic Routing with Congestion:")
print(f"Congestion Map: {router.congestion_map}")
多层仓库路径规划
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
    """

    # Group picks by level
    picks_by_level = picks.groupby('level')

    # Strategy: Minimize level changes
    # Visit all picks on one level before moving to next

    route = []
    total_distance = 0
    total_time = 0
    current_level = 1  # Start at ground level

    # Determine optimal level sequence
    # Heuristic: Ground level first, then upper levels in order

    level_sequence = sorted(picks_by_level.groups.keys())

    for level in level_sequence:
        level_picks = picks_by_level.get_group(level)

        # Move to level (vertical travel)
        if level != current_level:
            level_changes = abs(level - current_level)
            total_time += level_changes * vertical_travel_time
            current_level = level

        # Route within level (use largest gap or TSP)
        level_route = largest_gap_routing(
            level_picks,
            aisles={i: {'length': 100, 'width': 12} for i in range(1, 11)}
        )

        # Add to total route
        level_route_df = level_route['route']
        level_route_df['level'] = level
        route.append(level_route_df)

        total_distance += level_route['total_distance']

    # Combine all levels
    full_route = pd.concat(route, ignore_index=True)
    full_route['sequence'] = range(1, len(full_route) + 1)

    return {
        'route': full_route,
        'total_distance': total_distance,
        'total_time': total_time,
        'num_picks': len(picks),
        'levels_visited': len(level_sequence)
    }


# Example
picks_multi_level = pd.DataFrame({
    'pick_id': [f'P{i:03d}' for i in range(1, 31)],
    'level': np.random.choice([1, 2, 3], 30),
    'aisle': np.random.randint(1, 11, 30),
    'position_in_aisle': np.random.uniform(0, 100, 30),
})

result_ml = multi_level_routing(picks_multi_level, levels=[1, 2, 3])
print("\nMulti-Level Routing:")
print(f"Total Distance: {result_ml['total_distance']:.2f} ft")
print(f"Vertical Travel Time: {result_ml['total_time']:.0f} sec")
print(f"Levels Visited: {result_ml['levels_visited']}")

工具和库

路由软件

仓库管理系统:

  • Manhattan WMS:优化拣选路径生成
  • Blue Yonder (JDA) WMS:AI 驱动的路由
  • SAP EWM:按订单和按波次拣选路由
  • HighJump WMS:支持实时更新的动态路由

专业优化工具:

  • Lucas Systems:语音引导拣选与优化路线
  • Voxware:具备智能路由功能的语音拣选
  • Google OR-Tools:开源路由优化工具
  • Gurobi/CPLEX:商业优化求解器
Python 库
# Routing Optimization
from ortools.constraint_solver import routing_enums_pb2, pywrapcp
from scipy.optimize import linear_sum_assignment
import networkx as nx  # Graph algorithms

# TSP Solvers
from python_tsp.exact import solve_tsp_dynamic_programming
from python_tsp.heuristics import solve_tsp_simulated_annealing

# Distance Calculations
from scipy.spatial.distance import pdist, squareform, cityblock
import numpy as np

# Visualization
import matplotlib.pyplot as plt
import matplotlib.patches as patches

常见挑战与解决方案

挑战:单向通道

问题:

  • 通道狭窄,只允许单向通行
  • 无法使用 S 形路线(不能双向穿行)
  • 必须从入口返回

解决方案:

  • 采用往返路线作为基准
  • 优化入口/出口位置(前端与后端)
  • 有横向通道时使用中点
  • 考虑拓宽关键通道,以支持双向通行
  • 实施通道方向交替规则(奇数/偶数)
挑战:拥堵与阻塞

问题:

  • 多名拣选员在同一通道作业
  • 出现阻塞和等待时间
  • 为避让而导致路线变长

解决方案:

  • 使用能感知拥堵情况的实时路由
  • 错开拣选员的开始时间(间隔 5-10 分钟)
  • 按区域拣选(为拣选员分配专用通道)
  • 通过移动应用/语音系统动态重新规划路线
  • 分析交通流量以识别瓶颈
挑战:大型拣选清单

问题:

  • 单个订单/批次有 100 个以上的拣选位置
  • TSP 的计算开销过大
  • 无法进行实时路由

解决方案:

  • 使用启发式算法(最大间隔、S 形路线),而非 TSP
  • 拆分为较小批次
  • 离线预先计算路线(适用于重复订单)
  • 使用近似 TSP(LKH、Christofides)
  • 限制优化时间(在 5 秒内找到最佳路线)
挑战:拣选时间不固定

问题:

  • 有些拣选需要 5 秒,另一些则需要 60 秒
  • 重物、高层货架、按数量拣选
  • 基于距离的路由忽略了时间差异

解决方案:

  • 按时间而非距离为边加权
  • 在路线优化中纳入拣选时间
  • 优先安排困难的拣选任务(趁拣选员精力充沛时)
  • 预先摆放重型/大件物品(采用单独的工作流)
  • 通过时间与动作研究进行校准
挑战:布局变更

问题:

  • 仓库布局发生变化(库位规划调整)
  • 路线不再最优
  • 拣选员对库位变更感到困惑

解决方案:

  • 库位规划变更后重新计算路线
  • 逐步推出(按区域进行)
  • 立即更新 WMS 库位主数据
  • 培训拣选员熟悉新布局
  • 使用 RF/语音进行引导(不依赖具体库位)

输出格式

拣选员路由报告

路线优化分析 - 订单 #12345

拣选清单摘要:

  • 拣选总数:42 行
  • 涉及通道:8 条(2、4、5、7、9、11、13、15)
  • 仓库区域:A(18 次拣选)、B(15 次拣选)、C(9 次拣选)

路由对比:

方法距离(英尺)预计时间(分钟)改进幅度
往返路线1,84528.5基准
S 形路线1,12417.439%
最大间隔法89213.852%
TSP 最优路线83412.955%

推荐路线(最大间隔法):

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 |
...

路线可视化:

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)

绩效指标:

  • 每次拣选距离:21.2 英尺/次
  • 预计每小时拣选量:182 次(往返路线为 145 次)
  • 生产效率提升:+26%

需要了解的问题

如需更多背景信息:

  1. 仓库布局是什么样的(网格、横向通道)?
  2. 通道是单向还是双向的?
  3. 采用哪种拣选方式(单件、批次、分区)?
  4. 每个订单/批次的平均拣选量是多少?
  5. 目前使用哪种路由方法?
  6. 目前每小时拣选量是多少?
  7. 是否有具备路由功能的 WMS?
  8. 是否存在拥堵或阻塞问题?

相关技能

  • traveling-salesman-problem:用于 TSP 算法和理论
  • order-batching-optimization:用于创建最优配送批次
  • warehouse-slotting-optimization:用于影响路线的 SKU 布局
  • wave-planning-optimization:用于影响路径规划的波次设计
  • network-flow-optimization:用于仓库流建模
  • graph-algorithms:用于最短路径和路径规划
  • metaheuristic-optimization:用于大规模路径规划问题

评论

支持 Markdown