☰
系统动力学建模:从因果回路图到存量流量图与啤酒分销仿真
2026/10/29 20:08:57 网站建设 项目流程

简介:系统动力学课件与案例分析面向高校师生、管理决策学习者及需要掌握复杂系统建模的读者,围绕反馈回路、因果环路图、流图与仿真分析展开,帮助理解社会、经济、工程系统中的动态行为。压缩包内含1个PPT文件,约1.11MB,以课件形式集中呈现学科脉络、基本原理、分析步骤与典型案例,便于课堂讲授或自学时按章节梳理知识框架。已有1138人学习,说明其在入门与案例研读方面具有一定参考价值。课件从Forrester的工业动力学、世界模型讲起,延伸到我国区域规划、能源环境、企业管理等应用,并借啤酒游戏演示供应链库存与需求预测中的延迟和反馈;读者可据此掌握识别关键变量、绘制因果图、建立动态模型、开展仿真与敏感性分析的思路,也可将案例作为课程作业或小组讨论的素材。

1. 存量与流量:为什么每个角色都做了「合理」的决策,整条链还是崩了

系统动力学的课件里,最容易被一眼扫过去的是那个啤酒分销案例分析:四个角色,按同一套规矩订货,没有谁偷懒,没有谁算错,结果库存先爆仓再断货,订单曲线抖得像心电图。直觉把责任推给「预测不准」,系统动力学给出的答案更冷:行为由结构决定,人只是结构里的执行器。搞懂这句话,比背下十张因果回路图都值钱。这份资源的真正分量,是把「结构」从口号变成了能跑的东西:因果链怎么判极性、存量流量图怎么落地、方程怎么被一步步积出来、延迟为什么注定制造振荡。下面按「结论、机制、实现、取舍」走一遍,读完再看到一张反馈图,脑子里跑的就该是微分方程,而不是箭头。

2. 因果回路图到存量流量图:定性箭头怎么被翻译成可仿真的方程

课件第二部分讲原理,核心结论只有一句:系统的结构决定系统的行为。但「结构」这个词在因果回路图上其实非常含糊。一张画满箭头的图能告诉你回路是正是负,却告诉不了你系统会不会振荡、多久振一次、幅度多大。从图到方程,中间隔着一整套翻译规则,这套规则才是这门学科真正的技术含量所在。很多人画图很漂亮,一到建模就卡住,卡的就是这一步。

2.1 极性判定:负号个数决定回路是自我强化还是自我收敛

因果回路图的语法很朴素:一个变量、一条带箭头的因果链、链上标一个加号或减号。加号的定义是「其他条件不变时,A 增加会引起 B 增加」;减号则是「A 增加引起 B 减少」。判断一个回路的整体极性,规则是把回路上所有减号数出来,偶数个减号(包括零个)就是增强回路,奇数个就是调节回路。

拿课件里反复出现的一个例子走一遍。库存减少,缺货压力上升,这条链是减号;缺货压力上升,订货率上升,加号;订货率上升,入库率上升,加号;入库率上升,库存上升,加号。整条回路只有一个减号,于是它是调节回路,行为特征是朝着某个目标收敛。再看用户增长:用户数上升带来新增用户上升,新增用户上升又推高用户数,零个减号,增强回路,行为特征是指数增长,直到某个资源约束把它拽回来。

这里有个必须说清楚的边界:极性只回答方向,不回答强弱。两个同为增强的回路,在某一时刻谁主导系统,取决于各自的增益,而增益是参数和结构共同决定的。同一个模型,把某个调整时间从 4 周改成 20 周,主导回路就可能换庄,曲线形态完全变样。很多人第一次跑模型时会觉得结果很玄学,明明图没改,结论却反了;拆开看就是增益变化导致主导权转移,一点不玄。

2.2 三个基本元件与量纲约束:没有存量,系统就没有记忆

因果回路图最大的缺陷,是它无法表达「谁在积累」。存量流量图补上的正是这一块。标准元件只有三类:存量、流量、辅助变量,再加一种特殊的连线,信息箭头。

