Petri网与Flexsim在整车货运流程优化中的应用

发布时间:2026/10/25 14:50:47
Petri网与Flexsim在整车货运流程优化中的应用

简介:一份围绕铁路整车货运流程优化的研究资源,面向物流管理专业人员、铁路运输从业者及物流优化研究者。内容针对发送作业流程冗长、职能部门繁多、到达作业服务不规范等现实问题,基于Petri网完成发送与到达流程建模,使用Flexsim仿真定位瓶颈环节,并提出简化流程、提升效率、降低成本的具体优化方案,同时通过二次仿真验证效果。资源包内为单个PDF文档,体积约871KB,文档内附有Petri网建模的Python代码及详细解释,涵盖库所、变迁、弧和标记等核心类实现,以及流程模型构建与可视化方法;同时展示Flexsim仿真分析思路和Python辅助数据处理技术,便于复现研究并迁移到其他流程优化场景。已有87人学习,适合希望深入掌握铁路货运流程优化方法并借鉴仿真建模技术的专业人士。

1. 整车货运流程优化:为什么非用 Petri 网和 Flexsim 仿真不可

铁路货运站的整车作业优化,最让人头疼的不是方案不够多,而是不知道瓶颈到底在哪。日办理辆数卡在某个数上不去,到发线经常排满,调车机在站内来回跑却还是让车列等待,装卸机械反而有时闲着。光画流程图看不出问题,靠经验拍板加设备、加线路,往往是刚把 A 点的堵塞疏通,B 点立刻变成新瓶颈,钱花了效果却不明显。这个标题拆开就是一套标准打法:先用 Petri 网把整车货运流程建造成可计算的逻辑模型,从结构上找出死锁和资源瓶颈;再用 Flexsim 把模型放进动态时钟里跑仿真,用吞吐量、利用率和等待时间验证改进方案。两个工具一个管逻辑、一个管动态,配合起来才能回答「瓶颈在哪、改了到底有没有效」。这套组合适合场站运营、物流系统仿真和流程规划方向的从业者照着复现,也适合用来给方案做定量论证。

2. 用 Petri 网把整车货运流程变成可分析模型:库所、变迁与托肯的真实映射

2.1 为什么不用流程图:Petri 网对资源竞争的三个优势

流程图最大的问题是单一线程。画「列车到达→占用到发线→调车机取送→货位装卸→取走车辆」,逻辑上没错,但表达不了「到发线只有 3 条、调车机只有 1 台、货位只有 2 个」这种资源竞争。两个车列同时申请同一条到发线,流程图里画不出排队和互斥;调车机既要送车到货位,又要取走卸完的车,流程图里表达不了这种共享资源的抢占。这也是为什么现场人员画的作业流程图看起来很顺,但一按图核对能力总是对不上的原因。

Petri 网用五要素把流程变成形式化模型:库所(Place)表示状态或资源,变迁(Transition)表示动作或事件,弧(Arc)连接库所和变迁,权值(Weight)表示一次动作消耗或产生多少资源,初始标识(M0)表示系统初始状态。托肯(Token)在库所里的分布就是系统当前状态。相比流程图,Petri 网有三个直接对住铁路货运场景的优势。第一,并发是显式的,两个互不冲突的变迁可以同时使能,比如两列车在不同货位同时卸车,模型里能直接表达。第二,资源守恒看得见,库所容量和托肯数量直接对应到发线条数、货位个数和设备台数,不会画着画着就把资源画没了。第三,模型可做数学分析,关联矩阵、不变量、可达图都能算,不用等仿真跑完就知道结构上有没有死锁。

2.2 整车货运作业的映射规则:一个卸车场景的完整建模

常见做法是把「到发线空闲」「车列待取送」「调车机空闲」「车辆在货位」「装卸机械空闲」这类状态建模成库所,把「列车到达」「取送作业」「开始装卸」「装卸完成取走」建模成变迁。以「一列车到达、分解、卸车、取走」的完整流程为例,最小模型可以定义成下面的样子。库所和变迁的对应含义用表列清楚,后续写 Python 分析代码和搭 Flexsim 模型都要拿这张表当锚点。

