Skip to content

Instantly share code, notes, and snippets.

@lpe234
Created June 27, 2024 06:58
Show Gist options
  • Save lpe234/7eee7fc1a70d1d707b7e899efbf03a52 to your computer and use it in GitHub Desktop.
Save lpe234/7eee7fc1a70d1d707b7e899efbf03a52 to your computer and use it in GitHub Desktop.
基于SimPy离散事件模拟,实现简易仓库系统模拟

基于SimPy离散事件模拟,实现简易仓库系统模拟

离散事件模拟将系统随时间的变化抽象成一系列的离散时间点上的事件,通过按照事件时间顺序处理事件来演进,是一种事件驱动的仿真世界观。离散事件仿真将系统的变化看做一个事件,因此系统任何的变化都只能是通过处理相应的事件来实现,在两个相邻的事件之间,系统状态维持前一个事件发生后的状态不变。

0 概述

SimPy 是一个用于在 Python 中进行离散事件模拟的库。它提供了一个简单而强大的框架,用于模拟各种系统,例如:

  • 生产线: 模拟生产线上的机器、工人和产品流动。
  • 网络: 模拟网络中的路由器、交换机和数据包传输。
  • 交通系统: 模拟交通灯、车辆和道路网络。
  • 计算机系统: 模拟 CPU、内存、磁盘和程序执行。

文档:simpy.readthedocs

1 目标

我们将要实现以下功能的模拟:

  • 有一个仓库,里面会有若干个巷道
    • 每个巷道,分左右两部分 (假设所有巷道的货架规格相同)
    • 每个巷道一个机器人 (机器人在出口位置,等待执行指令,执行完毕后将货物放置在出口)
  • 现在会给这个仓库发送一些取货指令 (不考虑货格货物状态,假设货物无限)
    • 仓库接收到指令后,会根据巷道编号 分配给具体的巷道
    • 巷道会将指令转发给机器人
    • 机器人启动执行任务,取出货物后,再送至出口等待下一条任务
  • 耗时按货物所处的层、列,乘以各自的系数得到

2 抽象

2.1 对象

主要考虑对象的 name名称,env仿真环境

class Object(object):
    name: str = None
    env: Environment = None

2.2 系统

一个系统:

  • 会有startstop两个开关操作。
  • 同时,还能接收外部的push_task推送任务操作。
  • 然后将任务存储至自身的task_list任务池中.
  • 启动后,会不断地从任务池中,choose_task选取任务来run执行。
  • 执行完后,再根据一定的(业务)规则,push_task②推送至其他系统中。
class System(Object, abc.ABC):
    """
    系统
    """

    def __init__(self, name: str, env: Environment):
        super().__init__(name, env)
        self.task_list = []
        self.running = False

    def push_task(self, task: Task):
        logger.debug(f'{self.name} 接收到任务 {task.name}')
        self.task_list.append(task)

    def run(self):
        while self.running:
            if not self.task_list:
                yield self.env.timeout(1)
                continue
            task = self.choose_task()
            # 处理该任务
            yield self.env.process(self.run_task(task))

    def choose_task(self) -> Union[Task, None]:
        """
        当任务列表有任务时,根据一定的策略从任务列表选取一个出来
        :return:
        """
        if self.task_list:
            return self.task_list.pop(0)
        return None

    @abc.abstractmethod
    def run_task(self, task: Task):
        """
        具体的任务执行
        :param task:
        :return:
        """
        pass

    def start(self):
        logger.debug(f'{self.name} 开始启动')
        self.running = True
        self.env.process(self.run())

    def stop(self):
        self.running = False
        logger.debug(f'{self.name} 停止运动')

3 建模

3.1 位置

主要有巷道、方向、以及几层几列。

class Position(object):
    lane: int = None
    direction: Direction = None
    layer: int = None
    column: int = None

3.2 任务

主要有任务名称、位置,以及一个辅助字段: 任务状态。

class Task(object):
    def __init__(self, tid: str, name: str, position: Position):
        self.tid = tid
        self.name = name
        self.position = position
        self.status = TaskStatus.READY

3.3 仓库

根据配置信息,生成巷道数据

class Warehouse(System):
    def __init__(self, name: str, env: Environment):
        super().__init__(name, env)
        self.shelf_list: list[Shelf] = []
        self.__init()

3.4 巷道

主要关联机器人。(理论上两个Shelf组成一个巷道。实际并不会对仿真产生影响,故简化)

class Shelf(System):
    def __init__(self, name: str, robot: Robot, env: Environment):
        super().__init__(name, env)
        self.env = env
        self.robot = robot