存量是系统的状态变量,只能被流量改变,不能瞬时跳变,数学上就是积分。流量是单位时间内的变化量,数学上是导数。辅助变量是中间计算,本身不积累任何东西,纯粹为了让方程可读。信息箭头画成虚线,物料箭头画成实线,这个区分在检查模型时非常有用:实线必须满足守恒,虚线不必。如果一条实线直接连到了决策点,说明你把物理流当成信息流用了,模型会白白丢掉运输或建设延迟。

元件数学角色典型例子量纲约束
存量积分,状态变量库存、在途订单、设备数量、品牌认知人数单位 = 流量单位 × 时间单位
流量导数,单位时间变化量入库率、发货率、投资率、感染率必须显式带时间基准,如「件/周」
辅助变量代数式,无记忆库存缺口、期望需求、产能利用率随其引用的变量,必须能约掉

为什么非要存量?设想一个系统里全是辅助变量、一个存量都没有。那么所有方程退化成一组代数方程,t 时刻的输出完全由 t 时刻的输入决定,系统既没有记忆也没有惯性,任何扰动都会在同一瞬间传遍全图。可现实里的库存、人口、设备、资金、信誉,哪个不是缓慢积累、缓慢释放的?正因为它们只能被流量一点点改变,系统才产生了相位差,才可能振荡。这也是为什么有些模型图看着完全正确,一仿真就平淡如水:作者把所有该做成存量的东西都写成了辅助变量。

量纲这一关同样容易翻车。存量的单位必须是流量单位乘上时间单位,速率方程必须把时间基准显式写出来。库存单位是「件」,入库率写成「件」是错的,必须写成「件/周」,否则时间推进时会悄悄多乘或少乘一个系数,曲线形状对、数量级全错,排查起来极其费神。

3. 方程与积分:连续时间是怎么被切成一步步算出来的

图翻译完了,接下来是让它在机器上跑起来。课件里讲原理时给的结论是「用仿真语言和仿真软件对模型进行计算机模拟」,这句话背后其实是一整套数值方法。理解这一层,你才知道为什么同一个模型换个人跑,结果会不一样。

3.1 DYNAMO 的时序记号与前向欧拉的等价关系

早期这类模型用专门的仿真语言书写,语句按功能分成几类:L 是存量方程,R 是速率方程,A 是辅助方程,N 是初值,C 是常量,T 是表函数。变量名后面挂时间下标,J 表示上一时刻,K 表示当前时刻,JK 或 KL 表示两个时刻之间的区间。下面这段是简化后的库存系统:

* 一阶库存系统 N INV = 1000 / 初始库存 1000 件 C TARGET = 1200 / 目标库存 1200 件 C TADJ = 4 / 库存调整时间 4 周 C SHIP = 200 / 发货率 200 件/周 A GAP.K = TARGET - INV.K / 当前库存缺口,件 R ORD.KL = GAP.K / TADJ / 订货率,件/周 L INV.K = INV.J + DT*(ORD.JK - SHIP.JK) / 存量按净流入累积

把这段翻成现代语言,就是一行前向欧拉更新:

inv = 1000.0 # 存量初值,件 target, tadj, ship = 1200.0, 4.0, 200.0 dt = 0.25 # 仿真步长,周 history = [] for k in range(400): gap = target - inv # 辅助变量:缺口,件 order = max(0.0, gap / tadj) # 速率:缺口越大订得越多,件/周 inv = inv + dt * (order - ship) # L 语句:存量 + 步长 × 净流入 history.append((round(k * dt, 2), round(inv, 1), round(order, 1)))

这段代码里最值得盯的是dt。它不是随便取的精度参数,而是仿真能否成立的前提。经验规则是取系统最短时间常数的四分之一到八分之一。上面这个模型里存量调整的时间常数是 4 周,dt取 0.25 到 1 周都安全。如果贪图速度把dt改成 3 周,你会发现曲线开始出现锯齿状的假振荡;改到 5 周以上,库存甚至可能跑成负数,系统直接发散。这不是模型错了,是积分器被逼到了稳定边界外面。不少商业软件默认使用半隐式欧拉,对存量方程做隐式处理,稳定性好一些,但时间常数这个约束依然在。