元素类型含义初始托肯
P1 到发线空闲库所空闲到发线数量3
P2 车列待取送库所停在到发线等待调车作业的车列0
P3 调车机空闲库所调车机可用状态1
P4 车辆在货位库所已送达货位待装卸的车辆0
P5 装卸机械空闲库所装卸机械可用状态2
P6 卸车完成待取库所完成卸车等待取走的车辆0
T1 列车到达占用到发线变迁列车到达并占用一条到发线–
T2 调车机取送变迁将车列从到发线送到货位–
T3 开始装卸变迁车辆在货位上进行卸车作业–
T4 卸车完成取走变迁卸完车辆被取走,释放货位和装卸机械–

初始标识 M0 = (3, 0, 1, 0, 2, 0)。T1 使能条件是 P1 有托肯且外部有到达请求;T2 使能条件是 P2 有托肯且 P3 有托肯;T3 使能条件是 P4 有托肯且 P5 有托肯;T4 使能条件是 P6 有托肯。T2 发生后,P2 减 1、P3 减 1(调车机被占用)、P4 加 1、P1 加 1(到发线释放),这套增减关系就是下一章关联矩阵的来源。

这里有个值得强调的细节:到发线的释放时机。常见做法是车列被调车机取走的瞬间就释放到发线,而不是等货位卸完再释放。很多人第一次建模时把「到发线腾空」和「货位作业」强绑在一起,结果死锁率高得离谱,跟现场完全对不上。实际生产中,车列取走后到发线立刻能接下一列车,这是货运站提高日办理辆数的核心逻辑,映射时务必体现。

2.3 死锁和活性:静态就能看出的瓶颈

Petri 网的实用价值在于不用跑仿真就能预判死锁。以这个模型为例:如果 T1 连续发生 3 次,P1 的 3 个托肯被占用,再来的列车进不了到发线;如果此时 P2 有车列等待、P3 调车机被占、P4 货位全满,系统就卡死了——没有任何变迁可以发生。这种状态在 Petri 网里叫死锁标识。

判断死锁的标准方法是画可达图,枚举所有从 M0 能到达的标识,找出没有后继状态的标识节点。但状态数会随托肯数量指数增长,现场模型几十个库所、上百个托肯,枚举根本走不完。工程上更常用的是不变量分析:找一组库所的加权和,在任何可达状态下都守恒。比如「P1 + P2 + P4 + P6 = 3」如果成立,说明车列要么在到发线、要么等待取送、要么在货位、要么卸完待取,系统里同时在线的车列总数被限制为 3。货位容量只有 2,剩下 1 必须堵在到发线——这个守恒式本身就是瓶颈证据。下一章的代码会把计算落下来。

3. 用 Python 算出 Petri 网结构瓶颈:关联矩阵、不变量与死锁检测代码

3.1 构建关联矩阵:从形式化定义到代码的样板

关联矩阵把库所和变迁之间的弧权值写成矩阵:行对应库所,列对应变迁。正数表示变迁发生后该库所净增加的托肯数,负数表示净减少。拿上一章的最小模型,代码可以这样构建。

import numpy as np # 库所顺序: P1到发线空闲, P2车列待取送, P3调车机空闲, P4车辆在货位, P5装卸机械空闲, P6卸车完成待取 # 变迁顺序: T1到达占用, T2调车取送, T3开始装卸, T4卸完取走 places = ["P1_到发线空闲", "P2_车列待取送", "P3_调车机空闲", "P4_车辆在货位", "P5_装卸机械空闲", "P6_卸车完成待取"] transitions = ["T1_到达占用", "T2_调车取送", "T3_开始装卸", "T4_卸完取走"] # 关联矩阵 C: 行=库所, 列=变迁; 负数为消耗, 正数为产生 C = np.array([ [-1, 1, 0, 0], # P1: T1消耗1个空闲到发线, T2释放1个 [ 1, -1, 0, 0], # P2: T1产生1个待取送车列, T2消耗1个 [ 0, -1, 1, 0], # P3: T2占用调车机, T3释放 [ 0, 1, -1, 0], # P4: T2送入1个车辆到货位, T3消耗 [ 0, 0, -1, 1], # P5: T3占用装卸机械, T4释放 [ 0, 0, 1, -1], # P6: T3产生卸完待取, T4取走 ], dtype=int) M0 = np.array([3, 0, 1, 0, 2, 0]) # 初始标识 print("关联矩阵形状:", C.shape) print("库所-变迁对应关系:") for i, p in enumerate(places): row = " ".join(f"{v:3d}" for v in C[i]) print(f" {p:20s} [{row}]")

