7.1 KiB
情景模拟分析结果 v3(针对“几个疑问”)
对应问题来源:几个疑问.md
1. 初始化顺序与优先级:静态值、事件值、影响计算如何排布?
1.1 三类数据的“角色定义”
- 静态值(设备表/物料表/拓扑 static):系统的“默认底色/初值基线”。没有事件、没有影响计算时,也必须能给出一个自洽的初始状态。
- 事件值(始发事件/attr_changes):系统的“外部输入”。既可能是一次性的设定(某一步将某属性改成某值),也可能代表持续的干预(在一段时间内强制维持某值)。
- 影响计算(influence 公式):系统的“内部传播/派生规则”。它根据来源属性计算得到目标属性,是一个“生成值”的过程。
1.2 当前代码实现的顺序(实际行为)
在 ProjectServiceImpl.initSimulation 里,单帧生成顺序是:
- 注入尺寸(Device.size)
- 写入设备静态值
- 执行设备 influence 计算并写入 state
- 写入物料静态值(DB + topology)
- 执行物料 influence 计算并写入 state
- 最后对 device/material 应用事件覆盖(overrideWithEvents)
这相当于:Static → Influence → Event(最终覆盖)。代码位置见:ProjectServiceImpl.initSimulation 与 overrideWithEvents/readValue。
另外,readValue() 在计算 influence 时会“优先读事件、再读静态”,但最终仍会被 overrideWithEvents() 再覆盖一次,这意味着事件在该实现里拥有“最终裁决权”。见:readValue。
1.3 推荐的合理顺序(建议落地的“标准管线”)
建议将初始化/每步演进统一成一个明确的“管线”,并对事件类型做区分:
推荐管线:
- Static 基线:加载设备/物料静态值(含 DB 补全)作为默认状态。
- Event(输入/初始条件):应用在当前时刻生效的事件(尤其是 t=0 或 step=1 的“初始条件事件”)。
- Influence(派生计算):基于当前状态执行影响关系计算,得到派生值。
- Event(强制覆盖/锁定):仅对“强制锁定类事件”再次覆盖(可选,但强烈建议引入),确保“外部强制干预”不会被公式计算反覆盖。
这样做的好处:
- 既能让事件作为“输入”影响传播(步骤 2 发生在计算前),又能让“强制事件”在输出阶段生效(步骤 4)。
- 能解释并覆盖两类业务直觉:
- “我设了初始浓度,后续按公式变化”(步骤 2 + 3)
- “我强制把某槽位液位锁死,不管公式怎么算”(步骤 4)
1.4 关键补充:时间轴/采样点也属于优先级的一部分
当前 initSimulation 的帧生成时间点来自事件时间集合:collectTimePoints(valueProviders),若没有事件时间点会直接返回“无法生成帧”。见:collectTimePoints 与 initSimulation timePoints 为空处理。
建议:
- 当没有事件时,允许通过参数指定
start/end/stepInterval,生成基础时间网格(否则“纯静态模拟”永远跑不起来)。 - 对于 ramp/linear 事件,当前只把 start/end 加入时间点(中间不采样),但
readValue支持任意 t 的线性插值。若希望输出更“连续”的曲线,必须补齐采样点(例如每 1s/每步都采样)。
2. 合理性分析:为什么推荐上述顺序?
2.1 从“因果关系”角度
- 静态值是先验事实(设备尺寸、物料基础参数),应先进入状态。
- 事件是外部驱动/输入,会改变系统边界条件,应在影响传播前进入状态,否则传播用的仍是旧值。
- influence 是系统内部响应,应在输入就绪后计算。
- 强制覆盖事件是高优先级干预,必须在计算后仍能维持,否则“强制”二字失去意义。
2.2 从“可维护性/可解释性”角度
把事件分成两类最关键:
- Event-Input(输入型):参与计算的输入(初值设定、控制变量变化)。
- Event-Override(强制型):输出阶段的锁定/强制(故障注入、人工接管)。
如果不区分,只能在“事件最高”与“公式最高”之间二选一,最终会在不同业务场景下反复打补丁。
3. SimController 及相关接口分析(并给出建议)
3.1 现状:SimController 是“半成品/原型”,当前不可用
SimController.java 体现了一套更清晰的分层意图:Controller -> Repo 装配 -> Builder -> SimService -> 推理输入,但存在几处硬性问题:
- 依赖的
ProjectRepository / EventRepository / InfluenceRepository / InferenceConverter在项目代码中找不到定义,无法编译/运行(当前仓库中仅此文件引用这些类型)。 SimBuilder中对应的buildUnits/buildEvents/buildInfluenceNodes方法是注释掉的,占位未实现。见:SimBuilder.java。SimService当前实现仅是 KV 级别的简化引擎,且采用 事件先写、后计算覆盖 的顺序(会导致“事件设定失效”)。见:SimService.runSimulation。
3.2 与当前线上接口的关系
当前“初始化/运行模拟”入口实际在 ProjectController(/simulation/init、/simulation/run)并调用 ProjectServiceImpl。例如 runSimulation:见 ProjectController.java:L289-L300。
3.3 建议:保留 SimController 的分层方向,但必须补齐三件事
- 补齐依赖与 Builder:明确 Repo 层要读什么(拓扑、事件、影响关系)并落地成可编译的类/接口;实现
SimBuilder的构建方法或直接复用ProjectServiceImpl的解析器。 - 统一优先级管线:让
SimService采用“Static → Event(Input) → Influence → Event(Override)”(见第 1 章建议),避免出现“事件被公式反覆盖”。 - 统一对外 API:建议最终以
/sim/*作为唯一入口。