3.2 延迟阶数:为什么三阶延迟能把回路推过振荡临界点

延迟是这门学科里最容易被低估的元件。它有两种实现方式,效果差别巨大。

一阶延迟,本质上就是一个存量加一条流出速率:流出等于存量除以延迟时间。它的脉冲响应在输入出现的瞬间最大,然后指数衰减。这在数学上干净,在物理上却往往不合理,因为真实的运输、审批、建设通常是「先慢、中间快、最后拖尾」。传输延迟则是硬延迟,输入什么波形,隔一段时间原样吐出什么波形。

真实世界更接近中间状态,于是有了多阶指数延迟:把 N 个一阶延迟串起来,总延迟时间为 D,每一级的时间常数为 D/N。阶数越高,脉冲响应越接近钟形。三阶时峰值大致出现在 2D/3 的位置,这个结论直接决定了振荡的形态。

class DelayN: """N 阶指数延迟:串联 N 个一阶延迟,等价于 DELAY3 的通用形式""" def __init__(self, order, total_delay, init): self.tau = total_delay / order # 每级时间常数 self.stages = [init] * order # 每级存量,单位与输入流累积量一致 def step(self, u, dt): flows = [0.0] * len(self.stages) prev = u for i, s in enumerate(self.stages): outflow = s / self.tau # 每级按自身时间常数泄漏 flows[i] = prev - outflow prev = outflow for i in range(len(self.stages)): self.stages[i] += dt * flows[i] # 逐级欧拉推进 return prev # 最后一级的流出即延迟后的输出

构造参数只有三个:阶数决定曲线的陡峭程度,总延迟决定峰值位置,初值决定起步时的在途量。真正关键的是它对反馈回路相位的影响。按控制论的近似说法,一阶延迟最多贡献 90 度相位滞后,二阶 180 度,三阶可以到 270 度。一个负反馈回路只要总相位滞后超过 180 度,扰动就会被反馈放大并持续,振荡随之出现。

这条结论直接解释了啤酒分销模拟为什么会振荡:每层节点的在途延迟用三阶实现,四层叠加起来,相位滞后早就越过了临界线。你只把某层的延迟从 2 周改成 1 周,振荡可能立刻消失,看起来像玄学,其实是相位被拉回了安全区。

3.3 把内核串起来:一个能跑出真实形态的最小仿真

把存量、延迟、决策规则三样东西拼在一起,就是一个最小可用的仿真内核。下面这段去掉了所有业务细节,只保留机制:

class Node: """供应链单节点的 anchor-and-adjust 订货规则""" def __init__(self, target_inv, adj_inv, adj_pipe, ship_delay): self.inv = target_inv # 初始库存 = 目标库存 self.target_inv = target_inv self.adj_inv = adj_inv # 库存调整时间,周 self.adj_pipe = adj_pipe # 在途调整时间,周 self.transit = DelayN(3, ship_delay, 0.0) # 在途货物,三阶延迟 self.demand = target_inv / 8.0 # 需求平滑值初值 self.last_order = self.demand self.backlog = 0.0 def step(self, incoming, dt): arrivals = self.transit.step(self.last_order, dt) # 上期订单到货 self.backlog += incoming * dt # 累积未满足订单 shipped = min(self.inv, self.backlog) self.inv -= shipped self.backlog -= shipped self.demand += dt * (incoming - self.demand) / 2.0 # 一阶平滑预测需求 pipeline = sum(self.transit.stages) # 当前在途量 target_pipe = self.demand * 4.0 # 期望在途 = 需求 × 覆盖期 desired = (self.demand + (self.target_inv - self.inv) / self.adj_inv + (target_pipe - pipeline) / self.adj_pipe) self.last_order = max(0.0, desired) # 订单不能为负 return self.last_order

这个step里的三项各有明确含义:第一项是需求替代,按预测需求补货;第二项是库存纠偏,缺口除以调整时间;第三项是在途纠偏,防止订了货没到货就重复下单。三个参数都是可调的,而调它们会直接改变回路增益,这是后面政策分析的入口。adj_inv越小,纠偏越猛,回路增益越高,越容易振荡;adj_pipe越小,越急着补在途,牛鞭效应越明显。