这段代码的关键是符号约定。Petri 网教材里有两种记法:一种用「后集减前集」直接算净变化,一种用消耗矩阵减产生矩阵。这里用的是净变化矩阵:C[i][j] > 0 表示变迁 T_j 发生后库所 P_i 净增加 n 个托肯,C[i][j] < 0 表示净减少。后续不变量计算都建立在这个矩阵上,符号一旦搞反,算出来的 S-不变量含义完全颠倒。需要强调的第二点是命名:库所和变迁直接带业务含义,别用 P1、T1 裸名,模型一大人就会晕,对照现场资源写注释时特别容易出错。

3.2 T-不变量与 S-不变量:哪条作业链是必须循环的

不变量是 Petri 网结构分析的体检报告。T-不变量 x 满足 C @ x = 0 的非负整数向量,表示一组变迁按 x 规定的次数各发生一遍后,系统整体状态回到原点——也就是一个完整作业循环。S-不变量 y 满足 yᵀ @ C = 0,表示一组库所的加权托肯总数在运行中守恒。

from sympy import Matrix, Fraction C_sym = Matrix(C) # T-不变量: C @ x = 0 -> 求右零空间 t_invariants = C_sym.nullspace() print("T-不变量(作业循环):") for i, vec in enumerate(t_invariants): vec_int = [Fraction(v).limit_denominator() for v in vec] print(f" x{i+1} = {vec_int}") # S-不变量: y.T @ C = 0 -> 转置后求右零空间 s_invariants = C_sym.T.nullspace() print("\nS-不变量(守恒资源组):") for i, vec in enumerate(s_invariants): vec_int = [Fraction(v).limit_denominator() for v in vec] print(f" y{i+1} = {vec_int}") # 用第一个S-不变量验证守恒量 if s_invariants: y = np.array(s_invariants[0], dtype=int) conserved = int(y @ M0) print(f"\n守恒量 y.M0 = {conserved}")

这个模型里 T-不变量会算出一个完整循环 (1,1,1,1),即「到达→取送→装卸→取走」是一个闭环作业链。S-不变量中最有价值的一条对应「P1 + P2 + P4 + P6」守恒,守恒量等于 3——这就是系统在途车列上限。货位容量只有 2,那剩下 1 列必然要在到发线等待,日办理辆数的天花板上限被算死了,不是仿真跑出来的,是结构上决定的。

参数说明:sympy 的 nullspace 返回有理数基向量,用 limit_denominator 归一成整数向量方便解释。模型规模大、矩阵稀疏时,可以先对 C 做有理数域行化简再求零空间,千万别直接对浮点数求,数值误差会把不变量淹没。实际项目里我一般把所有 S-不变量列出来,逐个对照现场资源写注释,这堆注释本身就能当方案汇报材料用。

3.3 死锁检测:有限状态枚举的代码与其边界

结构不变量看不出「某个具体标识下是否卡死」,要看具体状态必须做可达标识枚举。做法是维护一个已访问标识集合,反复触发所有使能变迁生成新标识,直到没有新状态为止。

from collections import deque def enabled_transitions(marking, C): """返回当前标识下所有使能的变迁索引列表""" enabled = [] for j in range(C.shape[1]): col = C[:, j] # 变迁使能要求: 所有消耗弧上的库所托肯数不小于消耗量 can_fire = True for i in range(C.shape[0]): if col[i] < 0 and marking[i] < -col[i]: can_fire = False break if can_fire: enabled.append(j) return enabled def fire(marking, C, j): """触发变迁 j, 返回新标识""" return marking + C[:, j] def reachable_deadlocks(M0, C, max_states=100000): visited = {tuple(M0)} queue = deque([M0]) deadlocks = [] state_count = 0 while queue and state_count < max_states: m = queue.popleft() state_count += 1 en = enabled_transitions(m, C) if not en: deadlocks.append(m) continue for j in en: m2 = fire(m, C, j) if tuple(m2) not in visited: visited.add(tuple(m2)) queue.append(m2) return deadlocks, state_count deadlocks, cnt = reachable_deadlocks(M0, C) print(f"遍历状态数: {cnt}") print(f"死锁标识数: {len(deadlocks)}") for d in deadlocks[:10]: print(" 死锁标识:", dict(zip(places, d)))

对最小模型运行,结果通常不会出现死锁,因为资源配比还算合理。把货位容量从 2 改成 1,或者调车机数量从 1 改成 0(物理上不成立但模型上可行),死锁立刻出现。典型的死锁标识是「P2=1, P4=容量上限, P3=0」:有车列在到发线等待取送,但调车机被占、货位已满——调车机送完上一列还没回来,新到的车列进不了货位。这是整车货运作业里最典型的死锁结构,现场表现就是「到发线堵死、调车机来回空跑、货位闲不下来」。

这段代码的边界必须讲清楚:它只适合托肯总数很小的模型。状态数随托肯数指数增长,现场模型几十个库所、上百个托肯时,max_states 很快撞上限。工程上的正确用法是:先用枚举法在小规模模型上验证映射逻辑没错,再退回不变量分析做全规模结构判断。第 4 章的 Flexsim 仿真,本质上是绕开状态爆炸,用随机采样估计系统在长时间运行下的行为特征。

4. 把 Petri 网模型翻译成 Flexsim 仿真:整车货运作业最小模型的搭建与参数

4.1 映射表:库所、变迁、托肯在 Flexsim 里各是什么

Flexsim 的建模思维是「实体沿连接流动」,Petri 网思维是「托肯因变迁在库所间转移」。翻译的关键是把五要素一一对应:有限容量库所对应 Queue 实体,Capacity 参数直接填库所容量;变迁的延时对应 Processor 实体或消息延迟;托肯对应临时实体 item;资源类库所对应任务执行器或资源占用状态;弧的权值通过 Batch/Unbatch 表达。

Petri 网元素Flexsim 元素关键参数
有限容量库所(到发线、货位)QueueCapacity 设成库所容量;超出容量时的策略选 Stop
变迁(到达、取送、装卸)Processor / 消息延迟Process Time 设成作业时长分布
托肯(车列、车辆)临时实体 item按车列或单车粒度决定一个 item 代表多少辆
资源(调车机、装卸机械)任务执行器 / 资源 Queue数量等于资源数量,用忙闲状态统计利用率
弧的权值(一列车 N 辆车)Batch / Unbatch车列到货位后拆成 N 辆逐个装卸

映射时的常见做法是把「车列」作为 item 粒度。到发线就是一条 Capacity=3 的 Queue;调车机用任务执行器,执行「从到发线取走 item → 送到货位 Queue」的任务序列;货位 Queue 容量 2,item 进入后拆成单车,再用装卸机械资源逐辆处理。这样模型里 item 数量少,仿真速度快,又不丢车辆级细节。

4.2 最小模型的 FlexScript 骨架:事件驱动与资源占用

Flexsim 建模通常先拖实体、连端口,再在实体触发器里写 FlexScript。下面给出一套最小骨架,对应上一章 Petri 网模型:3 条到发线、1 台调车机、2 个货位、2 台装卸机械。这段示例代码讲解「到发线释放」和「调车机占用」两件事怎么用脚本表达。

// 模型实体命名约定: // Source_列车到达 -> Queue_到发线(Capacity=3) -> Processor_调车取送 -> Queue_货位(Capacity=2) -> Processor_装卸 -> Sink_离站 // 调车机资源先用全局变量模拟, 场景复杂后换成任务执行器 int tug_free = 1; // 1=调车机空闲, 0=占用, 放在模型全局变量区 // Source_列车到达的到达间隔: // return 60; // 按运行图固定间隔, 不要用指数分布 // Queue_到发线的 OnEntry: // 车列占用到发线库所, 触发调车请求 // if (tug_free == 1 && content(Queue_货位) < 2) 则把item发往Processor_调车取送 // Processor_调车取送的 Process Time: // return triangular(20, 30, 45, 1); // 取送作业, 均值30分钟 // Processor_调车取送的 OnFinish: // tug_free = 1; // 调车机释放 // 同时触发 Queue_到发线的下一车列进入取送流程 // Queue_货位的 OnEntry: // 车列到达货位, 拆成单车, 逐辆送Processor_装卸