3.5 机器人

主要用来执行任务。在执行任务过程中,核心点为: 资源的占用耗时的计算

class Robot(System):
    def __init__(self, name: str, env: Environment):
        super().__init__(name, env)
        self.resource = Resource(env)

4 运行测试

2024-06-27 14:55:09.618 | DEBUG    | __main__:start:129 - 机器人_01 开始启动
2024-06-27 14:55:09.618 | DEBUG    | __main__:start:129 - 货架_1 开始启动
2024-06-27 14:55:09.618 | DEBUG    | __main__:start:129 - 机器人_02 开始启动
2024-06-27 14:55:09.618 | DEBUG    | __main__:start:129 - 货架_2 开始启动
2024-06-27 14:55:09.618 | DEBUG    | __main__:start:129 - 机器人_03 开始启动
2024-06-27 14:55:09.619 | DEBUG    | __main__:start:129 - 货架_3 开始启动
2024-06-27 14:55:09.619 | DEBUG    | __main__:start:129 - 机器人_04 开始启动
2024-06-27 14:55:09.619 | DEBUG    | __main__:start:129 - 货架_4 开始启动
2024-06-27 14:55:09.619 | DEBUG    | __main__:start:129 - 机器人_05 开始启动
2024-06-27 14:55:09.619 | DEBUG    | __main__:start:129 - 货架_5 开始启动
2024-06-27 14:55:09.619 | DEBUG    | __main__:start:129 - 仓库 开始启动
2024-06-27 14:55:09.619 | DEBUG    | __main__:push_task:98 - 仓库 接收到任务 任务_01
2024-06-27 14:55:09.619 | DEBUG    | __main__:push_task:98 - 仓库 接收到任务 任务_02
2024-06-27 14:55:09.619 | DEBUG    | __main__:push_task:98 - 仓库 接收到任务 任务_03
2024-06-27 14:55:09.619 | DEBUG    | __main__:push_task:98 - 仓库 接收到任务 任务_04
2024-06-27 14:55:09.619 | DEBUG    | __main__:push_task:98 - 仓库 接收到任务 任务_05
2024-06-27 14:55:09.619 | DEBUG    | __main__:push_task:98 - 仓库 接收到任务 任务_06
2024-06-27 14:55:09.619 | DEBUG    | __main__:push_task:98 - 仓库 接收到任务 任务_07
2024-06-27 14:55:09.619 | DEBUG    | __main__:push_task:98 - 仓库 接收到任务 任务_08
2024-06-27 14:55:09.619 | DEBUG    | __main__:push_task:98 - 仓库 接收到任务 任务_09
2024-06-27 14:55:09.619 | DEBUG    | __main__:push_task:98 - 仓库 接收到任务 任务_10
2024-06-27 14:55:09.619 | DEBUG    | __main__:push_task:98 - 货架_3 接收到任务 任务_01
2024-06-27 14:55:09.619 | DEBUG    | __main__:push_task:98 - 货架_3 接收到任务 任务_02
2024-06-27 14:55:09.619 | DEBUG    | __main__:push_task:98 - 货架_3 接收到任务 任务_03
2024-06-27 14:55:09.619 | DEBUG    | __main__:push_task:98 - 货架_5 接收到任务 任务_04
2024-06-27 14:55:09.619 | DEBUG    | __main__:push_task:98 - 货架_4 接收到任务 任务_05
2024-06-27 14:55:09.619 | DEBUG    | __main__:push_task:98 - 货架_1 接收到任务 任务_06
2024-06-27 14:55:09.619 | DEBUG    | __main__:push_task:98 - 货架_4 接收到任务 任务_07
2024-06-27 14:55:09.621 | DEBUG    | __main__:push_task:98 - 货架_3 接收到任务 任务_08
2024-06-27 14:55:09.621 | DEBUG    | __main__:push_task:98 - 货架_5 接收到任务 任务_09
2024-06-27 14:55:09.621 | DEBUG    | __main__:push_task:98 - 货架_2 接收到任务 任务_10
2024-06-27 14:55:09.621 | DEBUG    | __main__:push_task:98 - 机器人_01 接收到任务 任务_06
2024-06-27 14:55:09.621 | DEBUG    | __main__:push_task:98 - 机器人_02 接收到任务 任务_10
2024-06-27 14:55:09.621 | DEBUG    | __main__:push_task:98 - 机器人_03 接收到任务 任务_01
2024-06-27 14:55:09.621 | DEBUG    | __main__:push_task:98 - 机器人_04 接收到任务 任务_05
2024-06-27 14:55:09.621 | DEBUG    | __main__:push_task:98 - 机器人_05 接收到任务 任务_04
2024-06-27 14:55:09.621 | DEBUG    | __main__:push_task:98 - 机器人_03 接收到任务 任务_02
2024-06-27 14:55:09.621 | DEBUG    | __main__:push_task:98 - 机器人_04 接收到任务 任务_07
2024-06-27 14:55:09.621 | DEBUG    | __main__:push_task:98 - 机器人_05 接收到任务 任务_09
2024-06-27 14:55:09.621 | DEBUG    | __main__:push_task:98 - 机器人_03 接收到任务 任务_03
2024-06-27 14:55:09.621 | DEBUG    | __main__:push_task:98 - 机器人_03 接收到任务 任务_08
2024-06-27 14:55:09.621 | DEBUG    | __main__:run_task:172 - [ROBOT] 机器人_01 开始任务执行 task: 任务_06(lane: 01(1), layer: 06, column: 10), time: 2024-06-27 14:55:11
2024-06-27 14:55:09.621 | DEBUG    | __main__:run_task:172 - [ROBOT] 机器人_02 开始任务执行 task: 任务_10(lane: 02(1), layer: 17, column: 01), time: 2024-06-27 14:55:11
2024-06-27 14:55:09.621 | DEBUG    | __main__:run_task:172 - [ROBOT] 机器人_03 开始任务执行 task: 任务_01(lane: 03(1), layer: 30, column: 10), time: 2024-06-27 14:55:11
2024-06-27 14:55:09.621 | DEBUG    | __main__:run_task:172 - [ROBOT] 机器人_04 开始任务执行 task: 任务_05(lane: 04(2), layer: 23, column: 19), time: 2024-06-27 14:55:11
2024-06-27 14:55:09.621 | DEBUG    | __main__:run_task:172 - [ROBOT] 机器人_05 开始任务执行 task: 任务_04(lane: 05(1), layer: 26, column: 12), time: 2024-06-27 14:55:11
2024-06-27 14:55:09.622 | DEBUG    | __main__:run_task:177 - [ROBOT] 机器人_01 完成任务执行 task: 任务_06(lane: 01(1), layer: 06, column: 10), time: 2024-06-27 14:55:55, cost: 44
2024-06-27 14:55:09.622 | DEBUG    | __main__:run_task:177 - [ROBOT] 机器人_02 完成任务执行 task: 任务_10(lane: 02(1), layer: 17, column: 01), time: 2024-06-27 14:56:21, cost: 70
2024-06-27 14:55:09.622 | DEBUG    | __main__:run_task:177 - [ROBOT] 机器人_05 完成任务执行 task: 任务_04(lane: 05(1), layer: 26, column: 12), time: 2024-06-27 14:57:19, cost: 128
2024-06-27 14:55:09.622 | DEBUG    | __main__:run_task:172 - [ROBOT] 机器人_05 开始任务执行 task: 任务_09(lane: 05(1), layer: 19, column: 19), time: 2024-06-27 14:57:19
2024-06-27 14:55:09.622 | DEBUG    | __main__:run_task:177 - [ROBOT] 机器人_04 完成任务执行 task: 任务_05(lane: 04(2), layer: 23, column: 19), time: 2024-06-27 14:57:21, cost: 130
2024-06-27 14:55:09.622 | DEBUG    | __main__:run_task:172 - [ROBOT] 机器人_04 开始任务执行 task: 任务_07(lane: 04(2), layer: 16, column: 18), time: 2024-06-27 14:57:21
2024-06-27 14:55:09.623 | DEBUG    | __main__:run_task:177 - [ROBOT] 机器人_03 完成任务执行 task: 任务_01(lane: 03(1), layer: 30, column: 10), time: 2024-06-27 14:57:31, cost: 140
2024-06-27 14:55:09.623 | DEBUG    | __main__:run_task:172 - [ROBOT] 机器人_03 开始任务执行 task: 任务_02(lane: 03(1), layer: 25, column: 14), time: 2024-06-27 14:57:31
2024-06-27 14:55:09.624 | DEBUG    | __main__:run_task:177 - [ROBOT] 机器人_04 完成任务执行 task: 任务_07(lane: 04(2), layer: 16, column: 18), time: 2024-06-27 14:59:01, cost: 100
2024-06-27 14:55:09.624 | DEBUG    | __main__:run_task:177 - [ROBOT] 机器人_05 完成任务执行 task: 任务_09(lane: 05(1), layer: 19, column: 19), time: 2024-06-27 14:59:13, cost: 114
2024-06-27 14:55:09.624 | DEBUG    | __main__:run_task:177 - [ROBOT] 机器人_03 完成任务执行 task: 任务_02(lane: 03(1), layer: 25, column: 14), time: 2024-06-27 14:59:39, cost: 128
2024-06-27 14:55:09.624 | DEBUG    | __main__:run_task:172 - [ROBOT] 机器人_03 开始任务执行 task: 任务_03(lane: 03(2), layer: 17, column: 04), time: 2024-06-27 14:59:39
2024-06-27 14:55:09.625 | DEBUG    | __main__:run_task:177 - [ROBOT] 机器人_03 完成任务执行 task: 任务_03(lane: 03(2), layer: 17, column: 04), time: 2024-06-27 15:00:55, cost: 76
2024-06-27 14:55:09.625 | DEBUG    | __main__:run_task:172 - [ROBOT] 机器人_03 开始任务执行 task: 任务_08(lane: 03(2), layer: 09, column: 20), time: 2024-06-27 15:00:55
2024-06-27 14:55:09.626 | DEBUG    | __main__:run_task:177 - [ROBOT] 机器人_03 完成任务执行 task: 任务_08(lane: 03(2), layer: 09, column: 20), time: 2024-06-27 15:02:11, cost: 76
2024-06-27 14:55:09.626 | DEBUG    | __main__:stop:135 - 仓库 停止运动
2024-06-27 14:55:09.626 | DEBUG    | __main__:stop:135 - 货架_1 停止运动
2024-06-27 14:55:09.626 | DEBUG    | __main__:stop:135 - 机器人_01 停止运动
2024-06-27 14:55:09.626 | DEBUG    | __main__:stop:135 - 货架_2 停止运动
2024-06-27 14:55:09.626 | DEBUG    | __main__:stop:135 - 机器人_02 停止运动
2024-06-27 14:55:09.626 | DEBUG    | __main__:stop:135 - 货架_3 停止运动
2024-06-27 14:55:09.626 | DEBUG    | __main__:stop:135 - 机器人_03 停止运动
2024-06-27 14:55:09.626 | DEBUG    | __main__:stop:135 - 货架_4 停止运动
2024-06-27 14:55:09.626 | DEBUG    | __main__:stop:135 - 机器人_04 停止运动
2024-06-27 14:55:09.626 | DEBUG    | __main__:stop:135 - 货架_5 停止运动
2024-06-27 14:55:09.626 | DEBUG    | __main__:stop:135 - 机器人_05 停止运动

