mcpField
P
picker-routing-optimization

picker-routing-optimization

github.com/kishorkukrejaその他8449 回インストール3 週間前に更新
リポジトリ

picker-routing-optimization について

倉庫運営チーム向けに、レイアウトやピッキング制約を評価し、ピッカーの経路方式を比較して推奨ルートを検討するスキルです。グラフや TSP による定式化、混雑対応、複数階の倉庫も扱います。

主な機能

  1. 01倉庫レイアウトとピッキング条件の評価
  2. 02S字・往復・中間通路・最大ギャップ方式
  3. 03TSPによるルート定式化
  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 時間あたりのピッキング数?
    • ルートに関するピッカーからのフィードバック?

ピッカールートのフレームワーク

ルート設定戦略

1. S 字型(通過)ルート

  • ピッキング対象がある各通路に入り、端まで通り抜けます
  • 反対側の端から出て、ピッキング対象がある次の通路へ移動します
  • 長所:シンプルで、通路内の往復がありません
  • 短所:ピッキング対象がない通路の区間も通過する場合があります
  • 効率:中程度(最適ルートの 60~70%)

2. 折り返しルート

  • 通路に入り、商品をピッキングして、同じ端から戻ります
  • 次の通路へ移動します
  • 長所:非常にシンプルで、予測しやすい方法です
  • 短所:往復が多く、移動距離が最長になります
  • 効率:低い(最適ルートの 40~50%)
  • 用途:狭い通路、または一方通行のみの場合

3. 中間地点ルート

  • ピッキング対象が手前半分にある場合は、手前側から折り返します
  • ピッキング対象が奥半分にある場合は、奥まで通り抜けます
  • 中間地点に横断通路が必要です
  • 長所:S 字型ルートや折り返しルートのみの場合より優れています
  • 短所:横断通路の整備が必要です
  • 効率:良好(最適ルートの 70~80%)

4. 最大ギャップルート

  • 通路内のピッキング対象間で最も大きな間隔を特定します
  • 最大の間隔を通らずに済むよう、通路への進入・退出を決めます
  • 長所:ピッキング対象の分布に応じて調整できます
  • 短所:より複雑で、計算が必要です
  • 効率:非常に良好(最適ルートの 80~90%)

5. 最適ルート(TSP ベース)

  • 巡回セールスマン問題として解きます
  • すべてのピッキング対象を訪問する最短経路を求めます
  • 長所:可能な限り最良のルートです
  • 短所:計算が複雑です(NP 困難)
  • 効率:最適(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 分間隔)
  • ゾーン別ピッキングを行います(通路をピッカーごとに割り当てます)
  • モバイルアプリ/音声システムを使って動的にルートを再設定します
  • 交通流を分析してボトルネックを特定します
課題:ピックリストが大きい

問題:

  • 1 つのオーダー/バッチに 100 か所以上のピック位置があります
  • TSP の計算コストが高くなります
  • リアルタイムルーティングが実現できません

解決策:

  • TSP の代わりにヒューリスティック(最大ギャップ法、S 字ルート)を使用します
  • 小さなバッチに分割します
  • ルートをオフラインで事前計算します(繰り返し発生するオーダー向け)
  • 近似 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 ピック)

ルーティングの比較:

方法距離(ft)推定時間(分)改善率
リターンルーティング1,84528.5基準
S 字ルーティング1,12417.439%
最大ギャップ法89213.852%
最適 TSP83412.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 ft/ピック
  • 推定ピック数/時間: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:大規模なルーティング問題について

コメント