这段骨架的意图不是直接跑通,而是把 Petri 网模型里的库所增减翻译成 Flexsim 的触发器。最容易忽略的是调车机释放时机:Petri 网模型里 T2 发生后到发线释放、调车机占用,T3 完成后调车机释放;Flexsim 里对应 Processor_调车取送的 OnFinish 置 tug_free=1,同时保证 Queue_到发线只在 OnFinish 的触发里送下一列车,这样才能和 Petri 网的使能条件完全对齐。

参数说明:用全局变量模拟单一调车机只适合最小模型,场景里调车机跨多个区域、任务有优先级时,要换成 Dispatcher + TaskExecuter 的任务序列体系,否则多个作业抢一个资源时逻辑会写乱。Process Time 用 triangular(20,30,45) 起步是常见的,均值和上下限明确,比直接用 exponential 方差小得多。

4.3 关键参数设定表:先定容量,再定分布,最后做标定

仿真参数分三类:到达参数、作业时长参数、容量参数。第一次搭模型按下面表格设定,跑通后再用现场统计标定。

参数建议取值理由
列车到达间隔按运行图固定间隔 60~120 分钟铁路是按图行车,不是随机到达
调车取送时长三角分布 triangular(20, 30, 45) 分钟均值 30,波动体现在上下限
单车装卸时长按货物品类分,triangular(10, 15, 25) 分钟整车装卸波动大于调车作业
到发线容量现场实际条数,示例 3必须与 Petri 网库所容量严格一致
货位容量现场实际货位数,示例 2与 S-不变量算出的上限对照
预热时间至少 5 个仿真日跑掉空场阶段,统计从稳态开始

有一条规定动作:容量参数必须和 Petri 网模型完全一致,否则 Petri 网算出的守恒量和死锁预判在仿真里对不上,后续所有瓶颈分析失去锚点。作业时长分布则可以在仿真里自由调整,因为仿真要回答的核心问题就是「如果取送快 20%,瓶颈会不会转移」。固定间隔到达的写法很简单,Source 的 Interarrival Time 填常量,或者用 Schedule 表按运行图逐列给出到达时刻。

5. 避坑:Petri 网模型与 Flexsim 仿真对不上的 5 个常见问题

5.1 库所容量改了容量忘了同步:模型对不上的头号翻车点

现象:Petri 网分析日办理辆数上限是 3 列,Flexsim 仿真跑出来却是 5 列,怎么调时长参数都对不上,开始怀疑不变量算错了。

原因:Flexsim 里某个 Queue 的 Capacity 设大了。Petri 网把到发线容量定为 3,但 Flexsim 里 Queue 默认容量上千,忘了改;或者货位 Queue 里因为 Batch/Unbatch 逻辑不对,实际 item 数超过 Petri 网托肯数,导致「Petri 网没死锁、仿真也没死锁」但吞吐量虚高。这类问题最难查,因为它不报错,只让结果悄悄偏离。

解决:建一张「Petri 网库所 ↔ Flexsim Queue 容量」对照表,贴在模型说明里,每次改容量两侧同步改。经验做法是在 Queue 的 OnEntry 里加一句容量断言,超出就输出红色日志,让模型自己报警。这个习惯能省掉大量对数据的时间。

5.2 变迁使能条件写成全局变量判断:并发被压成了串行

现象:仿真结果比现场吞吐量低 30%,调车机利用率显示 95%,到发线也有大量空闲时间,两个指标自相矛盾,现场人员一眼就质疑模型。

原因:把「到发线是否有空闲」「调车机是否空闲」写成了全局标志位,在多个触发器里互相读写。Flexsim 触发器是事件驱动,同一时刻多个 item 可能并行触发;全局变量一改,后触发的逻辑读到的是新值,Petri 网的并发使能关系丢失,系统被压成了串行。

解决:并发状态放进实体自身属性管理,别用全局变量。到发线还剩多少容量,直接读 Queue 的当前库存算;调车机忙闲状态,用任务执行器的状态统计字段。Petri 网使能条件是「每个输入库所有足够托肯」,翻译成 Flexsim 就是「每个输入实体有可用容量」。在 OnEntry 和 OnExit 里做容量检查,而不是维护标志位。