完整代码见:lpe234/7eee7fc1a70d1d707b7e899efbf03a52

import abc
import datetime
import enum
import random
import time
from typing import Union
from loguru import logger
from simpy import Resource, Environment
# 模拟任务 数量
TASK_SIZE = 10
# 巷道 数量
LANE_SIZE = 5
# 货架大小
MAX_LAYER = 30
MAX_COLUMN = 20
# 耗时 每层、每列
TIME_COST_PRE_LAYER = 2
TIME_COST_PRE_COLUMN = 1
class TaskStatus(enum.Enum):
"""
任务状态
"""
READY = 1
RUNNING = 2
DONE = 3
class Direction(enum.Enum):
"""
方向
"""
LEFT = 1
RIGHT = 2
class Position(object):
"""
货物位置
"""
# 巷道
lane: int = None
# 方向
direction: Direction = None
# 位置 (层)
layer: int = None
# 位置 (列)
column: int = None
def __init__(self, lane: int, direction: Direction, layer: int, column: int):
self.lane = lane
self.direction = direction
self.layer = layer
self.column = column
def __str__(self) -> str:
return f'lane: {self.lane:02}({self.direction.value}), layer: {self.layer:02}, column: {self.column:02}'
class Task(object):
"""
取货任务
"""
def __init__(self, tid: str, name: str, position: Position):
self.tid = tid
self.name = name
self.position = position
self.status = TaskStatus.READY
class Object(object):
"""
对象
"""
name: str = None
env: Environment = None
def __init__(self, name: str, env: Environment):
self.name = name
self.env = env
class System(Object, abc.ABC):
"""
系统
"""
def __init__(self, name: str, env: Environment):
super().__init__(name, env)
self.task_list = []
self.running = False
def push_task(self, task: Task):
logger.debug(f'{self.name} 接收到任务 {task.name}')
self.task_list.append(task)
def run(self):
while self.running:
if not self.task_list:
yield self.env.timeout(1)
continue
task = self.choose_task()
# 处理该任务
yield self.env.process(self.run_task(task))
def choose_task(self) -> Union[Task, None]:
"""
当任务列表有任务时,根据一定的策略从任务列表选取一个出来
:return:
"""
if self.task_list:
return self.task_list.pop(0)
return None
@abc.abstractmethod
def run_task(self, task: Task):
"""
具体的任务执行
:param task:
:return:
"""
pass
def start(self):
logger.debug(f'{self.name} 开始启动')
self.running = True
self.env.process(self.run())
def stop(self):
self.running = False
logger.debug(f'{self.name} 停止运动')
class Helpers(object):
@staticmethod
def time_cost(task: Task) -> float:
"""
搬运耗时
假设
- 每次机器人都在出口
- 每次只搬运一个货物
- 每次搬运完毕后,在出口等待指令
:param task:
:return:
"""
pos = task.position
cost = pos.layer * TIME_COST_PRE_LAYER + pos.column * TIME_COST_PRE_COLUMN
return cost * 2
@staticmethod
def ts_str(ts: float):
return datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S')
class Robot(System):
"""
取货机器人
"""
def __init__(self, name: str, env: Environment):
super().__init__(name, env)
self.resource = Resource(env)
def run_task(self, task: Task):
time_cost = Helpers.time_cost(task)
with self.resource.request() as req:
logger.debug(
f'[ROBOT] {self.name} 开始任务执行 task: {task.name}({task.position}), time: {Helpers.ts_str(self.env.now)}')
yield req
yield self.env.timeout(time_cost)
task.status = TaskStatus.DONE
logger.debug(
f'[ROBOT] {self.name} 完成任务执行 task: {task.name}({task.position}), time: {Helpers.ts_str(self.env.now)}, '
f'cost: {time_cost}')
class Shelf(System):
"""
货架
"""
def __init__(self, name: str, robot: Robot, env: Environment):
super().__init__(name, env)
self.env = env
self.robot = robot
def run_task(self, task: Task):
yield self.env.timeout(0)
self.robot.push_task(task)
def start(self):
self.robot.start()
super().start()
def stop(self):
super().stop()
self.robot.stop()
class Warehouse(System):
"""
仓库
"""
def __init__(self, name: str, env: Environment):
super().__init__(name, env)
self.shelf_list: list[Shelf] = []
self.__init()
def __init(self):
"""
初始化 仓库
:return:
"""
for idx in range(LANE_SIZE):
index = idx + 1
shelf = Shelf(f'货架_{index}', Robot(f'机器人_{index:02}', self.env), self.env)
self.shelf_list.append(shelf)
setattr(self, self.shelf_name(index), shelf)
def run_task(self, task: Task):
task.status = TaskStatus.RUNNING
# 此处需要判断需要哪个货架执行
pos = task.position
shelf: Shelf = getattr(self, self.shelf_name(pos.lane))
shelf.push_task(task)
#
yield self.env.timeout(0)
@staticmethod
def shelf_name(idx: int):
return f'shelf_{idx}'
def start(self):
for s in self.shelf_list:
s.start()
super().start()
def stop(self):
super().stop()
for s in self.shelf_list:
s.stop()
def generate_task(size: int) -> list[Task]:
task_list = []
for idx in range(size):
lane = random.randint(1, LANE_SIZE)
direction = random.choice([Direction.RIGHT, Direction.LEFT])
layer = random.randint(1, MAX_LAYER)
column = random.randint(1, MAX_COLUMN)
pos = Position(lane, direction, layer, column)
t = Task(f'task_{idx + 1:02}', f'任务_{idx + 1:02}', pos)
task_list.append(t)
return task_list
def send_task(task_list: list[Task], wh: Warehouse, env: Environment):
#
for t in task_list:
wh.push_task(t)
yield env.timeout(0)
def watch_task(task_list: list[Task], wh: Warehouse, env: Environment):
while True:
if [_ for _ in task_list if not _.status == TaskStatus.DONE]:
yield env.timeout(1)
continue
wh.stop()
break
def main():
base_time = time.time()
env = Environment(initial_time=base_time)
wh = Warehouse('仓库', env)
#
task_list = generate_task(TASK_SIZE)
#
env.process(wh.run())
env.process(send_task(task_list, wh, env))
env.process(watch_task(task_list, wh, env))
#
wh.start()
#
env.run()
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment