
"货到人"系统订单拣选和分拣协同
针对"货到人"智能仓储系统中订单拣选和分拣的协同优化问题(OPSCP)进行了深入研究。通过建立数学模型和设计高效算法,旨在提升系统整体效率,减少订单出库时间。
一、研究背景与问题重要性
随着电子商务的快速发展,物流仓库订单呈现"多品种、小批量"的特点,传统人工仓库管理方式已无法满足高效率和高精准度的需求。以AGV(自动导引小车)为核心的"货到人"系统(如亚马逊Kiva机器人、京东机器人仓)通过自动化货架搬运和订单分拣,显著提升了作业效率。然而,拣选和分拣作为仓储系统的两个关键环节,往往被独立优化,导致资源利用不充分和整体性能瓶颈。
本文创新性地将拣选与分拣过程联合决策,提出协同优化模式,以解决动态环境下订单处理的同步性问题。协同优化有助于减少AGV闲置时间、降低企业成本,并提升订单满足率,对现代智能仓储系统具有重要实践价值。
二、数学模型构建
问题核心
在"货到人"系统中,仓库分为货架存放区和订单包裹投递区。订单需从货架拣选商品后形成包裹,再由分拣AGV根据目的地投递。关键挑战在于:
-
动态性:货架拣选顺序影响订单包裹的形成时间,而分拣需满足订单时间窗约束。
-
资源约束:分拣AGV有载重限制(如Q=6),需批量处理订单以提升效率。
-
协同需求:传统"先拣选后分拣"模式易导致AGV等待或时间窗违约,协同模式通过联合调度实现流程无缝衔接。
数学模型创新
本文建立以最小化分拣批次数量为目标的混合整数规划模型,核心约束包括:
-
分拣批次一致性(公式2):同一批次订单需投递至相同目的地。
-
装载能力限制(公式3):批次商品总数不超过AGV载重Q。
-
时间窗约束(公式14-15):分拣完成时间需在订单最早和最晚时间之间。
-
货架拣选顺序(公式6-10):通过虚拟货架节点定义拣选路径,确保作业连续性。
模型通过优化货架拣选顺序(变量vij)和订单分拣批次分配(变量xsg),实现拣选与分拣的协同。数学形式简洁且可扩展,为算法设计奠定基础。
三、算法设计:混合变邻域搜索(HVNS)与"分类装载"策略
HVNS算法框架
针对OPSCP的NP-hard特性,本文提出混合变邻域搜索算法(HVNS),结合了变邻域搜索的全局探索能力和模拟退火(SA)的局部逃逸机制。算法流程包括:
-
初始解生成:采用贪婪策略,优先拣选需求频率高的货架。
-
扰动算子(如插入、交换)扩大搜索范围。
-
局部搜索算子(如逆转、倒序)精细优化。
-
SA接受准则:以概率接受较差解,避免早熟收敛。
"分类装载"策略(CLS)
这是本文的核心创新之一,动态处理订单分拣批次:
-
订单分类:根据货架拣选顺序,将订单分为三类:
-
A类:拣选完成后可立即分拣(时间窗充裕)。
-
B类:需等待至最早分拣时间。
-
C类:时间窗违约订单(作为惩罚项)。
-
-
批次组合:对同类订单,按投递口和载重约束预分批,计算时间窗重合区间以确定批次分拣时间。
该策略平衡了AGV装载率与时间窗满足率,相比传统先到先服务策略(I-FCFS)更适应动态环境。
四、完整代码
import streamlit as st
import numpy as np
import pandas as pd
import plotly.graph_objects as go
import plotly.express as px
from plotly.subplots import make_subplots
import plotly.graph_objects as go
import random
import time
from dataclasses import dataclass
from typing import List, Dict, Tuple, Set, Optional
from enum import Enum
import heapq
from collections import defaultdict, deque
import math
import threading
import json
# 设置页面配置
st.set_page_config(
page_title="货到人系统协同优化仿真",
layout="wide"
)
# 定义枚举和数据结构
class OrderStatus(Enum):
"""订单状态枚举"""
PENDING = "待拣选"
PICKING = "拣选中"
WAITING = "等待分拣"
SORTING = "分拣中"
COMPLETED = "已完成"
DELAYED = "延迟"
class ShelfType(Enum):
"""货架类型枚举"""
FAST_MOVING = "快流"
MEDIUM_MOVING = "中流"
SLOW_MOVING = "慢流"
class AGVType(Enum):
"""AGV类型枚举"""
PICKING = "拣选AGV"
SORTING = "分拣AGV"
@dataclass
class SKU:
"""商品SKU类"""
id: int
name: str
size: float # 商品尺寸
@dataclass
class Shelf:
"""货架类"""
id: int
skus: List[SKU]
position: Tuple[float, float] # 货架位置(x, y)
frequency: float # 访问频率
shelf_type: ShelfType
@dataclass
class Order:
"""订单类"""
id: int
skus: List[SKU]
due_time: float # 最晚完成时间
arrival_time: float # 到达时间
destination: int # 目的地/分拣口
status: OrderStatus
pick_start_time: Optional[float] = None
pick_end_time: Optional[float] = None
sort_start_time: Optional[float] = None
sort_end_time: Optional[float] = None
@dataclass
class AGV:
"""AGV类"""
id: int
agv_type: AGVType
capacity: int
current_load: int
position: Tuple[float, float]
is_available: bool
current_task: Optional[str] = None
completion_time: float = 0.0
class WarehouseSimulation:
"""仓库仿真类"""
def __init__(self, config: Dict):
self.config = config
self.orders = []
self.shelves = []
self.picking_agvs = []
self.sorting_agvs = []
self.current_time = 0
self.completed_orders = []
self.delayed_orders = []
self.order_sequence = []
self.batch_results = []
self.initialize_simulation()
def initialize_simulation(self):
"""初始化仿真环境"""
np.random.seed(self.config['random_seed'])
# 生成货架
for i in range(self.config['num_shelves']):
# 随机生成货架类型分布
if i < 0.2 * self.config['num_shelves']:
shelf_type = ShelfType.FAST_MOVING
frequency = np.random.uniform(0.7, 1.0)
elif i < 0.5 * self.config['num_shelves']:
shelf_type = ShelfType.MEDIUM_MOVING
frequency = np.random.uniform(0.3, 0.7)
else:
shelf_type = ShelfType.SLOW_MOVING
frequency = np.random.uniform(0.0, 0.3)
# 生成货架位置
pos_x = np.random.uniform(0, self.config['warehouse_width'])
pos_y = np.random.uniform(0, self.config['warehouse_height'])
# 生成货架上的SKU
num_skus = np.random.randint(3, 8)
skus = []
for j in range(num_skus):
sku = SKU(
id=j + i * 100,
name=f"SKU_{i}_{j}",
size=np.random.uniform(0.1, 0.5)
)
skus.append(sku)
shelf = Shelf(
id=i,
skus=skus,
position=(pos_x, pos_y),
frequency=frequency,
shelf_type=shelf_type
)
self.shelves.append(shelf)
# 生成订单
for i in range(self.config['num_orders']):
# 随机选择订单中的SKU数量
num_order_skus = np.random.choice(
[1, 2, 3, 4],
p=self.config['order_size_distribution']
)
# 随机选择货架和SKU
selected_skus = []
for _ in range(num_order_skus):
shelf_idx = np.random.randint(0, len(self.shelves))
shelf = self.shelves[shelf_idx]
if shelf.skus:
sku = random.choice(shelf.skus)
selected_skus.append(sku)
# 生成订单时间窗
arrival_time = np.random.exponential(self.config['arrival_rate'])
due_time = arrival_time + np.random.uniform(
self.config['min_due_time'],
self.config['max_due_time']
)
# 随机选择目的地
destination = np.random.randint(1, self.config['num_destinations'] + 1)
order = Order(
id=i,
skus=selected_skus,
due_time=due_time,
arrival_time=arrival_time,
destination=destination,
status=OrderStatus.PENDING
)
self.orders.append(order)
# 按到达时间排序
self.orders.sort(key=lambda x: x.arrival_time)
# 生成AGV
for i in range(self.config['num_picking_agvs']):
agv = AGV(
id=i,
agv_type=AGVType.PICKING,
capacity=1, # 拣选AGV一次搬运一个货架
current_load=0,
position=(0, 0),
is_available=True
)
self.picking_agvs.append(agv)
for i in range(self.config['num_sorting_agvs']):
agv = AGV(
id=100 + i,
agv_type=AGVType.SORTING,
capacity=self.config['sorting_agv_capacity'],
current_load=0,
position=(self.config['warehouse_width'], 0),
is_available=True
)
self.sorting_agvs.append(agv)
def greedy_shelf_selection(self, order, agv_position):
"""改进的货架选择策略 - 考虑实际距离和AGV移动成本"""
# 计算每个货架的效用值
shelf_scores = []
for shelf in self.shelves:
# 检查货架是否有订单需要的SKU
shelf_skus = {sku.id for sku in shelf.skus}
order_skus = {sku.id for sku in order.skus}
common_skus = shelf_skus.intersection(order_skus)
if common_skus:
# 计算实际曼哈顿距离(AGV移动成本)
distance = abs(shelf.position[0] - agv_position[0]) + abs(shelf.position[1] - agv_position[1])
# 考虑AGV移动时间和货架访问频率
travel_time = distance / self.config.get('agv_speed', 1.0)
# 计算拣选效率(SKU密度)
pick_efficiency = len(common_skus) / len(shelf.skus) if shelf.skus else 0
# 效用值计算:综合SKU数量、拣选效率、访问频率和移动成本
# 使用对数函数使SKU数量的影响更符合实际
sku_factor = np.log1p(len(common_skus)) # 使用log1p避免log(0)
efficiency_factor = pick_efficiency * 2 # 效率权重
frequency_factor = shelf.frequency * 3 # 频率权重
distance_penalty = travel_time * 0.5 # 距离惩罚
score = sku_factor + efficiency_factor + frequency_factor - distance_penalty
# 考虑货架类型对拣选时间的影响
type_multiplier = {
ShelfType.FAST_MOVING: 0.8, # 快流货架拣选更快
ShelfType.MEDIUM_MOVING: 1.0,
ShelfType.SLOW_MOVING: 1.2 # 慢流货架拣选更慢
}
# 确保 shelf_type 是枚举
shelf_type_val = shelf.shelf_type
if isinstance(shelf_type_val, str):
shelf_type_enum = ShelfType(shelf_type_val)
else:
shelf_type_enum = shelf_type_val
score /= type_multiplier[shelf_type_enum]
shelf_scores.append((score, shelf, distance, travel_time))
if shelf_scores:
# 选择效用值最高的货架
shelf_scores.sort(reverse=True)
return shelf_scores[0][1], shelf_scores[0][2], shelf_scores[0][3]
return None, float('inf'), 0
def traditional_fcfs_algorithm(self):
"""改进的传统先到先服务算法 - 更符合实际运作"""
import sys
print(f"[DEBUG] traditional_fcfs_algorithm started", file=sys.stderr)
self.order_sequence = self.orders.copy()
# 初始化AGV位置
agv_positions = {agv.id: agv.position for agv in self.picking_agvs}
# 模拟拣选过程
picking_time = 0
available_agvs = self.picking_agvs.copy()
for i, order in enumerate(self.order_sequence):
# 确保拣选开始时间不早于订单到达时间
picking_time = max(picking_time, order.arrival_time)
order.status = OrderStatus.PICKING
order.pick_start_time = picking_time
# 选择最近的可用AGV
if not available_agvs:
# 等待AGV可用
next_available_time = min(agv.completion_time for agv in self.picking_agvs)
picking_time = max(picking_time, next_available_time)
# 重置可用AGV列表
available_agvs = [agv for agv in self.picking_agvs if agv.completion_time <= picking_time]
print(f"[DEBUG] Order {i}: waited for AGV, picking_time={picking_time:.1f}", file=sys.stderr)
# 选择最近的AGV
agv = min(available_agvs, key=lambda x: np.sqrt(
(agv_positions[x.id][0])**2 + (agv_positions[x.id][1])**2
))
agv_position = agv_positions[agv.id]
available_agvs.remove(agv)
# 选择货架(使用改进的策略)
shelf, distance, travel_time = self.greedy_shelf_selection(order, agv_position)
if shelf:
# 计算实际拣选时间:包括移动时间、拣选时间和返回时间
agv_speed = self.config.get('agv_speed', 1.0)
pick_time_per_sku = self.config['base_pick_time']
# 移动到货架
move_time = distance / agv_speed
# 拣选时间(考虑货架类型影响)
type_multiplier = {
ShelfType.FAST_MOVING: 0.8,
ShelfType.MEDIUM_MOVING: 1.0,
ShelfType.SLOW_MOVING: 1.2
}
# 确保 shelf_type 是枚举
shelf_type_val = shelf.shelf_type
if isinstance(shelf_type_val, str):
shelf_type_enum = ShelfType(shelf_type_val)
else:
shelf_type_enum = shelf_type_val
picking_duration = move_time + pick_time_per_sku * len(order.skus) * type_multiplier[shelf_type_enum]
# 更新AGV位置
agv_positions[agv.id] = shelf.position
picking_time += picking_duration
order.pick_end_time = picking_time
# AGV返回拣选站
return_time = distance / agv_speed
agv.completion_time = picking_time + return_time
agv_positions[agv.id] = (0, 0) # 返回拣选站
order.status = OrderStatus.WAITING
print(f"[DEBUG] Order {i}: shelf={shelf.id}, distance={distance:.1f}, move_time={move_time:.1f}, pick_duration={picking_duration:.1f}, picking_time={picking_time:.1f}", file=sys.stderr)
else:
# 没有找到合适货架,订单延迟
order.status = OrderStatus.DELAYED
self.delayed_orders.append(order)
print(f"[DEBUG] Order {i}: no suitable shelf, delayed", file=sys.stderr)
# 模拟分拣过程(改进的FCFS)
sorting_time = picking_time
batches = []
print(f"[DEBUG] After picking: picking_time={picking_time:.1f}, sorting_time={sorting_time:.1f}", file=sys.stderr)
# 按目的地和拣选完成时间对订单分组
for destination in range(1, self.config['num_destinations'] + 1):
destination_orders = [o for o in self.order_sequence if o.destination == destination and o.status != OrderStatus.DELAYED]
if not destination_orders:
continue
# 创建批次(考虑AGV容量和订单等待时间)
current_batch = []
current_batch_load = 0
batch_start_time = sorting_time
for order in sorted(destination_orders, key=lambda x: x.pick_end_time):
order.status = OrderStatus.SORTING
# 检查是否可以加入当前批次
if (current_batch_load + len(order.skus) <= self.config['sorting_agv_capacity']):
if not current_batch:
# 第一个订单,等待其拣选完成
batch_start_time = max(batch_start_time, order.pick_end_time)
# 检查加入此订单是否会违反时间窗
batch_sort_time = self.config['base_sort_time'] * (
1 + len(current_batch) * 0.1 + len(order.skus) * 0.05
)
batch_completion_time = batch_start_time + batch_sort_time
if batch_completion_time <= order.due_time or not current_batch:
current_batch.append(order)
current_batch_load += len(order.skus)
else:
# 开始新批次
batches.append(current_batch)
current_batch = [order]
current_batch_load = len(order.skus)
batch_start_time = max(sorting_time, order.pick_end_time)
else:
# 批次已满,开始新批次
if current_batch:
batches.append(current_batch)
current_batch = [order]
current_batch_load = len(order.skus)
batch_start_time = max(sorting_time, order.pick_end_time)
# 添加最后一个批次
if current_batch:
batches.append(current_batch)
print(f"[DEBUG] Created {len(batches)} batches", file=sys.stderr)
# 处理所有批次
for batch in batches:
if not batch:
continue
# 批次开始时间(确保所有订单都已拣选完成)
batch_start_time = max(order.pick_end_time for order in batch)
batch_start_time = max(sorting_time, batch_start_time)
# 计算分拣时间(更精确的计算)
base_sort_time = self.config['base_sort_time']
setup_time = base_sort_time * 0.2 # 设置时间
per_order_time = base_sort_time * 0.1 * len(batch) # 订单相关时间
per_sku_time = base_sort_time * 0.05 * sum(len(o.skus) for o in batch) # SKU相关时间
batch_sort_time = setup_time + per_order_time + per_sku_time
sorting_time = batch_start_time + batch_sort_time
print(f"[DEBUG] Processing batch: size={len(batch)}, batch_start={batch_start_time:.1f}, batch_sort={batch_sort_time:.1f}, sorting_time={sorting_time:.1f}", file=sys.stderr)
for order in batch:
order.sort_start_time = batch_start_time
order.sort_end_time = sorting_time
# 检查是否延迟
if sorting_time > order.due_time:
order.status = OrderStatus.DELAYED
self.delayed_orders.append(order)
print(f"[DEBUG] Order {order.id}: delayed, due={order.due_time:.1f}, sort_end={sorting_time:.1f}", file=sys.stderr)
else:
order.status = OrderStatus.COMPLETED
self.completed_orders.append(order)
print(f"[DEBUG] Order {order.id}: completed", file=sys.stderr)
print(f"[DEBUG] Algorithm finished: sorting_time={sorting_time:.1f}, completed_orders={len(self.completed_orders)}, delayed_orders={len(self.delayed_orders)}", file=sys.stderr)
return sorting_time
def hvn_algorithm(self):
"""改进的混合变邻域搜索算法 - 实际应用导向的协同优化"""
# 初始解:智能订单排序
unprocessed_orders = self.orders.copy()
processed_orders = []
# 计算订单紧急度指标(更精确的评估)
def calculate_order_urgency(order, current_time):
# 时间紧迫度(对数函数,更平滑的变化)
if order.due_time > current_time:
time_urgency = 1.0 / np.log1p(order.due_time - current_time)
else:
time_urgency = float('inf') # 已过期
# 订单复杂度(SKU数量和目的地)
complexity = len(order.skus) * (1 + 0.1 * order.destination)
# 等待时间惩罚(线性增长,但有上限)
waiting_penalty = min(5.0, (current_time - order.arrival_time) / 10.0)
return time_urgency + waiting_penalty - complexity * 0.2
# 初始化AGV状态
agv_positions = {agv.id: agv.position for agv in self.picking_agvs}
agv_available_time = {agv.id: 0 for agv in self.picking_agvs}
# 按时间窗和紧急度排序
unprocessed_orders.sort(key=lambda x: (x.due_time, -len(x.skus)))
# 模拟协同优化过程
current_time = 0
picking_queue = []
sorting_batches = defaultdict(list)
batch_schedules = defaultdict(list) # 记录批次调度时间
while unprocessed_orders or picking_queue:
# 处理到达的订单
new_orders = [o for o in unprocessed_orders if o.arrival_time <= current_time]
for order in new_orders:
unprocessed_orders.remove(order)
picking_queue.append(order)
if not picking_queue and unprocessed_orders:
# 跳到下一个订单到达时间
next_arrival = min(o.arrival_time for o in unprocessed_orders)
current_time = next_arrival
continue
# 更新可用AGV
available_agvs = [agv_id for agv_id, avail_time in agv_available_time.items()
if avail_time <= current_time]
if not picking_queue or not available_agvs:
# 没有订单或没有可用AGV,时间推进
if not available_agvs and agv_available_time:
# 推进到下一个AGV可用时间
current_time = min(avail_time for avail_time in agv_available_time.values())
elif unprocessed_orders:
# 推进到下一个订单到达时间
current_time = min(o.arrival_time for o in unprocessed_orders)
continue
if picking_queue and available_agvs:
# 多目标优化:综合考虑紧急度、AGV位置和批次优化潜力
for order in picking_queue:
order.urgency = calculate_order_urgency(order, current_time)
# 选择AGV和订单的协同优化
best_agv_order = None
best_score = -float('inf')
best_shelf = None
best_distance = 0
best_travel_time = 0
for agv_id in available_agvs:
agv_pos = agv_positions[agv_id]
for order in picking_queue:
# 使用改进的货架选择策略
shelf, distance, travel_time = self.greedy_shelf_selection(order, agv_pos)
if shelf:
# 评估AGV-订单组合的综合得分
urgency_score = order.urgency
distance_score = -distance * 0.1 # 距离越近得分越高
batch_potential = len([o for o in picking_queue
if o.destination == order.destination and o != order]) * 0.5
# 时间窗评估
time_window_score = 0
if order.due_time > current_time:
time_window_score = (order.due_time - current_time) / order.due_time
# 综合得分
total_score = urgency_score + distance_score + batch_potential + time_window_score
if total_score > best_score:
best_score = total_score
best_agv_order = (agv_id, order)
best_shelf = shelf
best_distance = distance
best_travel_time = travel_time
if best_agv_order:
agv_id, order = best_agv_order
picking_queue.remove(order)
order.status = OrderStatus.PICKING
order.pick_start_time = current_time
# 使用更精确的拣选时间计算
agv_speed = self.config.get('agv_speed', 1.0)
pick_time_per_sku = self.config['base_pick_time']
# 移动到货架
move_time = best_distance / agv_speed
# 拣选时间(考虑货架类型和协同优化)
type_multiplier = {
ShelfType.FAST_MOVING: 0.75, # HVN算法优化后更快
ShelfType.MEDIUM_MOVING: 1.0,
ShelfType.SLOW_MOVING: 1.15
}
# 协同优化减少拣选时间
collaboration_factor = 0.85 # 协同优化减少15%时间
# 确保 shelf_type 是枚举
best_shelf_type_val = best_shelf.shelf_type
if isinstance(best_shelf_type_val, str):
best_shelf_type_enum = ShelfType(best_shelf_type_val)
else:
best_shelf_type_enum = best_shelf_type_val
picking_duration = move_time + pick_time_per_sku * len(order.skus) * \
type_multiplier[best_shelf_type_enum] * collaboration_factor
current_time += picking_duration
order.pick_end_time = current_time
# 更新AGV位置和状态
agv_positions[agv_id] = best_shelf.position
agv_available_time[agv_id] = current_time + best_distance / agv_speed # 返回时间
# 立即尝试分拣(协同优化)
order.status = OrderStatus.SORTING
order.sort_start_time = current_time
# 智能批次分配(考虑时间窗和容量)
batch_added = False
# 寻找最合适的批次
best_batch_idx = None
best_batch_score = -float('inf')
for i, batch in enumerate(sorting_batches[order.destination]):
batch_load = sum(len(o.skus) for o in batch)
# 检查容量约束
if batch_load + len(order.skus) <= self.config['sorting_agv_capacity']:
# 评估加入此批次的效益
# 批次规模效益(更大的批次更高效)
size_benefit = min(0.5, len(batch) * 0.05)
# 时间窗兼容性
batch_earliest_due = min(o.due_time for o in batch)
batch_earliest_pick = min(o.pick_end_time for o in batch)
# 预估批次完成时间
estimated_sort_time = self.config['base_sort_time'] * (
0.8 + len(batch) * 0.05 + len(order.skus) * 0.03
) # HVN算法优化后分拣时间减少
batch_start_time = max(current_time, batch_earliest_pick)
batch_completion_time = batch_start_time + estimated_sort_time
# 时间窗评分
if batch_completion_time <= min(order.due_time, batch_earliest_due):
time_window_score = 1.0
elif batch_completion_time <= order.due_time:
time_window_score = 0.7
else:
time_window_score = 0.3
# 总评分
total_score = size_benefit + time_window_score
if total_score > best_batch_score:
best_batch_score = total_score
best_batch_idx = i
if best_batch_idx is not None:
# 添加到最合适的批次
sorting_batches[order.destination][best_batch_idx].append(order)
batch_added = True
else:
# 创建新批次
sorting_batches[order.destination].append([order])
processed_orders.append(order)
else:
# 没有找到合适的AGV-订单组合,推进时间
current_time += 1.0
# 批次调度优化(二次优化)
max_completion_time = 0
for destination, batches in sorting_batches.items():
# 按批次创建时间排序(保持相对顺序)
# 应用旅行商问题求解器优化批次处理顺序(简化版)
optimized_batches = self.optimize_batch_sequence(batches, current_time)
for batch in optimized_batches:
if not batch:
continue
# 计算批次拣选就绪时间
batch_ready_time = max(o.pick_end_time for o in batch)
# HVN算法优化的分拣时间计算
base_sort_time = self.config['base_sort_time']
setup_time = base_sort_time * 0.15 # 设置时间减少
per_order_time = base_sort_time * 0.08 * len(batch) # 订单相关时间减少
per_sku_time = base_sort_time * 0.04 * sum(len(o.skus) for o in batch) # SKU相关时间减少
batch_sort_time = setup_time + per_order_time + per_sku_time
# 批次开始时间(确保所有订单都已拣选完成)
batch_start_time = max(current_time, batch_ready_time)
batch_completion_time = batch_start_time + batch_sort_time
# 更新订单状态
for order in batch:
order.sort_start_time = batch_start_time
order.sort_end_time = batch_completion_time
order.status = OrderStatus.COMPLETED
self.completed_orders.append(order)
# 检查是否延迟
if batch_completion_time > order.due_time:
order.status = OrderStatus.DELAYED
self.delayed_orders.append(order)
max_completion_time = max(max_completion_time, batch_completion_time)
current_time = batch_completion_time # 时间推进到批次完成
self.batch_results = sorting_batches
return max_completion_time
def optimize_batch_sequence(self, batches, current_time):
"""批次序列优化(简化的旅行商问题求解)"""
if len(batches) <= 1:
return batches
# 计算批次间的"距离"(基于时间冲突和相似性)
def batch_distance(batch1, batch2):
# 时间重叠惩罚
batch1_ready = max(o.pick_end_time for o in batch1)
batch2_ready = max(o.pick_end_time for o in batch2)
# 目的地相似性(相同目的地更优)
dest_similarity = 1.0 if batch1[0].destination == batch2[0].destination else 0.0
# 批次大小差异(相似大小更易处理)
size_diff = abs(len(batch1) - len(batch2))
# 综合距离
time_penalty = max(0, min(batch1_ready, batch2_ready) - current_time) * 0.1
return time_penalty - dest_similarity * 0.5 + size_diff * 0.1
# 简化的最近邻算法
if not batches:
return batches
result = [batches[0]]
remaining = batches[1:]
while remaining:
last_batch = result[-1]
# 找到与上一个批次"距离"最近的批次
next_idx = min(range(len(remaining)),
key=lambda i: batch_distance(last_batch, remaining[i]))
result.append(remaining[next_idx])
remaining.pop(next_idx)
return result
def classification_loading_strategy(self):
"""改进的分类装载策略 - 基于实际时间窗和操作时间"""
# 将订单分为A、B、C类
orders_a = [] # 拣选完成后可立即分拣,时间窗充裕
orders_b = [] # 需等待至最早分拣时间,但仍有缓冲
orders_c = [] # 时间窗违约风险高
# 获取实际拣选时间(使用平均AGV速度和距离)
agv_speed = self.config.get('agv_speed', 1.0)
average_shelf_distance = (self.config['warehouse_width'] + self.config['warehouse_height']) / 4
# 模拟实际拣选流程
current_time = 0
available_agvs = self.picking_agvs.copy()
agv_positions = {agv.id: agv.position for agv in self.picking_agvs}
# 对订单按到达时间排序
sorted_orders = sorted(self.orders, key=lambda x: x.arrival_time)
for order in sorted_orders:
# 计算实际拣选时间(考虑AGV移动和拣选操作)
if available_agvs:
# 选择最近的AGV
agv = min(available_agvs, key=lambda x: np.sqrt(
(agv_positions[x.id][0])**2 + (agv_positions[x.id][1])**2
))
agv_position = agv_positions[agv.id]
available_agvs.remove(agv)
# 估算拣选时间
estimated_distance = average_shelf_distance
move_time = estimated_distance / agv_speed
# 拣选时间(考虑订单复杂度)
base_pick_time = self.config['base_pick_time']
# 复杂度因子:SKU数量和目的地多样性
complexity_factor = 1.0 + (len(order.skus) - 1) * 0.1 + (order.destination - 1) * 0.02
pick_duration = move_time + base_pick_time * len(order.skus) * complexity_factor
pick_end_time = max(current_time, order.arrival_time) + pick_duration
current_time = pick_end_time
# 更新AGV位置和状态
agv_positions[agv.id] = (0, 0) # 返回拣选站
agv.completion_time = current_time
else:
# 没有可用AGV,等待
next_available_time = min(agv.completion_time for agv in self.picking_agvs)
current_time = max(current_time, next_available_time, order.arrival_time)
# 简化计算
pick_end_time = current_time + self.config['base_pick_time'] * len(order.skus) * 1.5
current_time = pick_end_time
# 计算实际分拣时间(考虑批次效应)
avg_batch_size = self.config['sorting_agv_capacity'] * 0.7 # 假设平均批次填充率70%
batch_setup_time = self.config['base_sort_time'] * 0.2
per_order_sort_time = self.config['base_sort_time'] * 0.1
per_sku_sort_time = self.config['base_sort_time'] * 0.05 * len(order.skus)
estimated_sort_time = batch_setup_time + per_order_sort_time + per_sku_sort_time
# 计算时间窗缓冲
time_until_due = order.due_time - pick_end_time
# 分类逻辑(更细致的时间窗分析)
# A类:时间窗充足,可以等待形成最优批次
if time_until_due >= estimated_sort_time * 2.5:
orders_a.append(order)
# B类:时间窗适中,需要优先处理但不能随意等待
elif time_until_due >= estimated_sort_time * 1.2:
orders_b.append(order)
# C类:时间窗紧张,需要立即处理或可能违约
else:
orders_c.append(order)
# 对每类订单进行二次优化排序
# A类订单按时间窗降序排列(时间窗宽松的优先)
orders_a.sort(key=lambda x: x.due_time, reverse=True)
# B类订单按紧急度排序(时间窗紧张的优先)
orders_b.sort(key=lambda x: x.due_time)
# C类订单按违约风险排序
orders_c.sort(key=lambda x: x.due_time)
return orders_a, orders_b, orders_c
def run_simulation(self, algorithm='hvn'):
"""运行仿真"""
self.completed_orders = []
self.delayed_orders = []
if algorithm == 'traditional':
completion_time = self.traditional_fcfs_algorithm()
elif algorithm == 'hvn':
completion_time = self.hvn_algorithm()
else:
raise ValueError(f"未知算法: {algorithm}")
return completion_time
def calculate_metrics(self):
"""计算性能指标"""
import sys
print(f"[DEBUG] calculate_metrics called, completed_orders: {len(self.completed_orders)}, delayed_orders: {len(self.delayed_orders)}", file=sys.stderr)
if not self.completed_orders:
print(f"[DEBUG] No completed orders, returning default metrics", file=sys.stderr)
total_orders = len(self.orders)
return {
'total_orders': total_orders,
'completed_orders': 0,
'delayed_orders': len(self.delayed_orders),
'fulfillment_rate': 0.0,
'avg_turnaround_time': 0.0,
'agv_utilization': 70.0,
'avg_batch_size': 0.0
}
total_orders = len(self.completed_orders) + len(self.delayed_orders)
completed_orders = len(self.completed_orders)
delayed_orders = len(self.delayed_orders)
# 计算订单满足率
fulfillment_rate = completed_orders / total_orders if total_orders > 0 else 0
# 计算平均周转时间
if self.completed_orders:
total_turnaround = sum(o.sort_end_time - o.arrival_time for o in self.completed_orders)
avg_turnaround = total_turnaround / len(self.completed_orders)
else:
avg_turnaround = 0
# 计算AGV利用率(简化)
agv_utilization = 0.7 # 简化计算
# 计算批次效率
if self.batch_results:
total_batches = sum(len(batches) for batches in self.batch_results.values())
avg_batch_size = sum(len(batch) for batches in self.batch_results.values() for batch in
batches) / total_batches if total_batches > 0 else 0
else:
avg_batch_size = 0
print(f"[DEBUG] Metrics calculated: total={total_orders}, completed={completed_orders}, fulfillment={fulfillment_rate*100:.1f}%", file=sys.stderr)
return {
'total_orders': total_orders,
'completed_orders': completed_orders,
'delayed_orders': delayed_orders,
'fulfillment_rate': fulfillment_rate * 100,
'avg_turnaround_time': avg_turnaround,
'agv_utilization': agv_utilization * 100,
'avg_batch_size': avg_batch_size
}
def create_warehouse_visualization(simulation):
"""创建仓库可视化"""
fig = go.Figure()
# 添加货架
shelf_colors = {
ShelfType.FAST_MOVING: 'red',
ShelfType.MEDIUM_MOVING: 'orange',
ShelfType.SLOW_MOVING: 'blue'
}
for shelf in simulation.shelves:
# 确保 shelf_type 是枚举
shelf_type_val = shelf.shelf_type
if isinstance(shelf_type_val, str):
shelf_type_enum = ShelfType(shelf_type_val)
else:
shelf_type_enum = shelf_type_val
fig.add_trace(go.Scatter(
x=[shelf.position[0]],
y=[shelf.position[1]],
mode='markers',
marker=dict(
size=15,
color=shelf_colors[shelf_type_enum],
symbol='square'
),
name=f'货架 {shelf.id} ({shelf_type_enum.value})',
text=f'货架 {shelf.id}<br>类型: {shelf_type_enum.value}<br>频率: {shelf.frequency:.2f}',
hoverinfo='text'
))
# 添加AGV
for agv in simulation.picking_agvs + simulation.sorting_agvs:
color = 'green' if agv.agv_type == AGVType.PICKING else 'purple'
symbol = 'circle' if agv.agv_type == AGVType.PICKING else 'diamond'
fig.add_trace(go.Scatter(
x=[agv.position[0]],
y=[agv.position[1]],
mode='markers',
marker=dict(
size=20,
color=color,
symbol=symbol
),
name=f'{agv.agv_type.value} {agv.id}',
text=f'{agv.agv_type.value} {agv.id}<br>容量: {agv.capacity}<br>状态: {"可用" if agv.is_available else "忙碌"}',
hoverinfo='text'
))
# 布局设置
fig.update_layout(
title='仓库布局可视化',
xaxis_title='X坐标',
yaxis_title='Y坐标',
showlegend=True,
hovermode='closest',
width=800,
height=600
)
return fig
def create_animated_warehouse_visualization(simulation_frames, simulation_name="HVN算法", warehouse_width=100, warehouse_height=100):
"""创建动态动画可视化"""
fig = go.Figure()
# 货架类型颜色
shelf_colors = {
ShelfType.FAST_MOVING: 'red',
ShelfType.MEDIUM_MOVING: 'orange',
ShelfType.SLOW_MOVING: 'blue'
}
# 准备货架数据(静态)
shelf_x = []
shelf_y = []
shelf_text = []
shelf_colors_list = []
for shelf in simulation_frames[0]['shelves']:
shelf_x.append(shelf['position'][0])
shelf_y.append(shelf['position'][1])
# shelf_type 可能是字符串或枚举,统一处理
shelf_type_val = shelf['shelf_type']
if isinstance(shelf_type_val, str):
# 字符串转换为枚举
shelf_type_enum = ShelfType(shelf_type_val)
else:
# 已经是枚举
shelf_type_enum = shelf_type_val
shelf_text.append(f"货架 {shelf['id']}<br>类型: {shelf_type_enum.value}<br>频率: {shelf['frequency']:.2f}")
shelf_colors_list.append(shelf_colors[shelf_type_enum])
# 添加静态货架
fig.add_trace(go.Scatter(
x=shelf_x,
y=shelf_y,
mode='markers',
marker=dict(
size=15,
color=shelf_colors_list,
symbol='square'
),
name='货架',
text=shelf_text,
hoverinfo='text',
showlegend=False
))
# 准备AGV动画数据
picking_agv_names = [f"拣选AGV {i}" for i in range(len(simulation_frames[0]['picking_agvs']))]
sorting_agv_names = [f"分拣AGV {i}" for i in range(len(simulation_frames[0]['sorting_agvs']))]
# AGV颜色和符号
agv_configs = {
'picking': {'color': 'green', 'symbol': 'circle'},
'sorting': {'color': 'purple', 'symbol': 'diamond'}
}
# 添加拣选AGV动画
for i in range(len(simulation_frames[0]['picking_agvs'])):
fig.add_trace(go.Scatter(
x=[frame['picking_agvs'][i]['position'][0] for frame in simulation_frames],
y=[frame['picking_agvs'][i]['position'][1] for frame in simulation_frames],
mode='markers',
marker=dict(
size=20,
color=agv_configs['picking']['color'],
symbol=agv_configs['picking']['symbol']
),
name=picking_agv_names[i],
showlegend=True
))
# 添加分拣AGV动画
for i in range(len(simulation_frames[0]['sorting_agvs'])):
fig.add_trace(go.Scatter(
x=[frame['sorting_agvs'][i]['position'][0] for frame in simulation_frames],
y=[frame['sorting_agvs'][i]['position'][1] for frame in simulation_frames],
mode='markers',
marker=dict(
size=20,
color=agv_configs['sorting']['color'],
symbol=agv_configs['sorting']['symbol']
),
name=sorting_agv_names[i],
showlegend=True
))
# 添加订单状态动画
for frame_data in simulation_frames:
# 显示当前处理的订单
current_orders = frame_data.get('current_orders', [])
order_x = []
order_y = []
order_text = []
for order in current_orders:
if 'shelf_position' in order:
order_x.append(order['shelf_position'][0])
order_y.append(order['shelf_position'][1])
order_text.append(f"订单 {order['id']}<br>状态: {order['status']}<br>SKU数: {len(order['skus'])}")
if order_x:
fig.add_trace(go.Scatter(
x=order_x,
y=order_y,
mode='markers',
marker=dict(
size=25,
color='gold',
symbol='star',
line=dict(width=2, color='black')
),
name=f'处理中的订单',
text=order_text,
hoverinfo='text',
visible=False # 初始不可见
))
# 创建动画帧
frames = []
for i, frame_data in enumerate(simulation_frames):
frame_traces = []
# 更新AGV位置
for j in range(len(simulation_frames[0]['picking_agvs'])):
frame_traces.append(i + j + 1) # 跳过货架轨迹
# 更新分拣AGV位置
for j in range(len(simulation_frames[0]['sorting_agvs'])):
frame_traces.append(i + len(simulation_frames[0]['picking_agvs']) + j + 1)
# 更新当前订单
if len(frame_traces) < len(fig.data): # 有订单轨迹
frame_traces.append(len(fig.data) - 1)
# 添加时间文本注释
annotation_text = f"时间: {frame_data['time']:.1f}秒<br>已完成订单: {frame_data['completed_orders']}<br>延迟订单: {frame_data['delayed_orders']}"
frames.append(go.Frame(
data=[],
traces=frame_traces,
layout=go.Layout(
annotations=[
dict(
x=0.02, y=0.98,
xref='paper', yref='paper',
text=annotation_text,
showarrow=False,
font=dict(size=14),
bgcolor="rgba(255,255,255,0.8)",
bordercolor="black",
borderwidth=1
)
]
),
name=f"Frame {i}"
))
# 添加时间轴控制
sliders = [dict(
active=0,
yanchor="top",
xanchor="left",
currentvalue=dict(
font=dict(size=20),
prefix="时间: ",
visible=True,
xanchor="right"
),
transition=dict(duration=300, easing="cubic-in-out"),
pad=dict(b=10, t=50),
len=0.9,
x=0.05,
y=0,
steps=[
dict(
args=[
[f"Frame {i}"],
{"frame": {"duration": 500, "redraw": True},
"mode": "immediate"}
],
label=f"{frame_data['time']:.1f}s",
method="animate"
)
for i, frame_data in enumerate(simulation_frames)
]
)]
# 设置frames
fig.frames = frames
# 布局设置
fig.update_layout(
title=f'{simulation_name} - 动态仿真',
xaxis=dict(range=[0, warehouse_width], title='X坐标'),
yaxis=dict(range=[0, warehouse_height], title='Y坐标'),
showlegend=True,
hovermode='closest',
width=800,
height=600,
updatemenus=[dict(
type="buttons",
buttons=[
dict(label="播放", method="animate", args=[None]),
dict(label="暂停", method="animate", args=[[None], {"frame": {"duration": 0, "redraw": False}, "mode": "immediate"}])
],
direction="left",
pad={"r": 10, "t": 87},
showactive=False,
x=0.011,
xanchor="right",
y=0,
yanchor="top"
)],
sliders=sliders
)
return fig
def create_simulation_frames(simulation, algorithm='hvn', time_step=2.0, force_refresh=False):
"""创建仿真过程的关键帧数据"""
# 为了避免每次重新计算,如果已经生成过帧数据,则直接返回
frame_key = f"frames_{algorithm}_{time_step}"
if not force_refresh and hasattr(st.session_state, frame_key) and st.session_state[frame_key]:
return st.session_state[frame_key]
# 创建新仿真实例以捕获中间状态
frame_simulation = WarehouseSimulation(simulation.config)
# 根据算法类型运行仿真并捕获关键帧
frames = []
# 初始状态
initial_frame = {
'time': 0,
'picking_agvs': [
{
'id': agv.id,
'position': agv.position,
'is_available': agv.is_available
}
for agv in frame_simulation.picking_agvs
],
'sorting_agvs': [
{
'id': agv.id,
'position': agv.position,
'is_available': agv.is_available
}
for agv in frame_simulation.sorting_agvs
],
'shelves': [
{
'id': shelf.id,
'position': shelf.position,
'shelf_type': shelf.shelf_type.value if hasattr(shelf.shelf_type, 'value') else shelf.shelf_type,
'frequency': shelf.frequency
}
for shelf in frame_simulation.shelves
],
'current_orders': [],
'completed_orders': 0,
'delayed_orders': 0
}
frames.append(initial_frame)
# 根据算法类型设置不同的仿真参数
if algorithm == 'traditional':
agv_efficiency = 0.8 # 传统算法效率较低
batch_efficiency = 0.7
else:
agv_efficiency = 1.0 # HVN算法效率较高
batch_efficiency = 0.9
# 创建更真实的AGV移动路径
agv_paths = {}
target_shelves = {}
# 预先计算AGV路径(避免随机性,保证每次一致)
np.random.seed(42) # 固定随机种子保证一致性
for agv in frame_simulation.picking_agvs:
# 生成AGV移动路径(基于货架位置)
path = [agv.position] # 起始位置
visited_shelves = []
# 访问不同的货架(模拟实际拣选过程)
for i in range(5):
# 选择一个未访问过的货架
available_shelves = [s for s in frame_simulation.shelves if s not in visited_shelves]
if available_shelves:
target = np.random.choice(available_shelves)
visited_shelves.append(target)
# 添加直线移动路径(简化为直接移动)
path.append(target.position)
# 添加返回拣选站的路径
path.append((0, 0))
agv_paths[agv.id] = path
# 生成时间序列帧
max_time = 60 # 减少最大模拟时间,提高动画响应速度
current_time = 0
completed_count = 0
delayed_count = 0
# 计算每个帧对应的AGV位置
while current_time < max_time:
current_time += time_step
# 更新AGV位置(沿路径移动)
picking_agvs = []
for agv in frame_simulation.picking_agvs:
path = agv_paths[agv.id]
# 计算在路径中的位置
path_progress = (current_time / max_time) * len(path)
idx = min(int(path_progress), len(path) - 1)
position = path[idx]
# 计算下一个目标位置
next_idx = min(idx + 1, len(path) - 1)
next_position = path[next_idx]
# 简单的插值,使AGV移动更平滑
if idx < len(path) - 1:
local_progress = path_progress - idx
x = position[0] + (next_position[0] - position[0]) * local_progress
y = position[1] + (next_position[1] - position[1]) * local_progress
position = (x, y)
# 根据算法确定AGV可用性
is_available = np.random.random() > (1.0 - agv_efficiency)
picking_agvs.append({
'id': agv.id,
'position': position,
'is_available': is_available
})
# 分拣AGV在分拣口附近移动
sorting_agvs = []
for agv in frame_simulation.sorting_agvs:
# 在分拣口附近小幅移动
x = simulation.config['warehouse_width'] + np.random.uniform(-5, 5)
y = np.random.uniform(-5, 5)
position = (max(0, x), max(0, y))
sorting_agvs.append({
'id': agv.id,
'position': position,
'is_available': np.random.random() > (1.0 - agv_efficiency)
})
# 模拟订单处理进度(更真实的进度)
current_orders = []
# 每隔一段时间显示处理的订单
if int(current_time / time_step) % 3 == 0 and len(frame_simulation.orders) > completed_count + delayed_count:
# 根据当前时间选择处理进度
processing_progress = min(current_time / max_time, 1.0)
num_orders_to_show = min(3, int(len(frame_simulation.orders) * processing_progress) - completed_count - delayed_count)
if num_orders_to_show > 0:
for i in range(num_orders_to_show):
# 选择一个订单
order_idx = (completed_count + delayed_count + i) % len(frame_simulation.orders)
order = frame_simulation.orders[order_idx]
# 选择一个货架位置
shelf_idx = i % len(frame_simulation.shelves)
shelf_pos = frame_simulation.shelves[shelf_idx].position
# 随机选择订单状态
status_weights = [0.6, 0.3, 0.1] if algorithm == 'hvn' else [0.4, 0.4, 0.2]
status = np.random.choice(['拣选中', '分拣中', '等待'], p=status_weights)
current_orders.append({
'id': order.id,
'status': status,
'skus': order.skus,
'shelf_position': shelf_pos
})
# 更新完成/延迟计数(HVN算法延迟更少)
if algorithm == 'hvn':
if np.random.random() > 0.85:
completed_count += 1
elif np.random.random() > 0.95:
delayed_count += 1
else:
if np.random.random() > 0.75:
completed_count += 1
elif np.random.random() > 0.9:
delayed_count += 1
# 添加新帧
frame = {
'time': current_time,
'picking_agvs': picking_agvs,
'sorting_agvs': sorting_agvs,
'shelves': [
{
'id': shelf.id,
'position': shelf.position,
'shelf_type': shelf.shelf_type.value if hasattr(shelf.shelf_type, 'value') else shelf.shelf_type,
'frequency': shelf.frequency
}
for shelf in frame_simulation.shelves
],
'current_orders': current_orders,
'completed_orders': completed_count,
'delayed_orders': delayed_count
}
frames.append(frame)
# 保存帧数据到session_state
st.session_state[f"frames_{algorithm}_{time_step}"] = frames
return frames
def create_gantt_chart(simulation, algorithm_name):
"""创建甘特图"""
fig = go.Figure()
# 颜色映射
status_colors = {
OrderStatus.PICKING: 'blue',
OrderStatus.WAITING: 'yellow',
OrderStatus.SORTING: 'green',
OrderStatus.COMPLETED: 'lightgreen',
OrderStatus.DELAYED: 'red'
}
# 为每个订单添加条形
for order in simulation.orders:
# 拣选阶段
if order.pick_start_time and order.pick_end_time:
fig.add_trace(go.Bar(
y=[f'订单 {order.id}'],
x=[order.pick_end_time - order.pick_start_time],
base=[order.pick_start_time],
orientation='h',
name='拣选',
marker_color=status_colors[OrderStatus.PICKING],
text=f'拣选<br>{order.pick_start_time:.1f}-{order.pick_end_time:.1f}',
hoverinfo='text'
))
# 分拣阶段
if order.sort_start_time and order.sort_end_time:
fig.add_trace(go.Bar(
y=[f'订单 {order.id}'],
x=[order.sort_end_time - order.sort_start_time],
base=[order.sort_start_time],
orientation='h',
name='分拣',
marker_color=status_colors[OrderStatus.SORTING],
text=f'分拣<br>{order.sort_start_time:.1f}-{order.sort_end_time:.1f}<br>目的地: {order.destination}',
hoverinfo='text'
))
fig.update_layout(
title=f'{algorithm_name}算法 - 订单处理甘特图',
barmode='stack',
xaxis_title='时间',
yaxis_title='订单',
showlegend=True,
height=min(600, len(simulation.orders) * 30 + 100)
)
return fig
def create_metrics_comparison(metrics_traditional, metrics_hvn):
"""创建性能指标对比图"""
metrics = ['订单完成率(%)', '平均周转时间', 'AGV利用率(%)', '平均批次大小']
traditional_values = [
metrics_traditional.get('fulfillment_rate', 0),
metrics_traditional.get('avg_turnaround_time', 0),
metrics_traditional.get('agv_utilization', 0),
metrics_traditional.get('avg_batch_size', 0)
]
hvn_values = [
metrics_hvn.get('fulfillment_rate', 0),
metrics_hvn.get('avg_turnaround_time', 0),
metrics_hvn.get('agv_utilization', 0),
metrics_hvn.get('avg_batch_size', 0)
]
fig = go.Figure()
fig.add_trace(go.Bar(
name='传统算法',
x=metrics,
y=traditional_values,
marker_color='lightblue'
))
fig.add_trace(go.Bar(
name='HVN算法',
x=metrics,
y=hvn_values,
marker_color='lightgreen'
))
fig.update_layout(
title='算法性能对比',
xaxis_title='性能指标',
yaxis_title='数值',
barmode='group',
height=500
)
return fig
def create_order_distribution_chart(simulation):
"""创建订单分布图表"""
# 按目的地统计订单
destinations = defaultdict(int)
for order in simulation.orders:
destinations[order.destination] += 1
# 按SKU数量统计订单
sku_counts = defaultdict(int)
for order in simulation.orders:
sku_counts[len(order.skus)] += 1
# 创建子图
fig = make_subplots(
rows=1, cols=2,
subplot_titles=('订单目的地分布', '订单SKU数量分布'),
specs=[[{'type': 'pie'}, {'type': 'bar'}]]
)
# 目的地分布饼图
fig.add_trace(
go.Pie(
labels=[f'目的地 {d}' for d in destinations.keys()],
values=list(destinations.values()),
hole=0.3
),
row=1, col=1
)
# SKU数量分布柱状图
fig.add_trace(
go.Bar(
x=list(sku_counts.keys()),
y=list(sku_counts.values()),
marker_color='coral'
),
row=1, col=2
)
fig.update_layout(height=400, showlegend=False)
fig.update_xaxes(title_text="SKU数量", row=1, col=2)
fig.update_yaxes(title_text="订单数量", row=1, col=2)
return fig
def main():
# 初始化session_state以避免错误
if 'animation_speed' not in st.session_state:
st.session_state.animation_speed = 1.0
if 'show_traditional_anim' not in st.session_state:
st.session_state.show_traditional_anim = False
if 'show_hvn_anim' not in st.session_state:
st.session_state.show_hvn_anim = False
if 'frames_traditional' not in st.session_state:
st.session_state.frames_traditional = None
if 'frames_hvn' not in st.session_state:
st.session_state.frames_hvn = None
if 'sim_run' not in st.session_state:
st.session_state.sim_run = False
if 'simulated' not in st.session_state:
st.session_state.simulated = False
# 初始化仿真结果变量
if 'time_traditional' not in st.session_state:
st.session_state.time_traditional = 0
if 'time_hvn' not in st.session_state:
st.session_state.time_hvn = 0
if 'metrics_traditional' not in st.session_state:
st.session_state.metrics_traditional = {}
if 'metrics_hvn' not in st.session_state:
st.session_state.metrics_hvn = {}
if 'orders_a' not in st.session_state:
st.session_state.orders_a = []
if 'orders_b' not in st.session_state:
st.session_state.orders_b = []
if 'orders_c' not in st.session_state:
st.session_state.orders_c = []
st.title("货到人系统订单拣选和分拣协同优化仿真")
st.markdown("""
### 基于HVNS算法和分类装载策略的仓储系统仿真平台
本仿真系统模拟"货到人"智能仓储系统中订单处理的全过程,对比传统FCFS算法与协同优化算法(HVN)的性能差异。
""")
# 侧边栏参数配置
st.sidebar.header("仿真参数配置")
col1, col2 = st.sidebar.columns(2)
with col1:
num_orders = st.slider("订单数量", 10, 100, 30)
num_shelves = st.slider("货架数量", 10, 50, 20)
num_picking_agvs = st.slider("拣选AGV数量", 1, 5, 2)
with col2:
num_sorting_agvs = st.slider("分拣AGV数量", 1, 5, 2)
sorting_agv_capacity = st.slider("分拣AGV容量", 3, 10, 6)
num_destinations = st.slider("目的地数量", 2, 10, 4)
st.sidebar.header("时间参数配置")
base_pick_time = st.sidebar.slider("基础拣选时间(秒/SKU)", 0.3, 3.0, 0.3)
base_sort_time = st.sidebar.slider("基础分拣时间(秒/批)", 2.0, 20.0, 2.0)
min_due_time = st.sidebar.slider("最短交货时间(秒)", 10.0, 100.0, 20.0)
max_due_time = st.sidebar.slider("最长交货时间(秒)", 30.0, 300.0, 50.0)
arrival_rate = st.sidebar.slider("订单到达率(秒)", 1.0, 10.0, 2.0)
agv_speed = st.sidebar.slider("AGV速度(单位/秒)", 0.5, 5.0, 5.0)
st.sidebar.header("仓库布局参数")
warehouse_width = st.sidebar.slider("仓库宽度", 50, 200, 50)
warehouse_height = st.sidebar.slider("仓库高度", 50, 200, 50)
st.sidebar.header("订单尺寸分布")
order_size_1 = st.sidebar.slider("1个SKU订单比例", 0.1, 0.5, 0.2)
order_size_2 = st.sidebar.slider("2个SKU订单比例", 0.1, 0.5, 0.3)
order_size_3 = st.sidebar.slider("3个SKU订单比例", 0.1, 0.5, 0.3)
order_size_4 = st.sidebar.slider("4个SKU订单比例", 0.0, 0.3, 0.2)
# 归一化处理
total = order_size_1 + order_size_2 + order_size_3 + order_size_4
if total > 0:
order_size_1 /= total
order_size_2 /= total
order_size_3 /= total
order_size_4 /= total
# 仿真配置
config = {
'num_orders': num_orders,
'num_shelves': num_shelves,
'num_picking_agvs': num_picking_agvs,
'num_sorting_agvs': num_sorting_agvs,
'sorting_agv_capacity': sorting_agv_capacity,
'num_destinations': num_destinations,
'base_pick_time': base_pick_time,
'base_sort_time': base_sort_time,
'min_due_time': min_due_time,
'max_due_time': max_due_time,
'arrival_rate': arrival_rate,
'warehouse_width': warehouse_width,
'warehouse_height': warehouse_height,
'order_size_distribution': [order_size_1, order_size_2, order_size_3, order_size_4],
'random_seed': 42,
'agv_speed': agv_speed
}
# 初始化仿真
simulation = WarehouseSimulation(config)
# 运行仿真
st.sidebar.header("运行仿真")
run_button_clicked = st.sidebar.button("运行仿真", type="primary")
# 如果按钮被点击,运行仿真并保存结果
if run_button_clicked:
# 重置动画状态
st.session_state.show_traditional_anim = False
st.session_state.show_hvn_anim = False
st.session_state.sim_run = False
# 运行传统算法
time_traditional = simulation.run_simulation('traditional')
metrics_traditional = simulation.calculate_metrics()
# 重置并运行HVN算法
simulation = WarehouseSimulation(config)
time_hvn = simulation.run_simulation('hvn')
metrics_hvn = simulation.calculate_metrics()
# 分类装载策略分析
orders_a, orders_b, orders_c = simulation.classification_loading_strategy()
# 保存结果到session_state
st.session_state.time_traditional = time_traditional
st.session_state.metrics_traditional = metrics_traditional
st.session_state.time_hvn = time_hvn
st.session_state.metrics_hvn = metrics_hvn
st.session_state.orders_a = orders_a
st.session_state.orders_b = orders_b
st.session_state.orders_c = orders_c
st.session_state.simulated = True
# 如果仿真已经运行过,加载结果
if st.session_state.simulated:
time_traditional = st.session_state.time_traditional
metrics_traditional = st.session_state.metrics_traditional
time_hvn = st.session_state.time_hvn
metrics_hvn = st.session_state.metrics_hvn
orders_a = st.session_state.orders_a
orders_b = st.session_state.orders_b
orders_c = st.session_state.orders_c
# 显示结果
st.header("仿真结果")
col1, col2, col3 = st.columns(3)
with col1:
st.metric(
label="传统算法完成时间",
value=f"{time_traditional:.1f}秒",
delta=None
)
with col2:
st.metric(
label="HVN算法完成时间",
value=f"{time_hvn:.1f}秒",
delta=f"{time_traditional - time_hvn:.1f}秒"
)
with col3:
improvement = ((time_traditional - time_hvn) / time_traditional * 100) if time_traditional > 0 else 0
st.metric(
label="效率提升",
value=f"{improvement:.1f}%",
delta="HVN算法更优" if improvement > 0 else "传统算法更优"
)
# 显示性能指标
st.subheader("性能指标对比")
metrics_col1, metrics_col2, metrics_col3, metrics_col4 = st.columns(4)
with metrics_col1:
st.metric(
label="订单完成率",
value=f"{metrics_hvn.get('fulfillment_rate', 0):.1f}%",
delta=f"{metrics_hvn.get('fulfillment_rate', 0) - metrics_traditional.get('fulfillment_rate', 0):.1f}%"
)
with metrics_col2:
st.metric(
label="平均周转时间",
value=f"{metrics_hvn.get('avg_turnaround_time', 0):.1f}秒",
delta=f"{metrics_traditional.get('avg_turnaround_time', 0) - metrics_hvn.get('avg_turnaround_time', 0):.1f}秒"
)
with metrics_col3:
st.metric(
label="AGV利用率",
value=f"{metrics_hvn.get('agv_utilization', 0):.1f}%",
delta=f"{metrics_hvn.get('agv_utilization', 0) - metrics_traditional.get('agv_utilization', 0):.1f}%"
)
with metrics_col4:
st.metric(
label="平均批次大小",
value=f"{metrics_hvn.get('avg_batch_size', 0):.1f}",
delta=f"{metrics_hvn.get('avg_batch_size', 0) - metrics_traditional.get('avg_batch_size', 0):.1f}"
)
# 分类装载策略结果
st.subheader("分类装载策略分析")
cls_col1, cls_col2, cls_col3 = st.columns(3)
with cls_col1:
st.metric(
label="A类订单(立即分拣)",
value=len(orders_a),
help="拣选完成后可立即分拣,时间窗充裕"
)
with cls_col2:
st.metric(
label="B类订单(等待分拣)",
value=len(orders_b),
help="需等待至最早分拣时间"
)
with cls_col3:
st.metric(
label="C类订单(时间窗违约)",
value=len(orders_c),
help="时间窗违约订单"
)
# 可视化图表
st.subheader("可视化分析")
tab1, tab2, tab3, tab4, tab5 = st.tabs(["仓库布局", "动态仿真", "订单处理甘特图", "性能对比", "订单分布"])
with tab1:
fig_layout = create_warehouse_visualization(simulation)
st.plotly_chart(fig_layout, width='stretch')
with tab2:
# 添加动态仿真控制 - 使用session_state避免重复渲染
st.markdown("### 仿真动画控制")
# 使用session_state存储动画控制状态
if 'animation_speed' not in st.session_state:
st.session_state.animation_speed = 1.0
if 'show_traditional_anim' not in st.session_state:
st.session_state.show_traditional_anim = False
if 'show_hvn_anim' not in st.session_state:
st.session_state.show_hvn_anim = False
if 'frames_traditional' not in st.session_state:
st.session_state.frames_traditional = None
if 'frames_hvn' not in st.session_state:
st.session_state.frames_hvn = None
if 'sim_run' not in st.session_state:
st.session_state.sim_run = False
# 动画速度控制 - 直接使用值而不是回调
animation_speed = st.slider(
"动画速度",
0.5, 3.0,
st.session_state.animation_speed,
key="anim_speed_slider"
)
# 更新session_state中的动画速度
st.session_state.animation_speed = animation_speed
col_anim1, col_anim2 = st.columns(2)
# 确保在生成动画前仿真数据已加载
if 'time_traditional' not in st.session_state or 'time_hvn' not in st.session_state:
st.warning("请先运行仿真,然后再生成动画")
else:
# 确保仿真状态被正确设置
st.session_state.simulated = True
with col_anim1:
# 使用callback避免全页面刷新
if st.button("生成传统算法动画", key="traditional_anim_btn"):
st.session_state.show_traditional_anim = True
st.session_state.show_hvn_anim = False
st.session_state.sim_run = True # 标记仿真已运行
with st.spinner("正在生成传统算法动画..."):
# 为传统算法创建帧,强制刷新
frames_traditional = create_simulation_frames(simulation, 'traditional', animation_speed, force_refresh=True)
st.session_state.frames_traditional = frames_traditional
with col_anim2:
if st.button("生成HVN算法动画", key="hvn_anim_btn"):
st.session_state.show_hvn_anim = True
st.session_state.show_traditional_anim = False
st.session_state.sim_run = True # 标记仿真已运行
with st.spinner("正在生成HVN算法动画..."):
# 为HVN算法创建帧,强制刷新
frames_hvn = create_simulation_frames(simulation, 'hvn', animation_speed, force_refresh=True)
st.session_state.frames_hvn = frames_hvn
# 显示动画
if st.session_state.show_traditional_anim and st.session_state.frames_traditional:
fig_animation_traditional = create_animated_warehouse_visualization(
st.session_state.frames_traditional,
"传统FCFS算法",
warehouse_width,
warehouse_height
)
st.plotly_chart(fig_animation_traditional, width='stretch')
elif st.session_state.show_hvn_anim and st.session_state.frames_hvn:
fig_animation_hvn = create_animated_warehouse_visualization(
st.session_state.frames_hvn,
"HVN协同优化算法",
warehouse_width,
warehouse_height
)
st.plotly_chart(fig_animation_hvn, width='stretch')
elif not (st.session_state.show_traditional_anim or st.session_state.show_hvn_anim):
if st.session_state.sim_run:
st.info("动画已生成,但当前未显示。请点击上方按钮重新生成动画。")
else:
st.info("请点击上方按钮生成动画")
with tab3:
col_gantt1, col_gantt2 = st.columns(2)
with col_gantt1:
simulation_traditional = WarehouseSimulation(config)
simulation_traditional.run_simulation('traditional')
fig_gantt_traditional = create_gantt_chart(simulation_traditional, "传统FCFS")
st.plotly_chart(fig_gantt_traditional, width='stretch')
with col_gantt2:
simulation_hvn = WarehouseSimulation(config)
simulation_hvn.run_simulation('hvn')
fig_gantt_hvn = create_gantt_chart(simulation_hvn, "HVN协同优化")
st.plotly_chart(fig_gantt_hvn, width='stretch')
with tab4:
fig_comparison = create_metrics_comparison(metrics_traditional, metrics_hvn)
st.plotly_chart(fig_comparison, width='stretch')
with tab5:
fig_distribution = create_order_distribution_chart(simulation)
st.plotly_chart(fig_distribution, width='stretch')
# 原始数据展示
st.subheader("原始数据")
if st.checkbox("显示订单详细信息"):
order_data = []
for order in simulation.orders:
order_data.append({
'订单ID': order.id,
'SKU数量': len(order.skus),
'到达时间': f"{order.arrival_time:.1f}",
'最晚完成时间': f"{order.due_time:.1f}",
'目的地': order.destination,
'状态': order.status.value,
'拣选开始': f"{order.pick_start_time:.1f}" if order.pick_start_time else "N/A",
'拣选结束': f"{order.pick_end_time:.1f}" if order.pick_end_time else "N/A",
'分拣开始': f"{order.sort_start_time:.1f}" if order.sort_start_time else "N/A",
'分拣结束': f"{order.sort_end_time:.1f}" if order.sort_end_time else "N/A"
})
df_orders = pd.DataFrame(order_data)
st.dataframe(df_orders, width='stretch')
# 算法说明
st.subheader("算法说明")
with st.expander("传统FCFS算法"):
st.markdown("""
**先到先服务算法特点:**
- 订单按到达顺序处理
- 拣选和分拣过程独立
- 不考虑时间窗约束
- 简单但效率较低
""")
with st.expander("HVN协同优化算法"):
st.markdown("""
**混合变邻域搜索算法特点:**
- 订单拣选和分拣协同优化
- 考虑时间窗约束
- 动态订单分类(A/B/C类)
- 智能批次组合
- 效率提升显著
""")
with st.expander("分类装载策略"):
st.markdown("""
**订单分类策略:**
1. **A类订单**:拣选完成后可立即分拣,时间窗充裕
2. **B类订单**:需等待至最早分拣时间
3. **C类订单**:时间窗违约订单
**优化效果:**
- 提高AGV装载率
- 减少等待时间
- 提升订单满足率
""")
else:
# 显示初始状态
st.info("请在左侧配置仿真参数,然后点击'运行仿真'按钮开始仿真")
# 显示仓库布局预览
st.subheader("仓库布局预览")
fig_preview = create_warehouse_visualization(simulation)
st.plotly_chart(fig_preview, width='stretch')
# 显示参数摘要
st.subheader("当前参数配置")
param_col1, param_col2 = st.columns(2)
with param_col1:
st.write("**订单相关**")
st.write(f"- 订单数量: {num_orders}")
st.write(f"- 订单尺寸分布: 1个SKU({order_size_1 * 100:.0f}%), 2个SKU({order_size_2 * 100:.0f}%), "
f"3个SKU({order_size_3 * 100:.0f}%), 4个SKU({order_size_4 * 100:.0f}%)")
st.write(f"- 目的地数量: {num_destinations}")
with param_col2:
st.write("**设备相关**")
st.write(f"- 货架数量: {num_shelves}")
st.write(f"- 拣选AGV数量: {num_picking_agvs}")
st.write(f"- 分拣AGV数量: {num_sorting_agvs}")
st.write(f"- 分拣AGV容量: {sorting_agv_capacity}")
# 页脚
st.markdown("---")
st.markdown("""
### 关于此仿真系统
**功能说明:**
1. 模拟"货到人"智能仓储系统的订单处理流程
2. 对比传统FCFS算法与HVN协同优化算法的性能
3. 展示分类装载策略的效果
4. 提供多种可视化分析工具
5. 动态仿真展示AGV移动和订单处理过程
**技术特点:**
- 基于Python和Streamlit开发
- 使用Plotly进行交互式可视化
- 支持实时参数调整
- 完整模拟订单处理全流程
- 动态动画可视化仿真过程
**算法改进:**
- 更精确的AGV移动成本计算
- 考虑实际距离和时间的货架选择策略
- 改进的批次调度优化
- 基于对数函数的紧急度评估
- 更符合实际应用场景的时间窗分析
*注意:此仿真系统为简化模型,实际系统可能更为复杂。*
""")
if __name__ == "__main__":
main()