4. 啤酒分销案例:四个「局部最优」怎么会拼出全局振荡

课件把案例分析放在最后,是有道理的。前面所有机制,到这里才第一次同时发力。这个案例的精妙之处在于,它把每个角色的决策规则写死成同一套,谁也没有犯错的空间,但系统照样崩。

4.1 订货规则的物理含义:为什么它天然是短视的

上面代码里的三项公式,就是这个模拟里所有角色的共同决策规则。零售商只看得到自己门店的销量和自己的库存,批发商只看得到零售商的订单和自己的库存,谁都看不到终端真实需求。这个信息结构不是谁故意设计的,而是多层分销的自然产物。

关键在于「在途纠偏」这一项。当零售商发现库存下滑、订单激增时,它会同时订更多货,而这些货要经过运输延迟才能到达。在上游看来,需求信号被放大了第一次;上游为了应对这个放大后的需求,又会加大自己的订单,再放大第二次。四层下来,终端一个 10% 的需求扰动,到工厂那一端可能变成 60% 以上的订单波动。每一层都在做局部最优,加起来就是全局灾难。

4.2 方差放大的三个机制来源

把放大拆开看,能归到三处,每一处都对应因果图上的不同箭头。

第一处是需求信号处理。人面对波动时倾向于平滑,但平滑本身引入滞后,滞后在高增益回路里就是振荡的燃料。第二处是在途纠偏。当运输延迟不可观测、只能靠估算时,估算偏差会被当成真实缺口去补,补出来的订单再次进入上游。第三处是短缺博弈。一旦出现缺货,下游会倾向于超订,超订又加剧缺货,这个正反馈回路会短暂接管系统。

下面这张表是简化模型的量级示意,换成你自己的参数会变,但比例关系是稳定的:

节点订单标准差(示意)相位滞后(周)主导机制
零售终端1.00终端需求本身
批发商约 2.52 至 3需求平滑加库存纠偏
分销商约 54 至 6在途估计误差累积
工厂约 96 至 9生产延迟叠加批量决策

这张表最值得看的不是倍数,而是相位滞后那一列。每往上游走一级,滞后就加一段,增益又乘一次。增益大于 1 且相位越过 180 度,扰动不但不被消化,还会被放大成持续振荡。这跟调参数没关系,是结构本身决定的,想彻底解决只能改结构。

4.3 三种干预的差别:改参数、改结构、改边界

课件在原理部分把寻找较优结构分成三类:参数优化、结构优化、边界优化。这三者的效力差距非常大,很多人只做了第一类就以为到头了。

参数优化是改那几个敏感参数,比如库存调整时间、目标库存覆盖期、需求平滑窗口。它见效快、改动小,但天花板也低,因为结构没变,只是把增益从危险区挪到安全区,遇到更大的扰动还是会抖。

结构优化是增删存量或流量,或者改变信息连接。最典型的一招是让上游直接看到终端销售数据,而不是只看到下游订单。这一改,因果图上少了一条放大链,牛鞭效应的第一级增益直接消失。收益远大于调参数。

边界优化是把外生变量内生化。模型里产能如果写成一个常数,那它永远不可能成为振荡源;一旦把产能改成受投资和建设周期影响的存量,系统就多了一条长周期回路,很多慢振荡才会浮现出来。代价是模型变复杂、参数变多、验证变难,这是一笔必须算清楚的账。

5. 避坑:几处让模型「跑得动但不成立」的典型翻车

模型能跑出曲线,和模型成立,是两件事。下面几条是这类建模里最常见的翻车点,每条都按现象、原因、解决写清楚。

5.1 数值层面的坑:曲线爆炸与假振荡

现象一:跑着跑着某个存量指数级冲向无穷大。原因通常是某个增强回路没有配上约束项,比如用户增长模型里只写了口碑传播,忘了写市场饱和或服务能力上限。解决办法不是调小增长系数,而是给每个增强回路找到它的负反馈对手,用表函数把饱和关系显式写进去。

现象二:改小步长后曲线形态完全变了。原因是原步长已经越过稳定边界,你看到的振荡根本不是系统的行为,而是积分器的数值伪影。判断方法很直接:把步长减半再跑一次,如果曲线明显变化,说明原来的结果不可信。稳定做法是让步长小于最短时间常数的四分之一,改完再确认一次曲线是否收敛。

现象三:库存或人口跑出负值。除了步长问题,还有一种可能是流量缺少非负约束。速率方程里加max(0, ...)只是权宜之计,更根本的做法是检查这条流量在现实里是否真的可以取负。

5.2 结构层面的坑:量纲错位与边界画错

现象四:单位检查通不过,或者通得过但数量级离谱。原因几乎总是速率方程漏了时间基准,把「件/周」当「件」用。解决办法是把每个速率方程都显式除以时间单位,别依赖软件的自动推断。

现象五:模型能复现历史趋势,但换个初始条件就完全不灵。这是边界画错的典型信号:某个本应内生的变量被写成了常数,模型只是恰好在那一段历史区间里蒙对了。排查方法是把可疑的外生变量逐个改成内生,看行为是否还稳定。如果一改就崩,说明原来的「准」是靠外部输入喂出来的,模型本身没有解释力。

5.3 决策层面的坑:敏感性分析做在了错误的地方

现象六:做了几十组参数敏感性分析,结论都是「不敏感」,于是判定模型稳健。真实原因往往是参数取值都落在同一个主导回路支配的区间里,当然不敏感。正确的做法是先做回路主导性分析,看不同时段哪个回路在主导,再在主导权切换的临界点附近做敏感性分析,那样才能找到真正的杠杆点。这个顺序反过来做,得到的结论基本没有参考价值,属于典型的白忙一场。

6. 政策分析的三层优化:模型的目的不是预测,而是找到能改的那根杠杆

走到这里,前面所有的机制都指向同一个判断:这类建模的价值不在于把未来算准,而在于把结构显性化,让「改哪里」这件事有据可依。课件把政策分析分成参数、结构、边界三层,这个分层本身就是一种设计取舍。

参数优化最容易做,也最容易被误当成全部。它的逻辑是:结构不动,只在现有回路上找敏感参数,把增益调离危险区。优点是改动小、风险低、验证快;缺点是治标,遇到量级更大的扰动照样失效。现实里绝大多数管理动作都停在这一层,比如调安全库存、调补货周期,能缓解但不解决。

结构优化动的是回路本身。让上游共享终端数据、把批量订货改成连续补货、把审批层级砍掉一层,这些动作改变的是因果图上的箭头,而不是箭头上的数字。它的收益通常比参数优化大一个量级,代价是需要跨组织协调,落地阻力也大一个量级。这两者之间的取舍,本质上不是技术问题,而是「你能动多大的手术」的问题。

边界优化最激进,也最容易被滥用。把外生变量内生化,等于承认「这件事本身也是系统的一部分」,模型会变复杂,参数会变多,验证成本直线上升。判断该不该做的标准很朴素:这个变量在研究的时间尺度内是否真的会变,且变化是否反过来影响系统。如果答案是肯定的,不内生化就是自欺欺人;如果答案是否定的,内生化只是给模型增肥。

验证这层取舍是否成立,我一般会强制走四道检查:历史重现,看模型能否在不调参的情况下复现关键变量的形态而不只是趋势;极端条件测试,把输入推到零或者极大,看输出是否还符合物理常识;量纲一致性,逐条方程核对时间基准;回路主导性随时间的变化,确认在不同阶段主导回路是否合理切换。四道都过,才敢拿去做政策讨论。

血泪经验是:这四道里最容易被跳过的是第二道。极端条件测试看起来像形式主义,但它抓出来的往往是结构缺失,比如某个流量在需求归零时还能持续为正,说明它压根没连到需求上。这种错误在正常参数下完全看不出来,一到边界就露馅。

从那以后我每次建完模型,都会先把步长砍到原来的四分之一重跑一遍,再手动把几个关键输入推到零,看系统会不会做出荒唐反应,两关都过才敢往下走。这份课件和案例集把这套流程从原理到实现串成了一条完整的线,值得存下来慢慢拆。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询