5.3 到达间隔用了指数分布:仿真方差爆炸,结论全变噪声

现象:同一套方案跑 5 次,日办理辆数从 3 到 8 剧烈波动,置信区间宽到没法用。换随机种子结论就反转,方案 A 有时比 B 好、有时比 B 差,无法决策。

原因:铁路货运站是按图行车,车列到达时刻是运行图排定的,不是随机泊松到达。指数分布方差极大,短间隔和长间隔交替出现,到发线时而拥堵、时而空转,平均指标根本没有统计意义。这种情况最容易被误读成「仿真发散」——模型数值没问题,是输入分布选错了,这属于仿真参数里的玄学问题,越调随机种子越乱。

解决:Source 的 Interarrival Time 填固定常量,或者用 Arrival Schedule 按运行图输入真实到发时刻。实在要加扰动,用三角分布或正态分布,把标准差控制在均值的 20% 以内,别让随机性主导系统行为。

5.4 调车机跨区域作业被建成固定资源:利用率虚高 20 个点

现象:仿真报告里调车机利用率 95%,现场统计只有 70%,一线人员直接说模型不可信,项目推进卡壳。

原因:调车机在真实现场要在到发线、货位、编组区之间移动,移动时间占作业周期很大比重。建模图省事,把调车机建成一个固定的 Processor,只算了取送作业时间,忽略走行时间;或者把一台调车机绑在某一条线路上,没建跨区域移动。

解决:把调车机建成 Dispatcher + 多个 TaskExecuter,Travel Time 按区段距离设定;或者退一步,在 Processor 的 Process Time 里把平均走行时间折算进去。折算系数取法:用现场一个班的作业日志,统计「调车机从接到指令到移动到位」的平均耗时,加到处理时间上。这是最稳妥的折中做法,精度足够支撑方案对比。

5.5 预热时间设太短:统计窗口混入空场数据

现象:仿真刚开始的几天系统还没进满车列,平均队列长度偏低、完成时间偏高。拿前 3 天和后 7 天数据对比,差异明显,但没人注意到统计窗口混了空场。

原因:Flexsim 默认从模型开始运行就记录统计,初始状态是空场。空场阶段没有排队,所有指标都好得离谱,混进统计窗口会系统性低估瓶颈。

解决:模型先置空,跑预热期,预热期内清空统计,再从零开始记录。Flexsim 里可以在 Reset 后跑固定时长,然后清空 Statistics 再继续。预热时长怎么定?观察到发线累计吞吐量曲线,等斜率稳定后再清统计。常见做法是跑至少 5 个仿真日,或者等累计吞吐量曲线两次经过同样的峰值形态。

6. 瓶颈识别与效率提升方案验证:吞吐量、利用率与三套方案的对比方法

6.1 三个指标怎么读:队列长度、利用率、平均完成时间

瓶颈识别不靠肉眼盯堆积,靠三个指标对比:到发线平均队列长度(排队越长越接近瓶颈)、调车机利用率(持续高利用说明调车是卡点)、单车平均完成时间(整条链路的综合结果)。每次改动一个资源后,看哪个指标先顶到天花板,那个位置就是当前瓶颈。

6.2 三套方案的对比实验:单因子逐个试

常见做法是给方案编码:A 加一条到发线;B 改调车取送优先级为「先进先发」;C 增加一台调车机。用 Flexsim 的 Experimenter 跑批量实验,每组跑 20 次取均值,对比日办理辆数和平均完成时间。判定原则:小改大收益直接上,大改小收益要算投资回报。方案 C 看似直接加资源,但调车机之间的走行避让往往让收益打折,仿真的价值就在这里——用数据压住经验拍板。

做这类项目我最大的教训是别把模型越建越复杂。Petri 网画得再漂亮、Flexsim 仿真跑得再精细都不算本事,只有把瓶颈说清楚、把方案验证到位才算数。模型的价值是推动决策,不是证明自己会用工具。我现在拿到一个货运流程优化需求,会先花一周时间把 Petri 网结构和现场数据对齐,再花三天搭仿真做对比,而不是一上来就拖实体。希望这套「Petri 网定结构、Flexsim 验动态」的组合拳能帮到你,少踩几个我踩过的坑。

本文还有配套的精品资源,点击获取