完成求解器雅可比矩阵首轮优化,增加更新目录,整理了文档文件夹,增加了服务启动脚本

This commit is contained in:
lujingze committed 2026-08-17 07:33:31 +00:00
1 parent 6bb0591d32
commit 16a7eb2d6c
48 files changed
+8172 -217

No files matched your search

@@ -0,0 +1,94 @@
# AMESim 子模型公开组件迁移矩阵
状态:22 个模型已在临时 AMESim 库完成第一版公开,持续校准中
适用模型:`AmesimModels/test_mql.ame` / `app.simulation.examples.test_mql.system`
配套规范:[`component-model-authoring-spec-v1.md`](../standard/component-model-authoring-spec-v1.md)
## 目标
本文档记录 `test_mql` 中 AMESim 子模型族的公开状态和后续校准批次。实际注册清单以 `app/simulation/components/amesim/library.py` 为准。
当前原则:
- 不把现有固定拓扑 `TestMqlSystem` 整体注册为公开拖拽组件。
- 第一版公开只表示模型契约、目录、XML 编译和最小仿真链路已接通,不表示 AMESim 精确语义已完成复刻。
- 气动、标量信号和一维机械模型按当前求解链路能力公开;PNRP17 已打通首个实时气动—机械跨域闭合,事件和更一般跨域 DAE 仍保留明确限制。
- 固定算例专用的校准原语留在 `examples/test_mql/`,不与公共组件目录混为同一实现。
## 当前求解器能力边界
`app.simulation` 当前支持:
- 物理域:`pneumatic`、标量 `signal` 和一维 `mechanical`。
- 端口变量:气动 `p/m_flow/h_outflow` 及定向 `volume/volume_flow`、信号 `signal`、机械 `x/v/f`。
- 流量符号:`m_flow > 0` 表示流入当前组件。
- 网络方程:气动连接压力相等、流量和为零、组件压力-流量残差、stream 焓传播和移动边界容积传播;标量信号传播;机械 `x/v` 等值和 `f` 平衡。
- 动态:半显式 ODE,动态组件提供质量和内能状态导数。
- 暂不支持:完整事件系统、一般高指数 DAE、任意多域连接变量,以及从 PNGD00 自动批量导入其他气体。公共编译链已经支持“介质 + 物性计算方法”定义元件:`gi=0` 使用内置空气理想气体,`gi=1..99` 引用画布中的显式介质物性实例;介质定义内部通过 `property_model` 下拉接口选择计算方法。氦气已提供 Peng-Robinson 首版。
## 子模型族矩阵
| AMESim 子模型 | 数量 | AMESim 角色 | 当前公开状态 | 建议目标 | 先决条件 / 限制 |
| --- | ---: | --- | --- | --- | --- |
| `PNCH023` | 4 | 固定容积气室,带换热 | 第一版公开 | `amesim_pnch023`,`storage` | 已基于 `ThermodynamicVolumeComponent` 接入公开契约;仍需按 AMESim `cvol/extemp/kth/sth/gi` 复核质量、能量、换热方程。 |
| `PNCH012` | 8 | 变容气室,带换热 | 实时变容第一版公开 | `amesim_pnch012`,`storage` | 保留 `vol1..4/dvol1..4` SI 参数,并可从 PNRP17 经气动端口接收实时外部容积与变化率;已完成代表性联合仿真,仍需完整 `test_mql` baseline 校准。 |
| `PNOR001` | 8 | 常系数气动孔口 | 第一版公开 | `amesim_pnor001`,`flow` | 已接入代数组件公开契约;仍需按 AMESim `cq/area/Cv/Kv/flowset/gi` 复核双向流、零压差正则化和单位换算。 |
| `PNVO001` | 8 | 信号调制气动孔口 | 第一版公开 | `amesim_pnvo001`,`flow` | 已接入名为 `res` 的标量信号输入端口,可由 `amesim_step0` 驱动开度;精确事件语义和 AMESim baseline 仍留后续修模。固定开度变体 `amesim_pnvo001_fixed` 继续保留。 |
| `PN3NODE2` | 8 | 三端气动节点,压力温度由 port 2 固定 | 第一版公开 | `amesim_pn3node2`,`junctions` | 已接入三端等压、流量守恒基础版;AMESim port 2 参考温压语义和 stream 混合仍需单独测试。 |
| `P4NODE2` | 8 | 四端气动节点,压力温度由 port 2 固定 | 第一版公开 | `amesim_p4node2`,`junctions` | 已接入四端等压、流量守恒基础版;仍需复核 port 2 参考温压和多支路混合。 |
| `PNL00R` | 4 | 管路纯阻性摩擦段 | 第一版公开 | `amesim_pnl00r`,`flow` | 已接入准稳态阻性管公开契约;仍需按 AMESim `PNL00R` 参数和摩擦公式复核。 |
| `PNL0001` | 20 | C-R 动态管路 | 第一版公开 | `amesim_pnl0001`,`flow` | 已按公开契约接入两状态管内容积 + port 1 摩擦残差 + mode 2 换热项;仍需后续按 AMESim baseline 复核 `pn2pipefr_` 和 mode 1 多方语义。 |
| `PNL0002` | 8 | R-C-R 动态管路 | 第一版公开 | `amesim_pnl0002`,`flow` | 已按公开契约接入中心两状态容积 + 两端半长摩擦残差 + mode 2 换热项;仍需后续按 AMESim baseline 复核 `pn2pipefr_` 和 mode 1 多方语义。 |
| `PNL0003` | 8 | C-R-C 动态管路 | 第一版公开 | `amesim_pnl0003`,`flow` | 已按公开契约接入两端四状态容积 + 中心摩擦流 + mode 2 换热项;大压差动态闭合和 AMESim baseline 误差仍留后续修模。 |
| `PNPL01` | 16 | 零气动流源 | 第一版公开 | `amesim_pnpl01`,`boundary` | 当前实现一端零流边界,只约束端口质量流量为 0;压力源/外部边界语义留后续扩展。 |
| `PNGD00` | 1 | 氦气气体定义 | 氦气 Peng-Robinson 首版公开 | `amesim_helium_medium`,`media` | 已映射源模型 `fluidType=12/eosType=6`;密度和压力使用 PR EOS,热量学暂用手册参考点的定比热闭合。完整压力相关残余焓、比热和真实气体临界流仍待状态相关物性接口。 |
| `PNRP17` | 8 | 气动活塞与移动体耦合 | 第一版公开 | `amesim_pnrp17`,`mechanical` | 已接入 1 个气动端口和 4 个一维机械端口,按 `dp/dr/x0/gi` 计算环形有效面积、扫掠容积、容积变化率和表压作用力;已与 PNCH012、双质量块跑通 System XML 联合仿真,完整事件语义和 AMESim baseline 仍待校准。 |
| `MECMAS21` | 10 | 一维平动质量 | 第一版公开 | `amesim_mecmas21`,`mechanical` | 已接入一维机械端口 `x/v/f`、双端质量状态和基本摩擦/限位项,并跑通零力源与信号力源最小 System XML;参数注册已对齐 AMESim 选项码与条件显示,高级静摩擦、Stribeck 公式和倾角重力分量仍未实现。 |
| `LMECHN1` | 2 | 动态线性机械节点 | 第一版公开 | `amesim_lmechn1`,`mechanical` | 已接入 1..20 个右侧端口和动态最大编号左侧参考端口;工作区画布、接口编号与节点方程会随端口数同步变化,并兼容迁移旧版固定 `port_9` 工程。已跑通 `FORC -> LMECHN1 -> MECMAS21` 最小 System XML。 |
| `LSTP00A` | 8 | 弹性接触/端止动 | 第一版公开 | `amesim_lstp00a`,`mechanical` | 已接入两端一维机械端口、相对位移/速度接触力和最小 System XML 仿真;当前是连续罚函数基础版,完整 AMESim 事件/非光滑接触语义留后续 baseline 对齐。 |
| `F000` | 16 | 零力源 | 第一版公开 | `amesim_f000`,`mechanical` | 已作为一端机械零力边界公开,约束端口力为 0。 |
| `FORC` | 2 | 信号转力 | 第一版公开 | `amesim_forc`,`mechanical` | 已接入信号输入 `res` 到机械端口力源,可由 `STEP0/UD00` 驱动质量组件。 |
| `STEP0` | 8 | 阶跃信号源 | 第一版公开 | `amesim_step0`,`signals` | 已接入标量信号输出端口和求解时信号传播;当前是基础阶跃,不含更复杂事件调度语义。 |
| `UD00` | 2 | 分段线性信号源 | 第一版公开 | `amesim_ud00`,`signals` | 已接入标量信号输出端口、8 段 start/end/t 参数、循环模式和 System XML signal connection;当前按迁移实现语义处理最后一段外推,AMESim baseline 仍留后续复核。 |
| `DIRECT` | 44 | 直接连接 | 不注册为组件 | System XML `Connection` | 连接不是组件;物理连接必须保持无方向端点语义。 |
## 建议迁移批次
### 批次 1:已进入当前气动求解器的公开组件
以下模型已完成第一版公开,后续重点是 AMESim 公式和 baseline 精修:
- `amesim_pnor001`
- `amesim_pnl00r`
- `amesim_pnch023`
- `amesim_pn3node2`
- `amesim_p4node2`
这些模型已有 `MODEL_TYPE/MODEL_VERSION/PORTS/PARAMETERS/RESULT_VARIABLES/DISPLAY/create()` 契约和目录、XML、最小仿真测试;第一版状态不代表数值对齐完成。
### 批次 2:动态管路
- `amesim_pnl0001`:已第一版公开,保留后续 baseline 精修。
- `amesim_pnl0002`:已第一版公开,保留后续 baseline 精修。
- `amesim_pnl0003`:已第一版公开,保留后续 baseline 精修。
这些模型是 `test_mql` 对齐工作的核心。当前已先按动态组件契约公开,后续仍需要在固定算例和 AMESim baseline 上复核摩擦、换热、多方模式和大压差动态闭合;不能用准稳态 `pipe` 替代这些动态管路并宣称等价。
### 批次 3:信号、事件、机械和跨域组件
- `STEP0`:已第一版公开。
- `PNVO001`:已第一版公开,保留固定开度变体。
- `UD00`:已第一版公开。
- `MECMAS21`、`F000`、`FORC`、`LSTP00A`、`LMECHN1`:已第一版公开。
- `PNRP17`:已第一版公开,并完成 PNCH012 + 双质量块跨域闭合测试。
当前已具备一对一标量信号传播、一维机械端口基础闭合、弹性接触基础件、9 端机械节点,以及针对移动边界的气动容积定向传播与二次代数闭合。下一阶段重点是完整 `test_mql` 拓扑和 AMESim baseline 校准。
## 下一步执行建议
1. 以 `app.simulation.components.amesim.library` 的 22 个模型为公开清单唯一来源。
2. 优先校准 `PNCH023 / PNOR001 / PN3NODE2 / P4NODE2 / PNL00R` 与动态管路的 AMESim baseline 误差。
3. 每次调整模型都同步补充目录校验、参数边界、System XML 编译和最小仿真测试。
4. 用完整或代表性的 `test_mql` 画布校准 `PNRP17 + PNCH012` 的压力、力、位移和容积轨迹,并补齐事件边界语义。
+76
View File
@@ -0,0 +1,76 @@
# AMESim 氦气 Peng-Robinson 介质模型
## 本地资料依据
仓库中已经包含该模型所需的说明和算例数据:
- `AmesimModels/help/pneumatic_library_manual/03_gas_properties.pdf` 第 3.3 节
给出 AMESim Pneumatic Library 的气体物性层级和 Peng-Robinson 状态方程。
- `AmesimModels/help/pneumatic_library_manual/lib_pneumatic.pdf` 第 87--88 页
给出 293.15 K、1 barA 下的氦气参考物性。
- `AmesimModels/test_mql.ame` 中的 `PNGD00` 实例明确选择
`fluidType=12`(helium)和 `eosType=6`(Peng-Robinson)。
- `app/simulation/core/peng_robinson.py` 已实现纯物质 Peng-Robinson 方程,
并定义了 `HELIUM_PR`。
AMESim 手册中的 Peng-Robinson 形式为:
```text
p = r T / (v - b) - a alpha(T) / (v^2 + 2 b v - b^2)
a = 0.457235583 r^2 Tc^2 / Pc
b = 0.07779607 r Tc / Pc
alpha(T) = [1 + m (1 - sqrt(T/Tc))]^2
m = 0.37464 + 1.54226 omega - 0.26992 omega^2
```
当前 Python EOS 使用等价的摩尔体积形式,并通过最大物理解选择气相根。
## 首版参数
| 参数 | 数值 | 本地依据 |
| --- | ---: | --- |
| 摩尔质量 | `0.004002602 kg/mol` | `HELIUM_PR` |
| 临界温度 | `5.1953 K` | `HELIUM_PR` |
| 临界压力 | `227460 Pa` | `HELIUM_PR` |
| 偏心因子 | `-0.385` | `HELIUM_PR` |
| 定压比热 | `5193 J/(kg*K)` | AMESim 手册表 11.1 |
| 定容比热 | `3116 J/(kg*K)` | AMESim 手册表 11.1 |
| 比气体常数 | 由 `R/M` 计算,约 `2077.26 J/(kg*K)` | EOS + 手册表 11.1 |
| 293.15 K 黏度 | `1.96e-5 Pa*s` | AMESim 手册表 11.2 |
| Sutherland 常数 | `79.4 K` | 已迁移 `test_mql` 管路近似 |
手册参考点还给出 `rho=0.164 kg/m3`、`gamma=1.667`、`Z=1.0005`。
## 组件与索引映射
公开介质组件使用:
```text
modelType = amesim_helium_medium
gi = 画布自动分配的 1..99 引用索引
property_model = 0 (Peng-Robinson,本应用内的稳定编号)
fluidType = 12 (AMESim 元数据)
eosType = 6 (AMESim 元数据)
```
`property_model` 与 AMESim 的 `eosType` 有意分离:前者是单个介质组件内部的
下拉选项编号,后者用于保留 AMESim 原模型语义。以后给氦气增加其他计算方法时,
只需扩展氦气的物性模型注册表。
源归档的 `gasName` 缓存文本与结构字段存在冲突,因此介质识别以
`fluidType=12`、`PNGD_HELIUM` 和组件标签为准,不使用 `gasName` 猜测。
## 首版计算边界
首版公共组件实现以下闭合:
- `rho(p,T)`、`p(rho,T)` 和控制容积压力使用 Peng-Robinson EOS;
- `u=cv*T`、`h=cp*T`,温度反解使用同一组定比热;
- 黏度使用以 293.15 K 为参考点的 Sutherland 近似;
- `gi` 注册、气动连通域一致性、XML/JSON 保存均复用现有介质机制。
这是一版可运行的“PR 压力--密度闭合 + 定比热热量学”,不是 AMESim 真实气体
物性的完全复刻。AMESim 的 `Cp/Cv/h/mu/rho/Z` 接口均可随 `(p,T)` 变化,而当前
`GasMedium` 的焓、内能和比热接口只有温度参数。后续若要接入完整残余焓、
压力相关比热和真实气体临界流,应先扩展为状态相关物性接口,再调整气室、管路和
孔口公式。
+285
View File
@@ -0,0 +1,285 @@
# SystemSimulationApp 仿真性能评估(2026-08-15)
> 代码基线:`model-development@6a06489`,随后只加入本报告所述的可选埋点和基准工具。
> 本次评估的是前端流式接口实际使用的 System XML 求解路径;所有时间均为本机实测,不代表其他机器的绝对性能。
> 2026-08-16 已按本报告建议实现“仿真内独立物性缓存”“worker 启动暖机”、高刚度试探压力边界修复、方程关联块闭合、代数稀疏回退、外部 volume 跨域 ODE Jacobian 修正和 dense output 惰性构造;原始基线数据保留用于对照,当前大型 XML 复验见第 11 节。
## 1. 结论
1. **压力—流量闭合是原始基线的首要热点。** 2026-08-15 深度审计中,三个气动短算例有 71%~85% 的计时落在 `PressureFlowSolver.solve()` 的包含时间内。它同时包含残差组装及其触发的物性调用,不能与物性时间相加;2026-08-16 已完成方程块与稀疏首轮,当前现状见第 11 节。
2. **物性调用存在很高的完全相同输入重复率。** 按每次代数闭合重置精确输入影子集合后,空气链路、空气分支和氦气阶跃的重复率分别为 91.2%、96.5% 和 82.3%。空气公式很便宜,不能只凭重复率加缓存;Peng–Robinson 氦气更值得优化。
3. **评估基线已有的两项氦气 LRU 精确缓存有效。** 冷缓存审计中,`properties_from_mU` 命中率 95.5%,`temperature_from_pressure_enthalpy` 命中率 78.6%;21 次配对端到端测试中,暖缓存比每次清空缓存快约 7.9%。这些数据描述 2026-08-15 的原始基线,后续实现见第 9 节。
4. **长仿真的时间主要花在积分阶段。** 10 s 氦气均压算例耗时约 10.6~11.5 s,其中标准埋点测得积分占 90.5%,初始化约 4.2%,逐采样点后处理约 5.0%。
5. **结果 JSON 暂不是这些算例的首要矛盾。** 四个短算例的最终 NDJSON 结果约 29~59 KiB,编码中位数约 0.4~1.2 ms;501 个采样点的长算例约 507 KiB,编码约 18.5 ms。
6. **首次仿真有明显冷启动。** 新 Python 进程第一次短算例约 0.71 s,预热后同类算例约 0.06~0.13 s。剖析表明首次进入 SciPy 求解路径的惰性导入占了主要差额;这是服务首请求延迟,不是稳态吞吐。
7. **用户提供的高刚度 XML 已能完成 10 s 仿真。** 原始基线在 `0.000175 s` 左右因 `Initial guess is outside of provided bounds` 失败;原因是压力优化下界为 1 Pa,排除了 RK45 合法产生的、仍严格大于 0 Pa 的亚帕试探值。2026-08-16 将优化器压力下界放宽到 0 Pa 后,构成方程仍要求压力严格为正,完整 RK45 仿真通过且没有触发可恢复重试。
8. **闭合不再固定执行第二次全网压力求解,也不再把一个大物理岛等同于一个求解块。** 每次闭合仍先保证全网成立;stream 更新后,只重算声明为 stream-sensitive 的方程—未知量关联块。物理连通岛只是安全范围,当前 `secondaryBlockCount` 是真实方程块数;无法安全分类的自定义模型会保守回退原全网路径。
9. **历史物理岛版收益取决于模型拓扑。** `off` 模式配对测试中,空气链、空气分支、氦气阶跃、机械接触和高刚度短算例分别改善 7.9%、6.5%、0.6%、21.4% 和 14.6%。这些数据保留作纵向基线,但该版已由方程关联块实现取代。
10. **大型分支 XML 的 `0.69 s` 现象已定位并完整跑通。** 输入 SHA-256 为 `2fb95e65f5de0c85a6a17802aef74ea004087323fd00fd8d01acf0184ff71d48`,含 98 个组件、472 个代数未知量、74 个 ODE 状态。根因是外部 volume 跨域耦合在 ODE Jacobian 依赖图中漏 12 个实测显著项,而不是线程死锁;修正后结构由 1092 非零/27 色变为 1284 非零/31 色。最终稳定代码连续三次完整 `0~0.81 s` 用时 79.049 s、74.658 s 和 85.103 s,积分统计均为 `nfev/njev/nlu=3393/226/667`、接受步 1009。
11. **代数非线性回退已有可信声明图上的稀疏保护链。** 先求本轮未闭合方程块的 union sparse;失败恢复原始 `x0` 后做 global sparse,再失败才做 dense。受控扰动微基准在相同 `max_nfev=20` 下把真实残差回调由 3796 降至 164、墙钟约 7.357 s 降至 0.634 s(约 11.6 倍);活动接触或不可信声明仍走兼容 dense 路径。
12. **首轮其他优化均按适用范围解释。** worker 暖机已覆盖 sparse `least_squares` 的 LSMR 路径;dense output 仅在跨采样点或需要状态事件时构造,但本次大型 XML 含状态事件,因此没有本案收益。机械 `atol` 的 `1e-12→1e-10` A/B 约快 16%,但会改变机械误差合同,未采用;外层 thermofluid 流量固定点相对容差的 `1e-12→1e-9` A/B 反而增加 BDF 步数并改变轨迹,也未采用。
## 2. 埋点实现与污染控制
性能开关由进程启动环境变量 `SIMULATIONAPP_PROFILE` 决定:
| 模式 | 用途 | 记录内容 | 适合场景 |
| --- | --- | --- | --- |
| `off` | 正常运行,默认值 | 不在响应中加入性能数据;装饰器在模块加载时直接返回原函数 | 正式仿真和最终性能对比 |
| `standard` | 低开销阶段统计 | XML 校验、网络编译、系统构造、初始化、积分、后处理、结果组装 | 日常定位“大阶段” |
| `audit` | 深度审计 | 再展开 RHS、代数闭合、压力流量、stream、刷新、导数和物性内核 | 短算例诊断、调用频率与缓存评估 |
一次运行使用一个 `ContextVar` 隔离的 `PerformanceTrace`,不会把不同仿真任务的阶段计数混在一起。成功或失败的求解结果在 profiling 模式下都会把快照放入 `diagnostics.performance`。主要字段为:
- 阶段:`calls`、`inclusiveNs`、`selfNs`、`maxNs`、`errors`;
- 物性:上述时间字段,以及介质、操作、缓存查询/命中/未命中;
- audit 专有:闭合内精确输入唯一数/重复数、逆解迭代总数/最大值/收敛与未收敛次数;
- `propertyOutermostNs`:只累计最外层物性调用,避免把嵌套 PR 内核时间重复相加。
标准模式只保留低频的大阶段计时。21 次氦气阶跃配对运行中,标准模式相对关闭模式的中位开销为 1.9%;四个短算例分开校准为 0.5%~2.9%。audit 会逐次生成精确指纹并计时,短算例可慢到约 2.5~5 倍,因此 audit 数据用于定位和计数,最终优化收益必须回到 `off` 模式复测。
将当前代码的 `off` 模式与备份提交 `6a06489` 同时运行 21 次氦气阶跃,墙钟中位数差为约 0.3%,处于本机噪声范围。也就是说,默认关闭时没有观察到稳定的热路径退化。
## 3. 测试方法
环境:Windows 11、Python 3.12.3、SciPy 1.18.0、64 位 Intel 处理器。仓库没有 PyInstaller/Nuitka 等可执行文件构建链,本次直接使用项目实际启动后端的 `.venv-win` 解释器。把同一 Python 代码再包成单文件只会混入解包和启动成本,不会使这里的求解内核更接近生产路径。
基准工具入口:
```powershell
.venv-win\Scripts\python.exe -m app.simulation.benchmark_performance `
--mode audit --warmups 1 --runs 3 `
--factory "helium_step=tests.test_amesim_pnvo001_signal_xml:high_pressure_helium_step_project" `
--output app/data/performance-evaluations/helium-step.json
```
工具默认传入取消检查回调,从而走与前端流式仿真相同的低层逐步积分路径。它记录墙钟、进程 CPU、最终 NDJSON 编码、输入 SHA-256 和完整性能快照。原始 JSON 写入被 Git 忽略的 `app/data/performance-evaluations/`,避免把机器相关的大量样本提交到仓库。
本次代表算例:
| 算例 | 内容 | 暖机后 `off` 墙钟中位数 | 重复次数 |
| --- | --- | ---: | ---: |
| `air_chain` | 空气气缸—节流孔—管路—储罐 | 62.4 ms | 9 |
| `air_branched` | 空气分支网络 | 130.3 ms | 9 |
| `helium_step` | 高压 PR 氦气、信号阶跃阀 | 65.2 ms | 9 |
| `mechanical_contact` | MECMAS21/LSTP00A 弹性接触 | 33.0 ms | 9 |
| `helium_long` | 10 s PR 氦气均压、501 个输出点 | 10.63 s | 1 |
短算例先暖机 2 次再测 9 次;缓存 A/B 使用两个同时启动的独立进程各暖机 5 次、测量 21 次,以尽量抵消瞬时系统负载。长算例只测 1 次,因此它只用于判断数量级与阶段占比。
## 4. 深度阶段结果
下表时间是 audit 中位数,会包含审计自身开销;调用数和相对热点比绝对时间更可靠。
| 算例 | RHS | 完整闭合 | 压力流量求解 | 压力流量包含时间占 audit 总时间 | 物性调用 | 闭合内精确重复率 |
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
| `air_chain` | 33 | 37 | 74 | 77.0% | 4,265 | 91.2% |
| `air_branched` | 23 | 26 | 56 | 84.6% | 7,378 | 96.5% |
| `helium_step` | 79 | 102 | 235 | 71.2% | 11,121 | 82.3% |
| `mechanical_contact` | 74 | 78 | 156 | 29.5% | 0 | 不适用 |
这是 2026-08-15 原始基线的闭合数据:当时每次闭合先做 1 次压力求解,然后最多执行 25 轮 `stream → pressure-flow` 固定点,理论上最多 26 次;四个算例平均为 2.00、2.15、2.30 和 2.00 次/闭合。随后 2026-08-16 的第一版先把后续重算缩到 stream-sensitive 物理连通岛;该历史版本又被当前方程关联块版取代。当前做法是在安全物理范围内只重算敏感方程实际关联的块,没有敏感块时不强制第二次压力求解。这样裁剪的是无效重算,不是删除真实耦合。
## 5. 物性调用与缓存结果
氦气阶跃的冷缓存 audit 代表运行:
| 操作 | 调用 | 命中/未命中 | 命中率 | 真实逆解次数 | 平均迭代 | 最大迭代 | 未收敛 |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| `properties_from_mU` | 1,930 | 1,843 / 87 | 95.5% | 87 | 3.99 | 5 | 0 |
| `temperature_from_pressure_enthalpy` | 398 | 313 / 85 | 78.6% | 85 | 5.00 | 5 | 0 |
在原始基线中,暖机后以相同配置重复运行,这两项在代表快照中均为 100% 命中,说明当时的进程级精确 LRU 能跨同配置运行复用确定性轨迹。关闭埋点的端到端配对结果为:暖缓存中位数 77.05 ms,每次清空缓存为 83.69 ms;换算为暖缓存约快 7.9%。
audit 的自身时间排序还显示:`isentropic_density_pressure_factor` 调用 398 次,`density` 业务入口及 PR 密度内核各调用 1,198 次,PR `compressibility_roots` 调用 1,712 次。同一 `(p,T)` 周围存在“等熵因子内部求密度,随后流量公式再次求密度”的重复机会。这里应优先复用同一闭合内的精确结果或合并 API;不要用四舍五入/容差键缓存,否则会在残差函数中制造平台并影响 ODE/least-squares 的有限差分。
空气算例虽然精确重复率更高,但理想气体公式本身只有少量算术。对这些廉价函数增加字典查询可能比重算更慢,应先做专门 A/B,不应套用氦气结论。
## 6. 输出与失败样本
| 算例 | 最终结果大小 | NDJSON 编码中位数 |
| --- | ---: | ---: |
| `air_chain` | 29.2 KiB | 0.39 ms |
| `air_branched` | 59.1 KiB | 1.21 ms |
| `helium_step` | 55.5 KiB | 1.10 ms |
| `mechanical_contact` | 51.4 KiB | 0.62 ms |
| `helium_long` | 507.4 KiB | 18.48 ms |
用户高刚度 XML 的输入 SHA-256 为 `27048a99da0a21922d75785b760c3b5d04be3349b8aef6fbfedfd811d87ef1d5`。原始 audit 失败运行记录到 80 次 RHS、83 次闭合、165 次压力流量求解,最后一项各有 1 次错误;其 1.08 s 只代表历史失败路径,不能当作完整模型性能。允许严格正的亚帕试探压力后,同一模型已完成 10 s,当前完整性能结果见第 10 节。
## 7. 后续优化顺序
1. **[2026-08-16 已落实方程块与稀疏首轮] 优化压力流量执行计划。** 压力/流量方程、显式赋值、热流依赖和方程—未知量关联图已预编译;每轮 stream 更新后只重算敏感方程块。非线性时先做可信未闭合块的 union sparse,失败从原 `x0` 做 global sparse→dense;自定义、活动接触或结构不安全的网络保守回退兼容路径。后续仍可评估 equality group 真正消元和解析 Jacobian。
2. **[2026-08-16 已落实] 减少 PR 物性重复。** 复用组件当前 `(m,U,V)` 的状态恢复结果,并缓存相同输入的密度和等熵因子;沿用精确键、有界容量和按仿真隔离原则。
3. **[2026-08-16 已落实] 处理冷启动。** worker 在 FastAPI lifespan 中完成无业务副作用的 SciPy/XSD 微型暖机后再接收请求;不要把约 0.65 s 冷启动归因到每次仿真。
4. **长算例再看后处理复用。** 当前代表长算例的积分占 90.5%,所以积分/闭合仍优先;当采样更密或变量更多时,再评估复用已接受状态闭合、按需变量和降采样。
5. **[2026-08-16 已落实] 修复高刚度 XML 的压力试探边界。** 优化器允许严格正的亚帕试探值,物理构成方程仍拒绝零压和负压;该模型已用 10 s RK45 回归验证,不需要把这类合法试探误报为可恢复拒步。
6. **[2026-08-16 已落实] 补全外部 volume 的跨域 ODE Jacobian 依赖。** 机械位移写入气室容积后,气动储能与机械力平衡必须在状态稀疏图中双向关联;大型分支 XML 据此完整跑通。机械 `atol` 放宽虽有约 16% 的单次改善但改变精度合同;flow 固定点容差放宽反而增加步数,均未采用。
7. **[2026-08-16 已落实] dense output 惰性构造。** 仅当已接受步跨越下一样本或需要状态事件时构造插值;含状态事件的模型每步仍需要,不将其宣传为大型分支 XML 的收益来源。
## 8. 本次评估边界
- 没有固定 CPU 亲和性或关闭后台程序,短算例绝对时间存在数毫秒波动,因此以中位数和配对实验为主。
- audit 会显著改变廉价函数的单次耗时;不能把 audit 的物性毫秒数直接当成关闭埋点后的真实占比。
- 原始基线的 LRU 命中/未命中来自调用前后的全局 `cache_info()` 差值;本报告当时均为单任务运行。该并发统计限制已由第 9 节的仿真内独立缓存消除。
- 直接抛出 `HTTPException` 的校验/执行异常会结束 trace,但当前不会把快照附到错误响应;demo 属于返回 `failed` 部分结果的路径,所以本报告能够取得其失败快照。
- 本批没有测峰值 RSS、1/2/4 并发吞吐、浏览器解析/绘图或 8/32 单元拓扑扩展曲线。
- 没有为评估引入新的近似缓存、容差调整或求解器算法变更;所有性能结论都与数值优化改动解耦。
## 9. 2026-08-16 缓存与启动暖机复验
原先两个函数级 LRU 会在 Python 进程内跨仿真共享条目。现已改为每次仿真通过
`ContextVar` 创建独立缓存,并在运行结束后整体释放;并发任务不会共享缓存或
命中统计。每个“物性操作 + 介质实例”使用独立的 C 层有界 LRU,默认上限为
8192 项。当前只缓存四条有明确重复收益的氦气路径:密度、等熵密度—压力因子、
`properties_from_mU` 和 `temperature_from_pressure_enthalpy`。
同一个高压氦气阶跃算例的冷缓存 audit 结果为:
| 操作 | 调用 | 命中 / 未命中 | 命中率 |
| --- | ---: | ---: | ---: |
| `density` | 578 | 403 / 175 | 69.7% |
| `isentropic_density_pressure_factor` | 398 | 310 / 88 | 77.9% |
| `properties_from_mU` | 1,930 | 1,843 / 87 | 95.5% |
| `temperature_from_pressure_enthalpy` | 398 | 313 / 85 | 78.6% |
四项合计 2,869 次命中、435 次未命中、435 个最终条目,未发生驱逐。PR 三次根
计算从原审计的 1,712 次降到 689 次。关闭埋点、各自预热 5 次并测量 21 次时,
缓存开启/完全关闭的墙钟中位数在本机分别为 71.6 ms 和 154.0 ms。这个对比表示
“四项缓存整体”相对“完全不缓存”的收益,不能误解为相对旧版两个 LRU 又提升
53.5%。缓存开关两次运行的完整 `series` 和 `final` SHA-256 一致。
10 s、501 个输出点的氦气均压算例单次复验中,缓存开启和关闭分别用时
14.18 s 与 26.64 s;开启时命中 400,571 次、未命中 96,427 次。四个缓存均达到
各自 8192 项上限,共发生 63,659 次 LRU 驱逐,但仍完成到 10 s。该长算例每种
配置只测了一次,只能说明容量上限确实生效且仍有收益,不能作为稳定百分比承诺。
worker 暖机只执行内存中的一维 BDF、`least_squares`、`brentq`、稀疏 Jacobian
分组和 XSD 编译,不运行用户模型、不写文件、不填充氦气业务缓存。当前暖机还显式
覆盖携带 `jac_sparsity`、使用 sparse LSMR trust-region 子问题的代数路径,避免真实
用户任务第一次触发该 SciPy 分支时再承担惰性初始化。三个新进程的
中位数为:不暖机首个仿真 709.1 ms;启动暖机本身 637.1 ms;暖机后的首个仿真
69.8 ms,同进程第二次约 68~74 ms。也就是说总初始化成本没有消失,而是被
移到服务宣告就绪之前。
## 10. 2026-08-16 历史物理岛版闭合复验
> 本节保留方程关联块实现之前的物理岛版数据,用于纵向对照。它已不是当前执行计划;当前结果见第 11 节。
该轮把 signal 源/连接、stream 组件/端口/连接以及气动外部 volume 组件/连接的
静态查找移到系统构造阶段;运行时仍按每个状态执行实际传播和热力刷新。压力流量
闭合先进行一次全网求解,再根据预编译的依赖声明,只对 stream-sensitive 的物理
连通块做后续固定点重算。对未声明依赖的自定义 stream 组件、跨组件方程或非方阵
物理岛,执行计划保守回退到原全网求解,不以性能换取模型兼容性。
该历史版本的 `off` 模式配对结果如下。表中百分比是同一模型、同一数值配置下的墙钟改善,适合
判断优化方向,不是跨机器速度承诺:
| 算例 | 历史物理岛版相对原全网闭合的改善 |
| --- | ---: |
| `air_chain` | 7.9% |
| `air_branched` | 6.5% |
| `helium_step` | 0.6% |
| `mechanical_contact` | 21.4% |
| high-stiffness short | 14.6% |
`helium_step` 在该历史版本里只有一个需要在 stream 后继续求解的敏感物理岛,因此 0.6% 的改善处于
小幅范围;不能用其他无敏感岛模型的收益夸大氦气模型的效果。积分完成后的后处理
也没有跳过闭合:每个输出采样点仍重新应用状态、执行完整 `_close_current_state()`
并提取结果,只是闭合内部使用同一安全执行计划。
完整 high-stiffness 10 s 算例的历史 `off` 基线约为 28.126 s;本轮全部改动后的
多次运行中位数为 13.694 s。这个跨版本对比同时包含本轮多项改动,不能把差额全部
归因于物理岛裁剪。为了单独核对当时的闭合计划,在同版代码上做 optimized/forced-global
对照,墙钟分别为 14.676 s 和 16.929 s,完整 `series` 完全一致;这组受控对比才
直接反映该模型的执行计划收益。
当前诊断已经进一步消除旧命名歧义:`secondaryPhysicalIslandCount` 表示安全分类的
物理范围,`secondaryBlockCount` 表示方程—未知量关联图中的真实块数,
`secondaryUnknownCount` 表示这些方程块合计未知量。`solveCount` 统计实际求解器调用,
`closurePassCount` 单列发生过压力求解的闭合 pass;`last` 中还包含
`residualEvaluations`、`jacobianMode`、dense/方程块回退状态。不能再把
`secondaryBlockCount` 解释为物理岛数。
## 11. 2026-08-16 大型分支 XML 与方程块首轮复验
验证输入 `test_mql-full-branches-01-04.xml` 的 SHA-256 为
`2fb95e65f5de0c85a6a17802aef74ea004087323fd00fd8d01acf0184ff71d48`。模型规模如下:
| 项目 | 数量 |
| --- | ---: |
| 组件 | 98 |
| ODE 状态 | 74 |
| 压力/流量/机械代数未知量与方程 | 472 / 472 |
| 代数声明图结构非零 | 919(约 0.413%) |
| 全部独立代数方程块 | 58 |
| stream 后续敏感方程块 | 9,合计 192 个未知量 |
### 11.1 `0.69 s` 慢区的根因与完整结果
旧代码在 `0.69 s` 左右不是线程死锁:它仍会缓慢前进,但 BDF 在刚性变化区大量
缩步、重建有限差分 Jacobian 和执行 LU。定位出的结构错误是气动外部 volume
跨域耦合没有完整进入 ODE 状态依赖图。机械位置先写入气室容积,气室压力又反馈到
机械力平衡;旧图只沿普通物理端口追踪,漏掉这条闭环中的 12 个实测显著导数项。
修正后,状态稀疏图由 1092 个非零项、27 个颜色组变为 1284 个非零项、31 个颜色组。
颜色数增加是因为补上了真实依赖,并非回退到更差算法;完整 Jacobian 让 BDF 少走
错误 Newton 方向和重复试步。功能收口过程中的较早阶段测量为 92.187 s;最终稳定
代码连续三次完整 `0~0.81 s` 分别用时 79.049 s、74.658 s 和 85.103 s,数值工作量一致:
| 指标 | 结果 |
| --- | ---: |
| `nfev` | 3393 |
| `njev` | 226 |
| `nlu` | 667 |
| 接受步 | 1009 |
| 求解器启动次数 | 3 |
| 压力流量求解 | 28008,全部 seeded |
| 方程块/dense 非线性回退 | 0 / 0 |
| 三次 full-response 规范 JSON SHA-256 | `454cd11aece1c4a2296a88e2c1dd592eeace28565e342235fb7a7df34de5b18f` |
| `physical-solution-v1` SHA-256 | `04982f427867801c582fea81c6e2da0b726bd8a61d7894b311e4a807b19e89a7` |
这里的 full-response 哈希覆盖完整响应,所以诊断字段增删也会改变它。为稳定比较物理
结果,`physical-solution-v1` 只把 `{status, simulatedUntil, requestedStopTime, series,
final}` 投影为待哈希对象;schema 名只是外部标签,不进入对象。两种口径都使用
`json.dumps(sort_keys=True,separators=(",",":"),ensure_ascii=False)` 后计算 SHA-256。
旧 `09b5c7…` 是聚合诊断和最终 union 路径收口前的 full-response 哈希,响应结构不同,
不作为最终结果,也不能与当前口径直接比较。
容差 A/B 必须分开解释:机械状态 `atol` 从 `1e-12` 放宽到 `1e-10` 的单次测试约快
16%,但会改变机械状态与事件的误差合同,当前未采用;外层 thermofluid 流量固定点
相对容差从 `1e-12` 放宽到 `1e-9` 后,BDF 内部步数反而增加并改变积分轨迹,也未
采用。完整成功来自依赖图修正和闭合优化,不是牺牲积分精度或闭合精度。
### 11.2 stream 方程块与受控 A/B
第一次压力流量求解仍承担“全网必须成立”的语义;但可信声明图允许它在非线性时只把
本轮未闭合的独立方程块合并成一个 union sparse 问题,而不是固定构造 472 变量的
dense 问题。stream 更新后的固定点进一步只处理 9 个敏感方程块、合计 192 个未知量,
不再因为机械总线把拓扑连成一个大物理岛,就重复求解全部 472 个未知量。
在相同当前代码、相同 `0~0.01 s` 区间做 optimized/forced-global 配对,墙钟分别为
16.200 s 和 18.584 s,物理解与 `series` 逐值一致。这组对照隔离的是后续 stream
闭合作用域;它不包含完整 `0.81 s` 慢区的全部收益,不能与最终完整运行直接换算百分比。
### 11.3 非线性稀疏回退与其他首轮项
全局非线性回退当前采用兼容保护链:可信声明图先求未闭合方程块的 union sparse;
若块解失败,先把所有共享端口未知量恢复到原始 `x0`,再做 global sparse;若 sparse
仍未达到既有残差合同,再次从原 `x0` 做 global dense。活动接触会改变坐标/活动集,
不可信自定义声明也可能漏依赖,这两类不冒险使用静态稀疏图,继续走 dense 兼容路径。
受控扰动微基准在相同 `max_nfev=20` 下得到:
| 路径 | 真实残差回调 | 墙钟 |
| --- | ---: | ---: |
| dense | 3796 | 7.357 s |
| sparse | 164 | 0.634 s |
同一评估预算下约为 11.6 倍的回退成本改善;两条路径在 20 次优化器评估内都没有收敛,
所以这是“数值 Jacobian 试算成本”微基准,不是整体仿真加速承诺。诊断用
`residualEvaluations` 记录真实残差回调,避免仅看 SciPy `nfev` 漏掉内部差分调用。
worker 暖机现已覆盖带 `jac_sparsity` 的 sparse LSMR `least_squares` 路径。逐步积分的
dense output 也改为只在当前步跨越下一采样点或需要状态事件定位时构造;本 XML 含
状态事件,所以每步仍需要插值,这项优化对最终 79.049 s/74.658 s/85.103 s 复验没有收益。
@@ -0,0 +1,597 @@
# SystemSimulationApp 后端求解逻辑与效率优化调研(通俗版)
> 调研基线:2026-08-15(System XML v3 迁移后);2026-08-16 已补充压力边界、预编译闭合执行计划、方程关联图分块、代数稀疏回退、ODE Jacobian 修正和实测复验的当前状态。
> 本文所称“主求解路径”是当前前端实际调用的 System XML 流式接口;固定 TestModel 和 Test MQL 接口另行说明。机器相关的实测结果单独见[仿真性能评估 2026-08-15](仿真性能评估-2026-08-15.md)。
## 0. 三分钟读懂
### 0.1 求解器到底在做什么
先不管 ODE、RHS、BDF 这些名字。把一次仿真想成制作一段工程动画:
1. **检查装配图。** 气管有没有漏接,控制线方向对不对,模型参数是否齐全。
2. **读取当前“存量”。** 例如气室里有多少气体和能量,质量块现在的位置和速度。
3. **让当前瞬间自洽。** 根据这些存量,把此刻的压力、流量和力反复对账,直到连接规则和组件方程同时满足。
4. **计算变化速度。** 得出“下一小段时间内,质量、能量、位置、速度将怎样变化”。
5. **内部小步前进。** 求解器自己决定每次走多小;变化剧烈时会缩短步长。
6. **按用户指定时刻留快照。** 内部可能算很多小步,但结果文件只在 XML v3 的 `sampleStep` 指定的时刻保存数值。
7. **把进度和结果交给前端。** 运行中发心跳/进度,结束时一次性发送完整曲线数据。
项目里的技术说法“**半显式 ODE + 每次变化率计算前做代数闭合**”,翻译成人话就是:**会积累的量用时间积分向前推;必须在当前瞬间成立的关系,每次都先对账求平衡。**
### 0.2 用仓库里的真实案例贯穿全文
`tests/test_generic_system_xml_simulation.py:86-145` 有一条完整回归气路:
```text
高压气缸 低压储气罐
0.01 m³、500 kPa 0.1 m³、100 kPa
──> 节流孔 ──> 1 m 管路 ──>
```
图中箭头只表示这个初始压差下**预计**的气流方向,不代表物理连线本身有 `source/target` 方向。回归测试检查了:
- 气缸压力下降;
- 储气罐压力上升;
- 总质量和总能量守恒;
- 把工程 JSON 中物理边的 `source/target` 对调,结果不变。
测试只运行 `0~0.01 s`,设置结果采样 `sample_step=0.005 s`、内部上限 `max_step=0.001 s`、`method=BDF`(`tests/test_generic_system_xml_simulation.py:86-145, 254-300, 354-372`)。在 System XML v3 中,前两个字段分别写成 `sampleStep` 和 `maxStep`。它们可以这样理解:
```text
结果快照: 0 s -------- 0.005 s -------- 0.01 s
内部计算: 0 s - 小步 - 小步 - 小步 - ... - 0.01 s
每个内部步最多 0.001 s,也可能更短或被重算
```
- `sampleStep`:相机隔多久保存一张结果快照;
- `max_step`:求解器一次内部前进最多能走多远;
- `BDF`:一种适合系统中“有的变化快、有的变化慢”的自适应算法。本文不需要展开它的公式。
另一个真实案例位于 `tests/test_amesim_pnvo001_signal_xml.py:87-204, 230-246`:高、低压氦气室之间有一个阀,阶跃信号在 `0.04 s` 从 0 跳到 1。求解器会像遇到“定时闹钟”一样,准确停到事件时刻,更新阀命令,再从该时刻继续积分。
### 0.3 常见术语翻译
| 技术词 | 先这样理解 | 本项目里的具体含义 |
| --- | --- | --- |
| 动态状态(state) | 会随时间积累的存量 | 气体质量/内能 `[m,U]`,或机械速度/位置 `[v,x]` |
| 代数量 | 当前瞬间的仪表读数 | 压力、流量、连接力等;由当前状态和约束求出 |
| 代数闭合(closure) | 把所有账对平 | 让组件方程、连接守恒和当前状态同时成立 |
| 变化率计算(RHS) | 算下一刻变化有多快 | 输入当前状态,输出 `dm/dt`、`dU/dt`、加速度等 |
| ODE 积分 | 根据变化率向时间前进 | SciPy 的 BDF、Radau、RK45 等 |
| DAE | 状态和瞬时约束一起交给专用求解器 | 当前主内核不是通用 DAE 求解器 |
| stream 焓 | 气体随流动携带的“能量标签” | `h_outflow` 按实际流向传播/混合,不是两端温度相等 |
| 非线性迭代(`least_squares`) | 直接算不出时反复试值 | 压力流量快速路径失败后的回退方案 |
| Jacobian | “改一个量会影响哪些方程”的灵敏度地图 | 可帮助 BDF/Radau 和非线性求解少做试算 |
| dense output | 两个内部步之间的插值尺 | 用来补采样点和定位机械事件 |
| NDJSON | 一行一个 JSON 消息 | 同一 HTTP 响应中依次发送心跳、进度、结果 |
| worker | 后台办事通道 | 当前脚本是一个 Uvicorn worker;流式任务另开求解线程 |
只想了解系统如何运行,可以读第 0、3、5、6、9、10、13 节;需要改求解器时,再阅读其余技术细节和第 15 节代码索引。
## 1. 结论先行
1. **[已实现] 当前主内核是“半显式 ODE + RHS 内代数闭合”。** 动态组件只把储能状态交给 ODE 积分器;每次计算导数前,系统先传播信号、刷新热力状态、求压力/流量代数网络、传播变容边界、迭代 stream 焓并更新机械加速度。它不是通用 DAE 求解器,也不等价于完整 Modelica `inStream/actualStream` 语义(`README.md:18-20`、`app/simulation/README.md:174-195`)。
2. **[已实现] 当前前端主链路是 System XML 流式仿真。** 浏览器生成 XML,经 `POST /api/system-xml/simulate-stream` 发送;后端以 NDJSON 返回心跳与进度,最后在一个 JSON 行中返回完整结果。不是 WebSocket 或标准 SSE。
3. **[已实现] XML v3 的 `sampleStep` 是输出采样间隔,不是固定积分步长。** 内部的 `max_step`(XML 为 `maxStep`)才是自适应积分步长上限;`BDF/Radau/LSODA/RK45/RK23/DOP853` 均受支持。流式运行因总是提供取消检查,会使用 SciPy 低层求解器逐个已接受步推进。
4. **[已优化] 每次完整闭合仍先做 1 次语义上的全网压力流量求解,再传播 stream;后续固定点只重算 stream-sensitive 方程实际关联的方程块。** 物理连通岛现在只是安全分类的第一层,真正执行的 `secondaryBlockCount` 是方程—未知量关联图中的块数;无法可信分类的自定义或异常结构仍保守回退原全网路径。积分结束后,每个输出采样点仍执行一次完整闭合并提取结果。
5. **[已实现] 代数求解已有因果化快路径、方程块和稀疏 `least_squares` 回退。** 对可信内置声明,求解器从方程—未知量关联图编译 `jac_sparsity`,先把本轮未闭合的独立方程块合并成一个 union sparse 问题;若失败,恢复到同一个原始 `x0`,再执行全局 sparse,仍失败才执行兼容的 dense 路径。活动接触或不可信自定义声明不会冒险稀疏化,保留 dense 兼容路径。当前没有解析 Jacobian,但不再是“完全未提供 `jac_sparsity`”。
6. **[已实现] 当前启动脚本是一个 Uvicorn worker。** 每个流式任务再创建一个无并发上限的 daemon 线程和无界队列;没有进程池、集中任务队列、CPU/内存配额或持久化作业系统。同步仿真端点还会在 `async def` 中直接执行 CPU 密集代码。
7. **[推断] 优化应分两条线:**
- 单算例速度:减少闭合 pass、代数未知量与残差装配,缓存热物性,改善外层积分尺度/Jacobian,减少后处理重算;
- 服务吞吐与资源稳定性:有界进程 worker、结果分块/按需返回、任务表主动清理和前端结果存储降副本。
8. **[已修复] 高刚度 XML 不再因合法亚帕试探压力失败。** 压力优化下界由 1 Pa 放宽到 0 Pa,允许严格正的亚帕试探值,同时构成方程仍拒绝零压和负压;同一 10 s RK45 算例已完成且没有可恢复重试。
9. **[历史实测] 闭合裁剪的收益随拓扑明显变化。** 已被方程块版取代的物理岛版短算例 `off` 配对改善为 0.6%~21.4%;其 high-stiffness 10 s optimized/forced-global 对照为 14.676 s/16.929 s,完整 `series` 一致。数据保留作纵向基线,不能当作当前方程块版结果,也不能把其他拓扑的收益外推给单一氦气敏感岛。
10. **[实测] 98 组件的大型分支 XML 在 `0.69 s` 附近的长时间停留不是死锁。** 根因是气动外部 volume 跨域耦合在 ODE 有限差分 Jacobian 依赖图中漏了 12 个实测显著项,BDF 因此反复做小步和 Jacobian/LU 工作;修正后结构非零数由 1092 增至 1284、颜色组由 27 增至 31。最终稳定代码连续三次完整 `0~0.81 s` 用时 79.049 s、74.658 s 和 85.103 s,积分统计均为 `nfev/njev/nlu=3393/226/667`、接受步 1009;stream 后续闭合为 9 个方程块、合计 192 个未知量。
## 2. 证据标签与范围
- **[已实现]**:当前可执行代码或测试直接体现。
- **[约定]**:配置、Schema、类型或仓库说明规定,但不一定被每条入口完整执行。
- **[推断]**:从调用结构推导的资源或性能判断,尚无仓库内基准数据。
- **[发现]**:实现间的不一致、诊断缺口或潜在效率风险。
本次覆盖四条后端执行路径:
| 路径 | 是否为当前 UI 主路径 | 数值行为 |
| --- | --- | --- |
| `POST /api/system-xml/simulate-stream` | 是 | XML 校验、拓扑编译、通用系统积分;NDJSON 进度与最终结果 |
| `POST /api/system-xml/simulate` | 否 | 同一通用求解函数;同步返回、无任务登记/流式进度 |
| `POST /api/reactflow/simulate-testmodel` | 否 | 固定 TestModel 专用闭包与产物生成,不按任意 ReactFlow 边拓扑求解 |
| `POST /api/reactflow/simulate-test-mql` | 否 | 当前只返回结构/采样摘要和全零状态数组,不执行 132 状态时域积分 |
## 3. 主求解调用链
```mermaid
flowchart TD
A["前端校验参数并生成 System XML"] --> B["POST simulate-stream"]
B --> C["安全解析 + XSD v3 + 语义校验"]
C --> D["XML 执行模型直接编译 SimulationNetwork"]
D --> E["准备检查与求解器构造"]
E --> F["一致初始代数闭合"]
F --> G["自适应 ODE 逐步积分"]
G --> H["每个 RHS:信号/热力/代数/stream/机械闭合"]
G --> I["事件定位、状态重置、求解器重启"]
G --> J["逐采样点完整后处理闭合"]
J --> K["完整结果作为末条 NDJSON 返回"]
```
### 3.1 前端形成输入
**[已实现]** 前端先检查模型、表达式和仿真设置,再由 `buildSystemXml()` 在浏览器内构造精简 v3 XML。参数表达式会先求值,并按选定显示单位转成 SI 基准数值;求解期间这些组件参数保持静态,不存在前端实时调参或联合仿真输入通道(`frontend/src/App.tsx` 中的参数解析与 `buildSystemXml()`;`app/simulation/core/base.py:65-95`)。
前端生成 `simulationId`,通过 `fetch` 发送 XML,请求头携带该 ID;响应类型要求为 `application/x-ndjson`(`frontend/src/App.tsx` 中的运行入口与 `streamSystemSimulation()`)。
### 3.2 XML 校验、解析和网络编译
主序列见 `app/main.py` 的 `run_system_xml_simulation()`:
1. `validate_system_xml_document()` 执行大小/安全解析、v3 XSD 和语义校验;
2. XML 解析为只含求解信息的规范化执行模型,不重建 ReactFlow 画布;
3. `compile_system_xml_network()` 创建 `SimulationNetwork`;
4. 构造 `GenericFluidSystem` 与 `SolveIVPConfig`;
5. 调用通用 `simulate()`;
6. 汇总验证、模型接口、数值结果和错误诊断。
XML 安全层限制 5 MiB、禁用 DTD/实体和网络访问(`app/system_xml.py` 的安全解析与 `validate_system_xml_document()`)。
XML 编译最终进入 `_compile_solver_network()` 的两阶段装配(`app/main.py`):
- v3 语义层先核对组件 `modelVersion`、完整参数和端点引用;编译层再消费零端口介质定义节点;
- 按气动连通区域解析介质引用;
- 实例化实际组件,并根据注册模型恢复端口类型、角色和变量合同;
- 最后建立网络连接。
**[已实现]** v3 不携带坐标、旋转、镜像或端口显示侧,布局不会进入求解。`AmesimForc` 的物理正反由模型参数 `direction=+1/-1` 决定;旋转/镜像图标不会改变力方程(`app/simulation/components/amesim/mechanical/translational.py`)。
### 3.3 求解前结构检查
创建通用系统前,`generic_simulation_preparation_issues()` 检查(`app/simulation/systems/generic.py:93-201`):
- 所有物理端口已经连接;
- 压力流量系统的未知量数与方程数相等;
- 网络至少存在一个动态储能组件;
- 每个相连物理岛具有动态储能锚点;
- 不允许多个理想储能元件无阻力直接耦合。
这些检查是当前可求解结构的边界,不表示任意声明了 `PortDefinition` 的网络都能被通用求解器处理。
## 4. 数学结构与状态组织
本节的核心区别只有一个:**有些量要“记住过去并随时间积累”,有些量只要在当前瞬间满足约束。**
在高压气缸向低压储罐放气的案例里,气室内的气体质量和能量属于前者;端口压力和通过节流孔的瞬时流量属于后者。求解器先保存前者,再用它们求出后者。下面的“动态状态”“代数未知量”只是这两组量的技术名称。
### 4.1 动态状态
**[已实现]** 动态组件把自身状态拼成全局 ODE 向量:
- 固定/变容气室及部分动态管路通常使用质量与内能 `[m, U]`;导数由质量流、焓流和换热构成。PNCH023 示例见 `app/simulation/components/amesim/storage/chambers.py:111-218`。
- PNL0003 具有两个容积单元,使用四维 `[m1, U1, m2, U2]`(`app/simulation/components/amesim/flow/pipes.py:1043-1078`)。
- MECMAS21 的机械状态是 `[v, x]`,导数是 `[a, v]`(`app/simulation/components/amesim/mechanical/translational.py:569-700`)。
`MechanicalStateReducer` 会按机械连接中的 `x/v` 等值关系将刚性相连的质量归组,每组只保留一套 `[v, x]` 状态,从而避免重复积分同一个运动自由度(`app/simulation/solvers/mechanical.py:225-427`)。
### 4.2 代数未知量与方程
网络从物理端口合同和组件残差构造代数系统(`app/simulation/systems/network.py:159-237`):
- 气动端口的主要代数变量是 `p` 和 `m_flow`;
- 机械端口包含 `x`、`v`、`f`,其中已归属动态状态的坐标会被状态/等值关系约束;
- 连接对 `effort/equal` 生成两端差值,对 `flow/sumToZero` 生成两端和值;
- 组件再提供状态约束、构成关系、质量/力守恒等残差。
stream 焓 `h_outflow` 不直接强制相等;标量信号和气动外部容积也不进入同一通用连接残差,而由专用 resolver 处理。
### 4.3 压力流量因果化与非线性回退
`PressureFlowSolver` 初始化时预编译方程模板、effort 等值组、显式流/力赋值计划、未知量布局,以及方程声明到未知量的静态关联图。热流闭合先按物理连通关系划定安全范围,再在可信内置模型中按这张关联图拆成更细的方程块。每次 `solve()`:
1. 从上一解与当前动态状态播种端口未知量;
2. 传播状态拥有的压力、位移和速度;
3. 执行可显式求值的构成关系与守恒关系;
4. 处理单边接触约束;
5. 若缩放后残差不超过 `1e-7`,直接返回且 `evaluations=0`;
6. 否则找出残差未闭合或种子不可行的方程块,把这些互相独立的块合并成一次 sparse `least_squares`;
7. 方程块失败时恢复原始 `x0`,执行全局 sparse `least_squares`;全局 sparse 仍失败时再次恢复同一个 `x0`,最后执行 dense 兼容回退。
非线性回退当前参数为 `x_scale="jac"`、`ftol=xtol=gtol=1e-10`、`max_nfev=500`。可信结构会传入由声明图编译的 `jac_sparsity`;活动单边接触会改变坐标和活动集,自定义组件也可能没有完整依赖声明,因此这些情况继续使用 dense 路径。稀疏/分块是加速层,不会把失败候选带入兼容回退。诊断中的 `evaluations` 是 SciPy 报告的优化器 `nfev`,`residualEvaluations` 才包含数值 Jacobian 在内的真实残差回调数,并通过 `jacobianMode`、`denseFallbackUsed` 和块回退字段说明实际路径。
**[当前边界]** 第一次压力流量求解仍要保证全网所有方程成立,但“全网语义”不再等于“总是把全部未知量交给一个 dense 优化器”:可信声明图可只求本轮未闭合方程块的 union。后续 stream 固定点则进一步只重算敏感方程块。保守条件会恢复全网 sparse→dense 行为;没有可信依赖声明或活动接触时,仍可能直接恢复 dense,成本会随未知量数快速上升。
在用户提供的 `test_mql-full-branches-01-04.xml`(SHA-256 `2fb95e65f5de0c85a6a17802aef74ea004087323fd00fd8d01acf0184ff71d48`)中,472×472 代数关联矩阵只有 919 个结构非零项,密度约 0.413%,可拆为 58 个独立方程块。一个受控扰动实验在相同 `max_nfev=20` 下,dense 与 sparse 分别触发 3796 和 164 次真实残差回调,墙钟约为 7.357 s 和 0.634 s,即同一预算下约 11.6 倍的单次回退成本改善;这项实验只衡量回退开销,不代表两条路径在 20 次评估内已经收敛。
## 5. 每次导数计算的代数闭合
这一步就是第 0 节所说的“当前瞬间对账”。以高压气缸—节流孔—管路—低压储罐为例,求解器需要同时保证:
- 接头压力相容;
- 从一个组件流出的质量等于进入另一个组件的质量;
- 节流孔和管路自己的压差—流量关系成立;
- 气体携带的能量按实际流向交给下游;
- 若还有机械活塞或控制信号,它们在同一时刻也要一致。
代码先建立全网初始压力解,再传播 stream 焓;只有组件方程明确依赖 stream 的范围,才让 stream 焓和其方程—未知量关联块继续迭代到同一个固定点。这样既让当前 RHS 不依赖上一次调用留下的焓/流量历史、保持有限差分 Jacobian 可重复,又避免无关方程重复对账。物理岛仍用于确认范围不会跨越未声明边界,但不再等同于实际求解块。
`GenericFluidSystem._close_current_state()` 的实际顺序见 `app/simulation/systems/generic.py`:
| 顺序 | 操作 | 目的 |
| ---: | --- | --- |
| 1 | `SignalResolver.solve(time)` | 用构造时预绑定的信号源和连接更新时间信号 |
| 2 | 传播机械 `x/v` 等值关系 | 把当前机械状态同步到刚性连接端口 |
| 3 | `PneumaticVolumeResolver.solve()` | 用预绑定的气动端口、输出组件和连接传播外部 `volume/volume_flow` |
| 4 | 刷新动态组件热力端口 | 由当前 `m/U/V` 和最新体积恢复压力、温度、焓 |
| 5 | 第一次全网 `PressureFlowSolver.solve()` | 建立本轮热流固定点的初始压力和流量 |
| 6 | `StreamResolver.solve()`,必要时最多 25 轮重算敏感方程块 | 用预绑定的组件、端口和连接传播焓;让焓、温度引用和敏感构成流量同时收敛 |
| 7 | 更新机械约束加速度 | 为机械状态导数准备 `a` |
随后 `rhs()` 才收集各动态组件的导数。
因此每次闭合固定有 **1 次全网语义**的压力流量求解;没有敏感方程块时,stream 传播一次后结束,不再有第二次压力求解。若有 `B` 个敏感方程块,每一轮外层热流固定点会把相应块作为一个 union 快路径求解,最多 25 轮;保守回退时恢复原全网求解器。stream 自身仍有相对容差 `1e-9` 和最多 100 次内部迭代,外层热流固定点最多 25 轮,两层上限不能混为一个数。上述大型 XML 的敏感 union 是 9 个方程块、合计 192 个未知量,而不是其单一巨大物理连通岛中的全部 472 个未知量。
signal、stream 和外部 volume resolver 已把静态组件列表、端口引用和连接绑定预编译到系统构造阶段;运行时仍执行真实的信号赋值、焓迭代、volume 传播和动态热力刷新。闭合计划只在组件以布尔能力声明明确说明压力流量方程是否依赖 stream、且方程变量声明可信时裁剪;未分类自定义 stream 组件、非法声明、跨组件残差或局部非方阵都会保守回退全网。这个边界避免把“少扫描”误做成“少算物理关系”。
## 6. 初始化、积分参数与推进方式
可以把初始化理解为“先摆好第 0 帧”,把积分理解为“根据每一帧的变化速度继续制作后续帧”。初始化并不会自动修改所有不合理的初始存量;它主要是在给定初始质量、能量、位置和速度后,求出与之匹配的端口压力、流量和力。
### 6.1 初始状态
`consistent_initial_state_vector()` 取得组件构造时形成的初值,应用该状态并执行一次完整代数闭合,然后原样返回状态向量(`app/simulation/systems/generic.py:292-296`)。
**[重要边界]** 这不是通用 DAE 一致初值求解:它不会联合调整微分状态及导数,只求“给定当前动态状态时”的端口代数变量。固定 TestModel 的专用压力投影/初始化逻辑不能外推为通用 XML 求解器能力。
### 6.2 参数来源及真实含义
初学者最需要先分清 XML v3 的 `sampleStep` 和 `maxStep`(进入 Python 后分别是 `sample_step` 和 `max_step`):
- `sampleStep` 只控制**结果多久记一条**;
- `max_step` 才限制**内部一次最多走多远**;
- 自适应求解器可以走得比 `max_step` 更短,也可能先试一步、发现误差过大后退回来重算。
所以,把 `sampleStep` 从 0.1 改成 0.01 通常会让输出曲线更密、结果占用更多内存,但它不等价于命令求解器固定每 0.01 秒算一步。
| 参数 | 当前来源/默认值 | 实际用途 |
| --- | --- | --- |
| `t_start / t_stop` | 前端与 Pydantic 默认 `0 / 2 s` | 积分区间 |
| `sampleStep`(内部 `sample_step`) | 默认 `0.1 s` | 仅生成输出 `t_eval` 采样网格;工程 JSON 仍暂名 `simulation.step` |
| `maxStep`(内部 `max_step`) | 默认 `0.005 s` | 自适应求解器内部已接受步的上限 |
| `method` | 默认 `BDF` | BDF、Radau、LSODA、RK45、RK23、DOP853 |
| `rtol` | 通用 XML 路径硬编码 `1e-6` | 外层 ODE 相对误差;用户不可配置 |
| `atol` | `SolveIVPConfig` 标量默认 `1e-8`;机械状态收紧到 `min(default, 1e-12)` | 热力状态使用默认值,机械速度/位置使用更紧的分量容差;用户不可配置 |
| `first_step` | 默认 `None` | 交给 SciPy;用户不可配置 |
| 代数残差容差 | `1e-7` | 压力流量快速路径/接受标准 |
| 代数最大评估 | `500` | 单次 `least_squares` 上限 |
| stream 容差/迭代 | `1e-9 / 100` | 焓传播固定点 |
| 采样数上限 | `10001` | 限制输出样本,不限制 RHS 次数或事件数 |
前端/Pydantic 默认值见 `frontend/src/App.tsx` 的仿真默认配置和 `app/main.py:131-137`;通用路径构造 `SolveIVPConfig` 见 `app/main.py:684-701`;采样网格见 `app/simulation/systems/generic.py:204-225`。
**[已实现]** `sampleStep` 生成的采样网格会确保包含 `t_stop`,并在超过 10001 点时拒绝;它不会把 BDF 变成固定步算法。实际 RHS 次数由自适应误差控制、Jacobian 估计、拒绝步、事件重启和 `max_step` 共同决定。
### 6.3 逐步推进分派
`integrate_ode()` 位于 `app/simulation/solvers/solver.py:917-1009`:
- 有取消检查、信号断点或机械状态事件时,使用 SciPy 的低层 BDF/DOP853/LSODA/RK23/RK45/Radau 类逐步推进;
- 无上述需求时,可一次调用常规 `solve_ivp`;
- 当前流式接口总会传 `cancel_check`,所以总走逐步路径;
- 同步 XML 接口只有在没有断点/状态事件时才可能走一次性 `solve_ivp`。
逐步路径在每个已接受步上:
1. 检查取消;
2. 推进一步;
3. 仅当本步跨越下一个 `t_eval` 样本或需要机械状态事件检测时构造 dense output,并插入样本;
4. 检测状态事件;
5. 报告进度;
6. 必要时重建求解器。
**[已优化]** 没有跨采样点、也没有状态事件时,不再为每个已接受步无条件构造插值对象。该优化对稀疏采样、无机械事件的模型有收益;用户大型分支 XML 含机械状态事件,所以仍必须在每步保留用于事件定位的 dense output,本轮实测中这项优化对该 XML 没有收益。
Peng–Robinson 试探状态越界会抛 `RecoverableTrialStateError`;代码回到最后已接受状态,将 `max_step` 减半,最多重试 16 次(`app/simulation/solvers/solver.py:528-914`)。
高刚度 XML 的历史失败不是“所有亚帕压力都不物理”,而是优化器原 1 Pa 下界过严:RK45 的内部自适应试探会短暂给出仍严格大于 0 Pa、但小于 1 Pa 的压力种子。当前 `PRESSURE_LOWER_BOUND_PA=0.0`,合法正压试探交回 RK45 自身判断,零压/负压仍由构成方程拒绝。该模型 10 s RK45 已通过且可恢复重试数为 0,因此这里没有用异常重启掩盖真实模型错误。
### 6.4 大型分支 XML 的 `0.69 s` 慢区
用户 XML 包含 98 个组件、472 个代数未知量和 74 个 ODE 状态。旧代码不是在 `0.69 s` 死锁,而是 BDF 到达这一刚性变化区后开始大量缩步、重建有限差分 Jacobian 并做 LU 分解。定位发现,气动外部 volume resolver 把机械位移写入气室容积,形成“机械位置 → 气室热力/压力 → 气动力”的跨域闭环;旧 ODE 稀疏依赖图只沿普通物理端口追踪,漏掉了这条外部 volume 边的 12 个实测显著导数项。
修正采用保守的双向跨域依赖:接收外部容积的气动储能状态依赖相关机械状态,机械力平衡也依赖被耦合的气动状态。结构非零数因此由 1092 增至 1284,有限差分颜色组由 27 增至 31。颜色组稍多不是退化:旧图更小是因为漏项,给 BDF 的 Jacobian 数值不完整,导致后续重复试步的总成本更高。
功能收口过程中的较早阶段测量为 92.187 s;最终稳定代码连续三次完整运行到 `0.81 s`,分别用时 79.049 s、74.658 s 和 85.103 s。三次积分统计均为 `nfev=3393`、`njev=226`、`nlu=667`、接受步 1009、求解器启动 3 次;压力流量求解 28008 次,全部由 seeded 快路径满足残差合同,没有触发方程块或 dense 非线性回退。
三次完整响应按规范 JSON 序列化后的 SHA-256 均为 `454cd11aece1c4a2296a88e2c1dd592eeace28565e342235fb7a7df34de5b18f`。为避免诊断字段增删造成“物理结果没变但响应哈希变化”,另定义外部标签 `physical-solution-v1`:待哈希对象只投影 `{status, simulatedUntil, requestedStopTime, series, final}`,标签本身不放入对象;用 `json.dumps(sort_keys=True,separators=(",",":"),ensure_ascii=False)` 规范化后 SHA-256 为 `04982f427867801c582fea81c6e2da0b726bd8a61d7894b311e4a807b19e89a7`。旧 `09b5c7…` 是聚合诊断和最终 union 路径收口前的 full-response 哈希,受响应结构影响,不能与当前哈希直接比较。
本轮还区分验证了两种容易混淆的“容差”。机械状态 `atol` 从 `1e-12` 放宽到 `1e-10` 的单次 A/B 约快 16%,但这会改变机械状态与事件的误差合同,当前证据不足,未采用。另一项是外层 thermofluid **流量固定点**相对容差从 `1e-12` 放宽到 `1e-9`;它反而增加 BDF 内部步数并改变积分轨迹,也未采用。这里的正式修复是补全依赖图,不是通过放宽精度或闭合容差掩盖问题。
仓库有固定 RK4 回退,但通用压力流量求解器本身依赖 SciPy;因此它不能被视为一般流体网络在无 SciPy 环境下的完整替代方案。
## 7. 事件、取消与停止
### 7.1 信号离散时刻
STEP0、UD00 等信号源提供离散事件时刻。积分器先推进到事件左侧的相邻浮点时刻,再在精确事件时间更新信号并重建求解器,连续动态状态保持不变(`app/simulation/solvers/signal.py:70-99`、`app/simulation/components/amesim/signals/sources.py:128-141`)。
### 7.2 机械端挡
机械状态事件在已接受步的 dense state 上检查端挡穿越,最多 60 次二分定位;命中后按塑性/恢复系数重置位置和速度并重启积分器。同一时刻最多允许 64 次链式状态重置(`app/simulation/solvers/mechanical.py:430-627`、`app/simulation/solvers/solver.py:92-166`)。
**[约定]** 这是为信号断点与机械端挡编写的专用事件框架,不是可由任意组件声明残差事件的通用高指数 DAE 框架。
### 7.3 协作取消
流式任务的取消端点只设置共享 `threading.Event`。积分器在已接受步、重试边界等检查点协作停止;若正在执行一次耗时的热物性、stream 或 `least_squares` 调用,取消不能立即抢占(`app/main.py:535-547`、`app/simulation/solvers/solver.py:917-1009`)。
停止后若至少已有两个有效采样点,通用系统可整理并返回部分结果;相关行为由 `tests/test_generic_system_xml_simulation.py:433-533` 覆盖。
## 8. 后处理与现有诊断
### 8.1 结果后处理
积分完成后,`GenericFluidSystem.simulate()` 重置机械约束模式,并对每个 `solution.t`:
1. 重新应用状态;
2. 再执行一次完整 `_close_current_state()`;
3. 提取所有组件级及端口级公开结果变量。
见 `app/simulation/systems/generic.py:397-474`。
**[推断]** 采样密集或结果变量多时,这会形成明显的第二计算阶段;此时内存中还保留积分状态矩阵,CPU 与内存峰值可能重叠。
**[当前边界]** 本轮优化没有复用积分期间的闭合快照,也没有跳过后处理对账。每个输出点仍执行完整 `_close_current_state()`;变化仅在于该闭合内部使用同一套预编译绑定和敏感方程块执行计划。
### 8.2 当前返回的诊断
**[已实现]** 结果包含:
- 状态数、采样数;
- 压力流量 `solveCount`、`closurePassCount`、`secondaryPhysicalIslandCount`、真实方程 `secondaryBlockCount`、`secondaryUnknownCount`、保守回退原因、最大残差、最大单次评估数和最后求解作用域;
- stream 最大迭代数;
- 停止状态及部分错误上下文。
**[已实现]** 积分诊断已经包含分段及汇总的 `nfev/njev/nlu`、已接受步、求解器启动、状态迁移和可恢复重试数。设置 `SIMULATIONAPP_PROFILE=standard|audit` 后,响应还会加入分阶段墙钟时间;audit 进一步记录物性调用、精确重复、缓存命中和逆解迭代。
**[已修复]** 闭合现在聚合所有实际压力求解的最大残差和最大单次评估数,`last` 与 `lastScope` 指向最后一个真实求解作用域,不再被一个未执行或较早 pass 的局部变量覆盖。`solveCount` 统计求解器调用次数,`closurePassCount` 单列发生过压力求解的固定点 pass;`secondaryPhysicalIslandCount` 只表示安全分类得到的物理范围,`secondaryBlockCount` 明确表示方程关联块数,不能再把两者混称为“块”。代数诊断还返回真实 `residualEvaluations`、`jacobianMode`、dense/方程块回退状态。子作用域失败时,API 返回 `scopeKind` 和 `scopeComponents`。仍缺少峰值 RSS、任务队列深度等服务级指标;结果字节和编码时间目前由离线基准工具测量,不进入常规 API 响应。
## 9. 求解时前后端交流
主路径可以压缩为下图:
```text
浏览器 ──一次 POST:完整 System XML──────────────> 后端
浏览器 <──同一长连接:心跳、进度、心跳、进度──── 后端求解线程
浏览器 <──最后一个消息:完整结果 JSON─────────── 后端
浏览器 ──需要时另发取消 POST───────────────────> 后端
```
这里的“流式”主要是**进度消息流式**,不是每算出一段曲线就立刻传一段曲线。最终数值序列仍在末尾一次性返回。
### 9.1 当前协议
| 阶段 | 通信 | 当前行为 |
| --- | --- | --- |
| 提交 | 一个 HTTP POST | 请求体为完整 XML;`X-Simulation-Id` 标识任务 |
| 运行 | 同一响应上的 NDJSON | 进度事件、错误事件、5 秒心跳 |
| 完成 | 同一 NDJSON 流最后一行 | 一次性携带完整 `SimulationResult` |
| 用户取消 | 另一个短 POST | 设置协作取消事件;原流继续等待终态 |
| 流断开/停滞恢复 | GET 状态 | 每 500 ms 轮询,最多 30 秒 |
后端路由见 `app/main.py:589-634, 773-906`,前端解析、取消和恢复见 `frontend/src/App.tsx` 中的 `streamSystemSimulation()` 及相邻任务控制函数。
**[已实现]** 后端每 5 秒无队列事件时直接发送 heartbeat。通用系统按进度至少变化 0.25% 才发送积分进度,通常至多约 400 条积分进度事件(`app/simulation/systems/generic.py:318-343`)。
前端规则:
- 30 秒没有收到任何字节:连接超时;
- 60 秒只收到心跳而没有真实积分进度:判定 stalled 并请求取消;
- 正常运行不是轮询,轮询仅用于异常恢复。
**[发现]** 合法但单个已接受步/闭合超过 60 秒时,前端可能误判停滞。后端结果事件的 `phase` 使用 `completed/stopped/stalled/failed`,前端事件类型却声明 `"complete"`;运行时当前没有按该字段做严格校验,所以契约漂移尚未直接报错(`app/main.py:825-840`、`frontend/src/App.tsx` 的流式事件类型)。
### 9.2 开发和部署连接数
**[已实现]** 开发态 Vite 将 `/api` 代理到 `127.0.0.1:8000`(`frontend/vite.config.ts:4-10`),所以一个流式仿真在开发态占用浏览器→Vite、Vite→FastAPI 两段长连接;若生产部署由 FastAPI/反向代理直接提供 API,则具体连接层数取决于部署。
仓库没有 WebSocket 路由、`EventSource` 或 `text/event-stream`;当前 NDJSON 只是普通 HTTP 分块响应。
## 10. CPU、线程、内存、网络和磁盘占用
先区分两个问题:
- **一个算例跑得快不快**:主要看每次变化率计算做了多少轮闭合、非线性试算和热物性计算;
- **多人同时运行稳不稳**:主要看并发任务是否有上限、是否能使用多个进程、每个结果在内存中保留多少份。
“每个任务开一个线程”不等于“每个任务独占一个 CPU 核”。Python 组件逻辑、SciPy 数值核和底层 BLAS 的实际并行程度取决于运行环境;仓库没有 CPU/RAM 实测数据,所以本节只给代码可证明的结构和数量级。
### 10.1 进程与线程
- `bat/start-all.bat` 与 `bat/start-all.sh` 分别启动 Vite 与 FastAPI。
- `bat/start-backend.bat` 与 `bat/start-backend.sh` 的 Uvicorn 命令没有 `--workers`,当前脚本即单进程单 worker。
- 每个流式仿真创建一个 daemon `threading.Thread` 和一个无界 `queue.Queue`;没有信号量、线程池或排队上限(`app/main.py:773-880`)。
- 全局任务字典只在读写元数据时持锁,不限制同时启动的求解数量。
- `POST /api/system-xml/simulate` 是 `async def`,但直接执行同步 CPU 求解;若调用该端点,会占用当前 Uvicorn 事件循环。
**[推断]** 单个求解主要是串行 Python 全网扫描加 SciPy 数值核,常会持续消耗一个核心;多任务线程不保证线性利用多核,还可能出现 GIL 竞争、SciPy/BLAS 原生线程过度订阅和内存峰值相叠。仓库没有固定 BLAS 线程数,具体 CPU 占用必须在目标部署环境实测。
### 10.2 内存数量级
不计 Python 对象常数项,主峰值可写为:
```text
O(组件 + 连接 + 代数结构)
+ O(采样数 × 动态状态数)
+ O(采样数 × 公开结果变量数)
```
当前采样上限是 10001。放大因素包括:
- 积分状态矩阵与后处理 `series` 在后处理阶段同时存在;
- 最终完整结果保存在全局任务记录中,又被编码为一个大型 NDJSON 行;
- 前端收到结果后执行 `structuredClone`,再 `JSON.stringify` 写入 `sessionStorage`(`frontend/src/App.tsx` 的结果快照与恢复逻辑);
- 图表会把数值数组映射为对象点数组,多个曲线窗口会产生更多前端副本;
- CSV 导出把完整结果再次上传,后端在 `StringIO` 中一次性构造完整 CSV(`frontend/src/SimulationResultsView.tsx:1041`、`app/main.py:299-377`)。
`SIMULATION_TASK_RETENTION_SECONDS=600`,但过期任务只在注册下一个任务时清理;没有新任务时,最后一批终态结果可能一直保留到进程退出(`app/main.py:491-520`)。
### 10.3 队列、网络和磁盘
- 任务队列是无界的,但进度被 0.25% 节流;正常单任务队列通常不大,客户端变慢或终态序列化时仍没有硬上限。
- 最终数值序列不分块,网络、后端 JSON 编码、前端字符串缓冲与 `JSON.parse` 会在完成时形成瞬时峰值。
- 通用 XML 求解本身不写仿真产物,结果主要驻留内存。
- 固定 TestModel 与 public Test MQL runner 会在 `app/data/simulation-runs` 下写时间戳产物;这不是主流式路径的磁盘行为。
## 11. 其他求解入口不能与主路径混同
### 11.1 固定 TestModel
`POST /api/reactflow/simulate-testmodel` 从所选类型/参数中提取固定数量的气瓶、贮箱、管/孔板来构造专用 `TestModelClosure`,不消费用户的任意节点边拓扑;它复用 `integrate_ode`,并写 CSV、SVG 和报告产物(`app/main.py:1363-1447`、`app/simulation/examples/testmodel/run.py`)。
它是回归/演示算例,不是通用 ReactFlow 网络求解器。
### 11.2 Test MQL
`POST /api/reactflow/simulate-test-mql` 当前忽略任意拓扑和主要积分配置;`TestMqlSystem.simulate()` 构造 132 状态的全零 `y`,只返回结构数量随时间的摘要(`app/main.py:1450-1481`、`app/simulation/examples/test_mql/system.py:6302-6318`)。
完整的 112 个气动状态与 20 个机械状态闭包存在于独立诊断/comparison 代码,但没有接入这个公开 API;仓库仍将其描述为校准阶段,不能宣称与 AMESim 全时域等价。
## 12. 当前明确的效率热点
| 热点 | 代码证据 | 影响范围 | 判断 |
| --- | --- | --- | --- |
| 每次闭合的压力流量重算 | `generic.py` 的预编译热流闭合计划 | 每个 RHS、初始化、每个结果采样点 | [已优化] 固定 1 次全网语义初解;后续只重算 stream-sensitive 方程关联块,不再把整个敏感物理岛重复求解 |
| stream 每轮复制/比较焓并刷新 | `stream.py` | 每个闭合,最多 100 轮 | [静态预编译已完成] 组件、端口和连接已预绑定;每轮必要的数值复制、比较和刷新仍保留 |
| 非线性回退使用有限差分 least-squares | `algebraic.py` | 快速路径失效时 | [首轮已优化] 可信声明图先做未闭合方程块 union sparse;失败恢复 `x0` 后做 global sparse→dense;接触/不可信结构保留 dense |
| 外层 ODE Jacobian 稀疏依赖图 | `generic.py`、`solver.py` | BDF/Radau 的每步/Newton | [已修复] 已传 `jac_sparsity`;补上外部 volume 的跨域双向依赖,用户 XML 为 1284 非零/31 色 |
| 热物性重复反算 | `mediums.py` 及各动态组件 refresh | 每个 RHS/闭合 pass | [已优化] 2026-08-16 已加入仿真隔离的四项氦气精确 LRU;代表算例 2,869/435 次命中/未命中,PR 三次根调用由 1,712 降至 689 |
| 每采样点完整后处理闭合 | `generic.py:397-474` | 输出点 × 全网 | [已实现] |
| dense output 插值对象 | `solver.py` | 流式逐步路径 | [已优化] 仅跨样本或需要状态事件时构造;含状态事件的用户 XML 每步仍需要,因此无本案收益 |
| 无界求解线程与任务结果驻留 | `main.py:491-520, 773-880` | 并发任务 | [已实现] 稳定性风险,不等于单算例变慢 |
| 完整结果单行 JSON 与前端多副本 | `main.py:825-840`、`App.tsx:8872-8958` | 大输出 | [已实现] 内存/网络热点 |
下面是 2026-08-16 **物理岛版首轮实现的历史 `off` 基线**。这些数据仍可说明旧执行计划相对更早“每轮全网”的收益,但该实现已由方程关联块版取代,不能把表中的“块”解释为当前 `secondaryBlockCount`:
| 算例 | 墙钟改善 |
| --- | ---: |
| `air_chain` | 7.9% |
| `air_branched` | 6.5% |
| `helium_step` | 0.6% |
| `mechanical_contact` | 21.4% |
| high-stiffness short | 14.6% |
完整 high-stiffness 10 s 的历史基线约 28.126 s,本轮全部改动后的中位数为
13.694 s;这个跨版本差额不能归到某一项优化。当前代码上单独强制恢复全网后续
闭合的受控对照为 optimized 14.676 s、forced-global 16.929 s,完整 `series`
一致。`helium_step` 只有一个仍需重算的敏感岛,改善仅 0.6%;这说明收益取决于
可跳过多少无关网络,不能宣称单一氦岛也有两位数提升。后处理的逐采样点完整闭合
仍然保留。
当前方程关联块版另用用户大型分支 XML 做了受控短区间 A/B:输入 SHA-256 为 `2fb95e65f5de0c85a6a17802aef74ea004087323fd00fd8d01acf0184ff71d48`,含 98 个组件、472 个代数未知量和 74 个 ODE 状态。stream 后续求解的 9 个方程块合计 192 个未知量;`0~0.01 s` optimized 与 forced-global 分别为 16.200 s 和 18.584 s,物理解与 `series` 逐值一致。最终稳定代码完整 `0~0.81 s` 连续三次为 79.049 s、74.658 s 和 85.103 s,积分统计均为 `nfev/njev/nlu=3393/226/667`。短区间 A/B 只归因于后续 stream 闭合作用域,完整运行同时包含 Jacobian 修正和最终执行路径收口,二者不能混算成一个百分比。
## 13. 优化建议排序
以下按**预期综合收益**排序;同档位优先低风险、低难度项。排序同时参考代码结构和 2026-08-15 的阶段/物性实测,但尚未覆盖大规模拓扑与多任务吞吐。“单算例”指一个模型的墙钟时间,“吞吐”指多任务服务能力。
### 13.1 先看人话版
在改算法前,应先给各阶段计时和计数;这本身不直接加速,但能防止优化错地方。之后可按下面顺序理解主要方案:
| 顺序 | 人话方案 | 为什么可能更快 | 主要风险 |
| ---: | --- | --- | --- |
| 1 | 少做重复“瞬时对账” | [方程块首轮已完成] 初解保持全网语义,后续只重算 stream-sensitive 的关联方程块 | 自定义/异常结构必须继续保守回退,不能漏掉真实耦合 |
| 2 | 先整理方程,再求解 | [稀疏首轮已完成] 可信声明图将未闭合块合并求解;大模型回退时减少数值 Jacobian 试算 | 接触活动集和不可信自定义声明必须走兼容回退 |
| 3 | 给不同状态使用合适的“尺子” | 质量、内能、位置、速度量级差异很大;合理缩放可减少无效内部步 | 容差改变会影响精度和事件时刻 |
| 4 | 相同输入不要重复查热物性 | 同一轮闭合中常以相同状态反算压力、温度等 | 缓存失效不严谨会产生错误结果 |
| 5 | 只计算、保存和传输需要的曲线 | 采样多、变量多时,可同时减少后处理、内存和网络开销 | 会改变默认结果合同,需要保留完整模式 |
| 6 | 给并发任务设固定“办理窗口” | 有界进程 worker 可防止无限建线程,并更好利用多核 | 对单个算例未必更快,跨进程取消和结果传递更复杂 |
下面的完整表把这些方向进一步拆成 12 项,并明确收益、风险和实施难度。
| 排名 | 建议 | 主要收益对象 | 预期收益 | 风险 | 实施难度 |
| ---: | --- | --- | --- | --- | --- |
| 1 | [方程块首轮已完成] 将 `_close_current_state` 编译为按能力/依赖启用的执行计划:signal/stream/volume 预绑定;首次压力求解保留全网语义,仅在 stream 确实使构成关系变脏时重算相关方程关联块,并保留全网回退和耦合迭代上限。 | 单算例 | 历史物理岛版实测 0.6%~21.4%;大型 XML 方程块版短区间 16.200 s 对 18.584 s | 中:错误裁剪会破坏耦合一致性,需持续回归自定义模型 | 已完成首轮 |
| 2 | [稀疏首轮已完成] 可信方程声明图已用于 union block sparse 和 global sparse→dense 回退;后续继续评估 equality group 真正消元、解析 Jacobian,以及活动接触的安全分块。 | 单算例、大网络 | 扰动实验同预算残差回调 3796→164,约 11.6 倍;实际收益取决于是否触发非线性回退 | 高:接触活动集与错误声明会影响收敛 | 首轮已完成,继续深化 |
| 3 | [依赖图已修复] BDF/Radau 已使用状态 `jac_sparsity`,外部 volume 跨域漏边已补;后续再评估状态缩放和可配置分量级 `rtol/atol`,不要简单全局放宽容差。 | 单算例、刚性网络 | 大型 XML 已从 `0.69 s` 慢区定位并完整跑通;机械 `atol` A/B 虽约快 16%但改变精度合同,flow 固定点放宽则增加步数,均未采用 | 中高:会改变误差轨迹/事件时刻 | 稀疏图首轮完成,缩放待评估 |
| 4 | [已完成] 氦气高成本物性已按单次仿真做精确、有界缓存;dynamic components 及 signal/stream/volume 的静态组件、端口和连接也已预绑定。 | 单算例 | 已取得可见收益,且减少固定拓扑的重复查找 | 中:缓存失效错误会污染物理结果 | 已完成 |
| 5 | 改造结果选择和后处理:允许选择变量、采样/降采样;避免对不需要的变量和时间点执行完整闭合,必要时复用积分期间已接受的闭合快照。 | 单算例、内存 | 长仿真/多变量时高 | 中:结果合同与复用精度 | 中高 |
| 6 | 引入有界作业队列和固定大小的进程 worker;统一让同步端点也进入执行器,并设置最大并发、排队长度和结果尺寸。 | 吞吐、稳定性 | 高;单任务速度通常不变 | 中高:跨进程取消和序列化 | 高 |
| 7 | 进度与结果解耦:NDJSON 只发送进度和 `resultId`,结果按变量/时间块压缩下载或外部存储;前端改用 TypedArray/IndexedDB,图表先降采样。 | 内存、网络、UI | 大结果时高 | 中:需要版本化协议 | 中高 |
| 8 | 主动定时清理任务表,限制任务数/结果字节;成功交付后只保留摘要或引用。将无界进度队列改为“最新进度槽 + 不可丢终态槽”。 | 稳定性 | 中到高 | 低中 | 低中 |
| 9 | [首轮已完成] 只在当前步跨越下一采样点或需要状态事件检测时构造 dense output;后续记录并优化事件重启,大量周期 UD00 事件再评估惰性调度。 | 单算例、事件密集模型 | 无事件且采样稀疏时可减少插值对象;本次含状态事件 XML 无收益 | 低到中 | 首轮已完成 |
| 10 | CSV 在浏览器直接生成或按 `resultId` 服务端流式生成,避免全量 series 重新上传与 `StringIO` 全量复制。 | 内存、网络 | 中 | 低 | 低中 |
| 11 | 用共享 Schema/OpenAPI 生成前后端事件类型,修正 `complete/completed`;停滞依据服务端活动计数/已接受步时间戳并允许按模型调节。 | 可靠性、减少误杀重算 | 中 | 低 | 低中 |
| 12 | 长期评估支持稀疏残差/Jacobian 的 DAE 求解器,将外层 ODE 与内层代数 least-squares 统一成状态—代数系统。 | 复杂大模型 | 潜在很高 | 很高:架构与验证成本大 | 很高 |
结果访问器和每轮剩余临时容器不属于第 4 项已经完成的 signal/stream/volume 静态预绑定;前者应结合第 5 项后处理改造单独基准,不把尚未实测的小项混入已完成收益。
### 13.2 推荐落地顺序
低侵入阶段/物性观测、积分计数和代表算例首轮基准已经落地,但不把“加指标”误列为直接加速。下一步建议:
1. 继续汇总压力流量快路径命中、非线性 `nfev`、真实 `residualEvaluations` 和残差装配时间;单次诊断已能区分 block sparse、global sparse 和 dense 回退;
2. 用同一套基准把方程声明图 A/B 扩展到更多自定义组件、活动接触和保守回退路径;大型 98 组件 XML 与首轮短算例已经覆盖可信内置路径;
3. 补 1/8/32 单元规模曲线、1/2/4 并发吞吐和峰值 RSS;
4. 记录状态/结果数组字节、任务队列深度;结果 JSON 字节可继续由基准工具测量;
5. 再决定 equality group 真正消元/解析 Jacobian、结果按需计算和进程 worker 的实施深度。
每项算法改动都应继续验证质量/能量守恒、正反流、stream 混合、机械端挡、信号断点、取消部分结果和 AMESim/TestModel 基线。相关测试证据包括 `tests/test_generic_system_xml_simulation.py:244-533`、`tests/test_core_solver.py:19-508`、`tests/test_amesim_mechanical_public_components.py`。
## 14. 已实现、约定与推断的边界汇总
### 已实现
- System XML 校验、拓扑编译和通用半显式 ODE/代数求解主链。
- 气动、机械和信号的专用闭合顺序。
- 压力流量显式因果化快路径,以及可信声明图上的 union block sparse、global sparse→dense `least_squares` 兼容回退;失败候选不会污染原始 `x0`。
- signal/stream/外部 volume 静态绑定,以及“全网语义初解 + stream-sensitive 方程块重算 + 保守全网回退”的闭合执行计划。
- 明确区分物理岛、方程块和方程块未知量的诊断,并记录实际残差回调、Jacobian 模式、最后作用域和子块失败作用域。
- BDF/Radau 状态 `jac_sparsity`,以及外部 volume 跨域双向依赖修正。
- dense output 按采样跨越/状态事件惰性构造;状态事件模型仍保持每步插值能力。
- 允许严格正亚帕试探压力的高刚度 RK45 路径;零压和负压仍不接受。
- 自适应积分、输出采样、信号断点、机械端挡、协作取消和部分结果。
- NDJSON 长响应、心跳、取消端点、异常恢复轮询和任务状态表。
- 单 Uvicorn worker、每任务 daemon 线程、完整终态结果驻留与浏览器多副本行为。
### 约定
- 内核定位为半显式 ODE/代数 MVP,而非任意 DAE。
- XML/组件运行参数使用 SI 基准值。
- 采样上限、超时、心跳和任务名义保留时长。
- 组件参数在单次运行中静态;时间变化通过信号源等模型表达。
### 已有初步实测、仍需扩大样本
- 压力流量闭合是当前代表气动短算例的首要热点;物性调用具有高精确重复率,仿真隔离的四项 PR 氦气缓存已取得可见端到端收益。
- 长氦气代表算例的积分阶段占约 90.5%,后处理约 5%。
- 物理岛版闭合执行计划的历史短算例配对收益为 0.6%~21.4%;完整 high-stiffness optimized/forced-global 历史对照为 14.676 s/16.929 s,数值序列一致。这些基线保留用于纵向比较,但已不是当前方程块实现。
- 当前大型分支 XML 的 stream 后续闭合为 9 个方程块/192 个未知量;`0~0.01 s` optimized/forced-global 为 16.200 s/18.584 s 且物理解/`series` 逐值一致。最终稳定代码完整 `0~0.81 s` 连续三次为 79.049 s/74.658 s/85.103 s,积分统计一致。
- 代数 sparse 扰动实验将真实残差回调由 3796 降至 164(约 11.6 倍耗时改善);它是回退微基准,不能外推为所有仿真的整体加速倍数。
- 上述结论仍需在更大拓扑、更多真实工程和固定硬件环境复测。
### 推断及必须继续实测
- 单个任务实际占用几个核心、SciPy/BLAS 原生线程数和多任务扩展曲线。
- 典型/最大工程的峰值 RSS、结果 JSON 大小、浏览器内存副本和 sessionStorage 成功率。
- 各优化的实际收益;表中排序应在观测数据出现后更新。
## 15. 关键文件与符号索引
| 主题 | 文件与位置 | 关键符号 |
| --- | --- | --- |
| API 主入口与任务流 | `app/main.py` | `run_system_xml_simulation()`、`simulation_event_stream()` |
| JSON/XML 网络编译 | `app/main.py` | `compile_reactflow_network()`、`compile_system_xml_network()`、`_compile_solver_network()` |
| XML v3 校验/解析 | `app/system_xml.py`、`schemas/system-simulation-v3.xsd` | `SystemXmlDocument`、`validate_system_xml_document()` |
| 通用系统准备与仿真 | `app/simulation/systems/generic.py:93-474` | `GenericFluidSystem`、`_close_current_state()` |
| 压力流量代数闭合 | `app/simulation/solvers/algebraic.py` | `PressureFlowSolver.solve()`、方程关联图、sparse→dense 回退 |
| stream 方程块闭合 | `app/simulation/solvers/algebraic_blocks.py` | `StreamPressureBlockSolver` |
| stream 焓 | `app/simulation/solvers/stream.py:29-119` | `StreamResolver.solve()` |
| 标量信号 | `app/simulation/solvers/signal.py:40-109` | `SignalResolver`、`signal_event_times()` |
| 气动外部容积 | `app/simulation/solvers/pneumatic_volume.py:21-93` | `PneumaticVolumeResolver` |
| 机械因果化与事件 | `app/simulation/solvers/mechanical.py:225-627` | `MechanicalStateReducer` |
| ODE 推进 | `app/simulation/solvers/solver.py:37-1009` | `SolveIVPConfig`、`integrate_ode()` |
| 启动暖机 | `app/simulation/warmup.py` | BDF、稀疏分组、`least_squares` sparse LSMR 路径 |
| 可选性能埋点 | `app/simulation/performance.py` | `profile_run()`、`profile_phase()`、`profile_property()` |
| 可重复性能基准 | `app/simulation/benchmark_performance.py` | `python -m app.simulation.benchmark_performance` |
| 前端流式协议 | `frontend/src/App.tsx` | `streamSystemSimulation()`、取消/轮询 |
| 启动方式 | `bat/start-backend.bat`、`bat/start-backend.sh` | Uvicorn 单 worker 命令 |
| 主路径回归测试 | `tests/test_generic_system_xml_simulation.py`、`tests/test_core_solver.py` | 通用仿真、事件、取消 |
@@ -0,0 +1,641 @@
# SystemSimulationApp 接口类型与表示方式总结(通俗版)
> 调研基线:2026-08-12(System XML v3 接口基线)。本文依据当前仓库的代码、Schema、说明文档和测试编写。
> 这里的“接口”主要指组件上的端口(port/connector),不是只指 HTTP API。文末也单独列出了相关 HTTP API。
## 0. 三分钟读懂
先把整个系统想成一张“可以计算的工程图”:
- **组件**像气瓶、管路、阀门、质量块等设备;
- **端口**像设备上的接头或插座;
- **连接**像气管、机械连接杆或控制线;
- **编译**像正式计算前的接线检查:插头是否匹配、有没有漏接、方向是否正确;
- **求解**才是真正计算每个时刻的压力、流量、位移、速度等数值。
当前项目实际只有三类端口:
| 看到的类型 | 可以把它理解成 | 主要传递什么 |
| --- | --- | --- |
| `physical / pneumatic` | 气路接头 | 压力、质量流量、气体携带的能量 |
| `physical / mechanical` | 机械连接点 | 位移、速度、力 |
| `signal / signal` | 控制线 | 一个有方向的数值,例如阀门开度或目标力 |
最容易混淆的四种文件/数据,可以这样记:
| 数据 | 通俗比喻 | 它回答的问题 |
| --- | --- | --- |
| 组件目录 JSON | 产品说明书 | 某种组件天生有哪些端口、每个端口有哪些变量? |
| 工程 JSON | 画布存档 | 这张图上放了哪些组件、摆在哪里、怎样连? |
| System XML v3 | 交给后端的精简求解清单 | 只带组件、模型版本、SI 参数、连接和仿真设置,不负责保存画布 |
| 编译结果 JSON | 接线检查报告 | 后端恢复完整模型后,最终认出了哪些端口、连接和方程结构? |
贯穿全文的两个例子:
```text
案例 A:气路
储气容器/气室 A ── 节流孔或管路 ── 气室 B
气动端口 气动端口
案例 B:控制
阶跃信号源 step_1.out ──控制线──> 阀 valve_1.res
│
控制气路通断/开度
也可以是:step_1.out ──控制线──> 力源 force_1.res ──机械端口── 质量块
```
这两个示意图不是凭空编造的:仓库里已有对应的回归案例。`tests/test_generic_system_xml_simulation.py:86-145` 搭建了 `cylinder(500 kPa) → orifice → pipe → tank(100 kPa)` 气路;`tests/test_amesim_pnvo001_signal_xml.py:37-84` 搭建了 `step_1.out → valve_1.res`,同时让气缸、阀和气罐通过物理端口相连。
先记住六点就能继续阅读:
1. **物理端口必须同类相连。** 气动只能接气动,机械只能接机械,不能把“气管”插到“机械接头”上。
2. **信号线有方向。** 必须连接一个注册为 `output` 的端口和一个注册为 `input` 的端口;XML v3 不再另写 `source/target` 角色。
3. **物理线没有 source/target 的物理含义。** 画布虽然要写 `source/target`,后端会把它当作无方向的两个端点。
4. **流变量统一以“进入当前组件”为正。** 因此同一条气路两端的质量流量数值互为相反数。
5. **工程 JSON 和 XML 不重复保存完整变量表。** 后端依靠组件的 `modelType/type` 去注册表找回完整定义。
6. **`side/rotation/mirrored` 都只负责画面。** 力源是否反向由显式参数 `direction=+1/-1` 决定;转动或镜像图标不再改变方程。
只想看懂工程图,可以读第 0、2、3、5、6 节;需要开发或排查兼容问题时,再读第 1、4、7~11 节。
## 1. 本文中的标签和“谁说了算”
为了避免把“已经能运行”和“文档希望如此”混为一谈,本文使用四种标签:
- **[已实现]**:当前代码或测试直接体现的行为。
- **[约定]**:Schema、类型声明或说明文档规定的合同,但不一定所有入口都完整实现。
- **[推断]**:根据多处代码可以合理得到的判断,仓库没有直接承诺或实测数据。
- **[发现]**:代码层之间不一致、容易误解或存在兼容风险的地方。
如果不同层的说法不一致,优先相信更靠上的事实来源:
| 优先级 | 事实来源 | 关键文件/符号 | 通俗解释 |
| --- | --- | --- | --- |
| 1 | 端口核心定义 | `app/simulation/core/ports.py:7-207`:`PortVariableDefinition`、`PortDefinition`、`PortState` | 定义“插头标准”和运行时数值 |
| 2 | 具体组件模型类 | `MODEL_TYPE`、`PORTS`、`DISPLAY` | 声明某个产品实际装了哪些插头 |
| 3 | 模型注册器 | `app/simulation/registry.py:41-170, 416-490, 825-980` | 启动时核对产品声明,并生成目录 |
| 4 | 网络连接层 | `app/simulation/systems/network.py:83-150`:`SimulationNetwork.connect()` | 真正接线时做最终兼容检查 |
| 5 | JSON/XML | `ReactFlowPortDefinition`、System XML v3 XSD | JSON 搬运画布和端口显示快照;XML 只搬运可执行模型,端口合同由注册表恢复 |
**[约定]** [组件模型建模规范 v1](../standard/component-model-authoring-spec-v1.md)说明:组件模型类及受控库清单是后端事实来源。XML 或前端不能凭空创造一个模型没有声明的端口。
## 2. 常见术语翻译表
第一次阅读时,可以先把英文术语替换成右侧的日常说法。
| 术语 | 通俗说法 | 在本项目中的具体意思 |
| --- | --- | --- |
| component | 设备/元件 | 气室、节流孔、阀、质量块、信号源等 |
| port / connector | 接头/插座 | 组件可以与外界连接的位置 |
| interface contract | 接口说明书 | 端口名称、类型、变量、单位和连接规则的完整定义 |
| `kind` | 大类 | `physical` 物理连接,或 `signal` 控制信号 |
| `domain` | 专业类别 | 当前为 `pneumatic` 气动、`mechanical` 机械、`signal` 信号 |
| `nominalRole` | 名义用途 | 物理端口的入口/出口提示,或信号端口的输入/输出方向 |
| `effort` | 两端要相同的“势” | 气动压力 `p`;机械位移 `x`、速度 `v` |
| `flow` | 连接处要守恒的“流” | 气动质量流量 `m_flow`;机械力 `f` |
| `stream` | 随介质流动携带的性质 | 当前是气体流出比焓 `h_outflow` |
| `equal` | 两端相等 | 例如连接后 `p_A = p_B` |
| `sumToZero` | 两端相加为零 | 例如 `m_flow_A + m_flow_B = 0` |
| `streamMix` | 按实际流向传播/混合 | 不能简单令两端 `h_outflow` 相等 |
| `directed` | 按指定方向传值 | 例如阶跃源输出写入阀的信号输入 |
| registry / catalog | 型号登记表/产品目录 | 后端支持哪些模型,以及每种模型的完整定义 |
| compile | 接线检查和模型装配 | 根据 `modelType` 实例化组件并检查所有连接 |
| resolver | 专项计算器 | 分别处理信号、气体焓、移动容积等传播问题 |
| Schema / XSD | 格式规则 | 检查 JSON/XML 的字段和结构是否合规 |
| SI | 国际单位制 | Pa、kg/s、m、N 等;提交给求解器的值使用 SI 基准值 |
## 3. 用案例理解 physical 和 signal
### 3.1 案例 A:气室经节流孔连接
假设储气容器 A 的压力高于气室 B:
```text
tank_1.port_a ── orifice_1.port_a [节流孔] orifice_1.port_b ── chamber_1.port_1
```
仓库中的真实回归测试使用了一条更完整的链路:`cylinder(500 kPa) → orifice → pipe → tank(100 kPa)`(`tests/test_generic_system_xml_simulation.py:86-145`)。500 kPa 与 100 kPa 提供明显压差,便于检查压力和质量流量是否按预期推进。下面仍用 A、B 表示任意一对相连端口,规则与该测试相同。
这些都是 `physical / pneumatic` 端口。连接后,求解器关心三件主要事情:
1. 接头处的压力要相容;
2. 从一个组件流出的质量,必须流入另一个组件;
3. 气体携带的能量要按实际流向传递,发生汇合时还要混合。
这也是 `p`、`m_flow`、`h_outflow` 三个变量的来历:
| 变量 | 单位 | 人话解释 | 接线后的处理方式 |
| --- | --- | --- | --- |
| `p` | Pa | 接头处的绝对压力 | 两端相等:`p_A - p_B = 0` |
| `m_flow` | kg/s | 每秒有多少质量的气体流过 | 两端守恒:`m_flow_A + m_flow_B = 0` |
| `h_outflow` | J/kg | 如果气体从该组件流出,每公斤带走多少能量 | 按实际流向传播/混合,不直接令两端相等 |
| `volume` | m³ | 相邻移动机构提供的外部容积 | 内部辅助量,结果默认不展示 |
| `volume_flow` | m³/s | 上述外部容积每秒变化多少 | 内部辅助量,结果默认不展示 |
为什么两端的 `m_flow` 一正一负?项目统一规定“**进入当前组件为正**”。如果 0.01 kg/s 从 A 流进 B,那么从 A 的视角它在流出,约为 `-0.01`;从 B 的视角它在流入,约为 `+0.01`。这不是矛盾,只是观察对象不同。
`inlet`、`outlet`、`bidirectional` 是设计上的名义角色,不是止回阀。即使一个端口名义上叫 `outlet`,求解过程中仍可能出现反向流动。`PortState.actual_direction()` 使用约 `1e-12` 的死区判断 `in/out/stagnant`(`app/simulation/core/ports.py:218-231`)。
**[已实现]** `volume` 和 `volume_flow` 虽然使用 `signal/directed` 的变量规则,但它们仍装在气动物理端口里,不是画布上另一根信号线。`PneumaticVolumeResolver` 会沿现有气路传播它们(`app/simulation/solvers/pneumatic_volume.py:21-93`)。
### 3.2 案例 B:阶跃信号控制阀或力源
控制线与气管不同,它只把一个数值从发送方交给接收方:
```text
amesim_step0.out ───────────────> amesim_pnvo001.res
信号输出 output(发送方) 阀的信号输入 input(接收方)
```
当阶跃源在某个时刻从 0 跳到 1,`SignalResolver.solve()` 先更新信号源,再把输出值写到阀的 `res` 输入(`app/simulation/solvers/signal.py:40-68`)。阶跃发生的时刻还能作为积分断点,避免数值积分跨过突变点而不知情。
`tests/test_amesim_pnvo001_signal_xml.py:37-84` 正好演示了这个分工:`step_1.out → valve_1.res` 只传阀的控制命令,而 `cylinder → valve → tank` 的另外几条连接才传递气体的压力、质量流量和焓。控制线不会“变成气管”,阀组件负责在内部用命令改变气路行为。
同样的信号也能驱动力源:
```text
amesim_step0.out ──> amesim_forc.res [力源] amesim_forc.port_2 ── 机械网络
```
这里 `res` 是信号输入,`port_2` 是机械端口。信号与机械并没有直接相连,而是由 `amesim_forc` 组件内部方程把输入数值转换成力。
### 3.3 机械端口:把连接点当成同一个运动点
`physical / mechanical` 是一维平动机械连接,主要变量为:
| 变量 | 单位 | 人话解释 | 接线后的规则 |
| --- | --- | --- | --- |
| `x` | m | 连接点的位置 | 两端位移相等 |
| `v` | m/s | 连接点的速度 | 两端速度相等 |
| `f` | N | 组件在连接点承受的力 | 两端力相加为零 |
机械端口当前都标为 `bidirectional`。`MechanicalStateReducer` 会把刚性连接的一组惯性元件整理为一个共享的 `[v, x]` 状态坐标,避免同一运动被重复积分(`app/simulation/solvers/mechanical.py:225-248, 354-427`)。
### 3.4 跨域组件不是“不同插头直接相连”
当前有三种典型跨域组件:
- `amesim_forc`:信号输入 + 机械端口;
- `amesim_pnvo001`:信号输入 + 两个气动端口;
- `amesim_pnrp17`:一个气动端口 + 四个机械端口。
不同域之间的转换发生在组件内部方程中。网络层仍禁止把气动端口直接接到机械端口或信号端口。
## 4. 端口定义究竟包含什么
可以把 `PortDefinition` 看成端口铭牌。稳定定义在 `app/simulation/core/ports.py:38-47`:
| 字段 | 例子 | 通俗含义 |
| --- | --- | --- |
| `name` | `port_a`、`res` | 组件内部唯一的端口编号 |
| `kind` | `physical` / `signal` | 是物理接头还是控制线插座 |
| `domain` | `pneumatic` / `mechanical` / `signal` | 具体属于哪个专业类别 |
| `nominal_role` | `inlet`、`output` 等 | 名义用途;只有信号的 input/output 决定传播方向 |
| `positive_flow_direction` | `intoComponent` | 流和力的正号统一指向组件内部 |
| `variables` | `p`、`m_flow` 等 | 端口真正携带的变量、单位和连接规则 |
`side` 和 `order` 来自显示定义 `ComponentDisplaySpec.ports`,不是物理合同:
- `side`:端口图标画在节点左、右、上还是下;
- `order`:多个端口的显示顺序。
它们由 `ComponentModelSpec.as_catalog_dict()` 合并进目录响应(`app/simulation/registry.py:76-110`)。求解器不读取 `side`。
运行时的 `PortState` 是一只通用“数值盒子”,同时预留气动、机械和信号字段(`app/simulation/core/ports.py:194-207`)。不能因为盒子里有某个字段,就认定所有端口都支持该变量;真正要看的是 `PortDefinition.variables`。
## 5. JSON 和 XML 分别保存什么
本节继续用“储气容器连接节流孔”说明同一件事如何经过四层表示。
### 5.1 组件目录 JSON:产品说明书
`GET /api/components/catalog` 返回后端支持的全部型号(`app/main.py:283-287`、`app/simulation/registry.py:1002-1027`)。它受 `schemas/component-catalog-v1.schema.json` 约束,信息最完整。
下面是为了讲解而加了注释的 **JSONC**,不是可直接提交的严格 JSON:
```jsonc
{
"name": "port_a", // 端口编号
"kind": "physical", // 物理接头,不是控制线
"domain": "pneumatic", // 气动类别
"nominalRole": "bidirectional", // 设计上允许双向使用
"positiveFlowDirection": "intoComponent", // 正流量指向组件内部
"variables": [ // 完整变量表只在目录/编译结果中出现
{
"name": "p", // 压力
"role": "effort", // 连接后两端相等
"connectionRule": "equal",
"unit": "Pa",
"resultVisible": true
},
{
"name": "m_flow", // 质量流量
"role": "flow", // 连接后两端相加为零
"connectionRule": "sumToZero",
"unit": "kg/s",
"resultVisible": true
},
{
"name": "h_outflow", // 流出气体的比焓
"role": "stream", // 按流向传播/混合
"connectionRule": "streamMix",
"unit": "J/kg",
"resultVisible": true
}
],
"side": "left", // 只影响画面位置
"order": 10 // 只影响显示顺序
}
```
**[已实现]** 当前目录加载结果为 **27 个模型、61 个已声明端口**:38 个气动端口、19 个机械端口、4 个信号端口。两个介质定义模型没有端口,因此不计入 61 个端口。
### 5.2 ReactFlow 工程 JSON:画布存档
工程 JSON 保存“这次用了哪一个型号、端口快照和连线”,但不重复保存完整变量表。请求模型在 `app/main.py:86-155`;前端定义与生成逻辑见 `frontend/src/App.tsx` 中的 `PortDefinition`、`ReactFlowProjectPayload`、`buildProjectPayload()`。
下面仍是带说明的 JSONC:
为避免示例过长,这里只截取“储气容器接到节流孔”的局部画布;节流孔另一端尚未接出,所以它是**字段讲解片段**,不是可直接运行的完整工程。可运行的完整链路见 `tests/test_generic_system_xml_simulation.py:86-145`。
```jsonc
{
"projectSchemaVersion": 1, // 当前工程 JSON 的唯一格式版本
"name": "tank-orifice-demo",
"nodes": [
{
"id": "tank_1", // 本张图中的实例 ID
"type": "simulationComponent",
"position": {"x": 120, "y": 80}, // 画布位置
"data": {
"label": "储气容器 A",
"componentType": "tank",
"modelType": "tank", // 后端靠它回查完整模型定义
"modelVersion": "1.0.0", // 锁定保存时使用的模型合同
"ports": [
{
"name": "port_a",
"kind": "physical",
"domain": "pneumatic",
"nominalRole": "bidirectional",
"positiveFlowDirection": "intoComponent",
"side": "right"
}
],
"parameters": {} // 当前组件实例的参数值
}
},
{
"id": "orifice_1",
"type": "simulationComponent",
"position": {"x": 360, "y": 80},
"data": {
"label": "节流孔",
"componentType": "orifice",
"modelType": "orifice",
"modelVersion": "1.0.0",
"ports": [
{"name": "port_a", "kind": "physical", "domain": "pneumatic",
"nominalRole": "bidirectional", "positiveFlowDirection": "intoComponent", "side": "left"},
{"name": "port_b", "kind": "physical", "domain": "pneumatic",
"nominalRole": "bidirectional", "positiveFlowDirection": "intoComponent", "side": "right"}
],
"parameters": {}
}
}
],
"edges": [
{
"id": "edge-1",
"source": "tank_1", // ReactFlow 画线需要 source/target
"sourceHandle": "port_a",
"target": "orifice_1",
"targetHandle": "port_a", // 对物理线而言,不代表流动方向
"data": {"isContactEdge": false}
}
],
"simulation": {
"t_start": 0,
"t_stop": 2,
"step": 0.1,
"max_step": 0.005,
"method": "BDF"
}
}
```
后端会按 `modelType` 创建真实模型,并先要求节点 `modelVersion` 与注册版本完全一致,
再检查这里的端口名、类型、域、名义角色和正号是否与注册定义一致。
**[已实现] 前端工程和后端求解模型有意保持分工。**
- 目录 JSON 有完整 `variables`,前端只读取完成画布连接和即时提示所需的端口级字段;后端编译仍是物理合同的最终检查点。
- 工程 JSON 使用必填 `projectSchemaVersion: 1`,每个节点保存 `modelVersion`,端口必须是结构化对象,连接必须写明两端 Handle。结构版本不受支持时直接拒绝;节点模型版本缺失或不匹配时不得编译、导出 XML 或仿真。
- UI 继续通过浏览器 `localStorage` 和本地 JSON 文件保存工程。后端同时提供严格按工程 JSON v1 校验的工程列表、保存和读取接口;仿真主路径仍只向后端提交精简的 System XML v3。
### 5.3 System XML v3:交给后端的精简求解清单
XML v3 和工程 JSON 不再追求“保存同一份完整工程”。两者分工很明确:工程 JSON 保存怎样编辑和显示,XML v3 保存后端求解什么。当前结构由 `schemas/system-simulation-v3.xsd` 定义;完整规范见 `docs/standard/system-xml-v3.md`。
#### 5.3.1 生成出来的 XML 是什么结构
```text
System 整个可执行模型
├─ Simulation 恰好 1 个:仿真时间和算法
├─ Components 组件清单
│ └─ Component * 0~多个组件
│ └─ Parameter * 组件的完整 SI 参数
└─ Connections 接线清单
└─ Connection * 0~多条连接
├─ Endpoint 每条连接恰好两个端点
└─ Endpoint
```
XML 外形是一棵树,模型仍是一张连接图。组件平铺在 `<Components>` 中,`<Connections>` 再通过“组件 `id` + 注册端口名”把它们接起来。顶层顺序固定为 `Simulation → Components → Connections`。
与 v2 相比,v3 主动删掉了 `Port` 快照和画布字段。各字段来源如下:
| XML v3 内容 | 来源 | 通俗解释 |
| --- | --- | --- |
| `System/@name` | `project.name` | 可选的模型名称 |
| `schemaVersion/unitSystem` | 生成器固定写入 | 当前协议固定为 v3,参数使用 SI |
| `Simulation` | 求值后的仿真设置 | 起止时间、结果采样间隔、内部最大步长和算法 |
| `Component/@id` | `node.id` | 连接实际引用的稳定实例编号 |
| `Component/@type` | `modelType` | 用哪个后端模型类创建实例 |
| `Component/@modelVersion` | 组件目录/注册表 | 锁定本文件采用的模型合同版本 |
| `Parameter` | 参数表达式求值并换算后的值 | 写出该模型的全部注册参数,只留最终 SI 数值 |
| `Endpoint` | ReactFlow 边两端的 handle | 用“组件 ID + 端口名”重新接线 |
实际生成过程可以概括为:
1. 收集当前节点、连线和仿真设置;
2. 把工程 JSON 的 `simulation.step` 映射成 XML 的 `sampleStep`;
3. 根据组件目录写入 `modelVersion`,并用注册默认值补齐全部参数;
4. 把参数表达式求值、换算为 SI,只写 `Component/Parameter`;
5. 每条边只写两个 `Endpoint`,不复制端口类型或方向角色。
XML v3 不保存显示名称、`symbol`、坐标、`side`、旋转、镜像、参数显示单位、科学计数法偏好、撤销历史、当前选择和仿真结果。因此它适合校验、交换和求解,但不能无损还原前端画布;要继续编辑,应保存工程 JSON。
#### 5.3.2 一个最小 XML 片段
下面用“阶跃信号 → 力源 → 零力端”同时展示信号和机械连接:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<System name="signal-force-demo" schemaVersion="3" unitSystem="SI">
<Simulation tStart="0" tStop="2"
sampleStep="0.1" maxStep="0.005" method="BDF"/>
<Components>
<Component id="step_1" type="amesim_step0" modelVersion="0.1.0">
<Parameter name="initial" value="0"/>
<Parameter name="final" value="20"/>
<Parameter name="time" value="0.04"/>
</Component>
<Component id="force_1" type="amesim_forc" modelVersion="0.2.0">
<!-- +1 为默认施力方向,-1 为反向 -->
<Parameter name="direction" value="1"/>
</Component>
<Component id="zero_1" type="amesim_f000" modelVersion="0.1.0"/>
</Components>
<Connections>
<Connection id="signal-1">
<Endpoint component="step_1" port="out"/>
<Endpoint component="force_1" port="res"/>
</Connection>
<Connection id="mechanical-1">
<Endpoint component="force_1" port="port_2"/>
<Endpoint component="zero_1" port="port_1"/>
</Connection>
</Connections>
</System>
```
这里没有写 `kind/domain/role`,也没有 `<Port>`。后端看到 `type="amesim_step0"` 后,会从注册表知道 `out` 是信号输出;看到 `amesim_forc.res` 后,会知道它是信号输入;同理还能恢复两个机械端口的变量合同。也就是说,端口名是“查说明书的索引”,不是由 XML 自己重新定义接口。
XML v3 的连接规则是:
- 物理和信号 `Connection` 都只写两个 `Endpoint`;
- 两端的 `kind/domain/nominalRole/variables` 都从当前注册模型恢复;
- 信号连接必须恰好包含一个注册输出端和一个注册输入端,但端点先后顺序不决定方向;一个输出可以扇出到多个输入,每个输入只能有一个驱动;
- 物理端点无序,流向由求解结果和统一正号约定决定;
- 参数必须完整、有限、使用 SI,并通过当前模型的范围或枚举校验。
**[已实现] 布局与物理方向已经分开。** `side/rotation/mirrored` 只保留在工程 JSON 中,XML v3 不包含它们。`amesim_forc` 从模型版本 `0.2.0` 起用显式 `direction=+1/-1` 控制力的正反:相同输入 20 N 时,`direction=+1` 要求机械端口平衡值为 `f=-20 N`,`direction=-1` 时为 `f=+20 N`。旋转或镜像图标不改变求解结果(`app/simulation/components/amesim/mechanical/translational.py`、`tests/test_amesim_mechanical_public_components.py`)。
### 5.4 编译结果 JSON:接线检查报告
`POST /api/reactflow/compile-model` 和 `POST /api/system-xml/compile-model` 最终调用 `SimulationNetwork.as_interface_dict()`(`app/simulation/systems/network.py:280-314`)。
它不是新的工程存档,而是告诉调用者:“后端实际装配出了什么”。下例也是带注释的结构示意:
```jsonc
{
"name": "tank-orifice-demo",
"components": [
{
"id": "tank_1",
"type": "tank",
"ports": [
{
"name": "port_a",
"kind": "physical",
"domain": "pneumatic",
"variables": [
// 工程 JSON 和 XML 都没保存的完整变量定义,在这里由注册表恢复
{"name": "p", "connectionRule": "equal", "unit": "Pa"},
{"name": "m_flow", "connectionRule": "sumToZero", "unit": "kg/s"},
{"name": "h_outflow", "connectionRule": "streamMix", "unit": "J/kg"}
]
}
]
}
],
"connections": [
{
"id": "edge-1",
"kind": "physical",
"domain": "pneumatic",
"endpoints": [
{"component": "tank_1", "port": "port_a"},
{"component": "orifice_1", "port": "port_a"}
] // 物理连接只有两个端点,不再保留 source/target 含义
}
],
"unconnectedPorts": [] // 若有漏接,会在这里或诊断中体现
}
```
### 5.5 四层字段对照
先说结论:v3 XML 只携带连接实际用到的端口名,端口大类和完整变量表都由后端注册表恢复。这样不会同时维护“模型类中的端口”和“XML 中的端口副本”。
| 模型含义 | 目录 JSON | 工程 JSON | System XML v3 | 编译结果 JSON |
| --- | --- | --- | --- | --- |
| 端口编号 | `name` | `name` | 只在 `Endpoint/@port` 出现 | `name` |
| 物理/信号 | `kind` | `kind` | 不保存,由 `type + port` 恢复 | `kind` |
| 气动/机械/信号 | `domain` | `domain` | 不保存,由注册表恢复 | `domain` |
| 名义角色 | `nominalRole` | `nominalRole` | 不保存,由注册表恢复 | `nominalRole` |
| 正号约定 | `positiveFlowDirection` | 同名字段 | 不保存,由注册表恢复 | 同名字段 |
| 完整变量和规则 | `variables[]` | 不保存 | 不保存 | `variables[]`,由注册表恢复 |
| 显示侧 | `side` | `side` | 不保存 | 不是求解合同 |
| 画布连线 | 不适用 | `source/.../targetHandle` | 两个 `Endpoint` | 规范化后的连接端点 |
| 模型合同版本 | `modelVersion` | 节点显式保存并与目录核对 | `Component/@modelVersion` 必填 | 按当前注册模型编译 |
## 6. 接线时后端具体检查什么
可以把 `SimulationNetwork.connect()` 当作最后一道“防止插错线”的检查(`app/simulation/systems/network.py:83-150`)。它会依次确认:
1. 组件和端口确实存在,且不是自己接自己;
2. 两端 `kind` 一样;
3. 两端 `domain` 一样;
4. 完整变量表一致;
5. 信号线是一端 input、一端 output;
6. 一个物理端口最多接一条线;需要分支时必须放入 `tee`、`amesim_pn3node2`、`amesim_p4node2` 等分支组件;
7. 连接 ID 和端点组合没有重复。
对于案例 A 的一条气动连接,网络直接形成:
```text
p_A - p_B = 0 # 接头两侧压力一致
m_flow_A + m_flow_B = 0 # 流出一侧的质量等于流入另一侧的质量
```
不会形成 `h_outflow_A = h_outflow_B`。焓要在压力、流量确定后,由 `StreamResolver` 根据真实流向传播或混合。物理连接端点顺序不影响结果,相关测试包括 `tests/test_component_interfaces.py:51-60`、`tests/test_system_xml_protocol.py:146-176` 和 `tests/test_generic_system_xml_simulation.py:354-372`。
XML v3 还会先经过:
- 安全解析和 5 MiB 大小限制(`app/system_xml.py:21, 243-280`);
- XSD 格式检查;
- 组件类型、`modelVersion`、参数完整性和值域检查;
- 端点引用检查,并从注册表恢复端口类型、角色、变量和正号约定;
- 端点占用、信号输入/输出配对、物理域和变量合同检查。
XML 语义检查会把未连接端口记为 warning;真正进入通用求解前,未连接的**物理端口**会成为 `PORT_UNCONNECTED` error(`app/simulation/systems/generic.py:93-125`)。未连接信号端口不会阻止求解。
## 7. 当前容易踩坑的跨层差异
这些问题不妨碍理解主流程,但开发或制作 XML 时必须注意。
### 7.1 信号多接的规则前后端不一致
**[发现]** 后端网络和 XML 只限制物理端口单连接,没有禁止多个信号源同时连接到一个 input。`SignalResolver.solve()` 会按连接顺序依次写入,因此最后一个值覆盖前面的值。
前端 `canConnectPorts()` 却对所有端口实行一对一:既阻止多个源写同一输入,也阻止一个输出连到多个输入(`frontend/src/App.tsx`)。所以:
- 在浏览器里通常画不出这种多接;
- 直接调用 XML/API 却可能构造出来;
- 最后覆盖不是稳定的“求和”或“仲裁”规则,不应依赖。
后续最好统一为“每个信号输入只能有一个驱动”,或者显式增加求和、选择、总线组件。
### 7.2 XML 不写端口类型,不等于后端不知道类型
**[已实现]** v3 有意删除 `Port`、连接的 `kind/domain` 和端点 `role`。这不是允许调用者“随便省略”,而是把端口合同收回到唯一事实来源:后端注册模型。语义检查会用 `Component/@type + Endpoint/@port` 查出 `kind/domain/nominalRole/variables`;端口不存在或两端不兼容仍会报错。手写 v3 时不要把 v2 的这些字段加回来,XSD 会拒绝它们。
### 7.3 JSON 和 XML 对缺省参数的处理不同
**[发现]** ReactFlow JSON 编译和 JSON→XML 导出会用注册默认值补齐缺失参数;System XML v3 语义检查会对缺少的注册参数报告 `PARAMETER_REQUIRED_MISSING`。因此“工程 JSON 可以省略默认参数”不等于“手写 XML 也可以省略”。正规导出器会替你写全,人工制作 v3 时必须列全。
### 7.4 当前 API 只接受 v3
仓库只保留当前的 v3 协议和 XSD。v1/v2 不属于受支持输入,旧版本号只出现在拒绝边界测试和“旧格式不受支持”的说明中。
**[已实现]** 当前 `validate_system_xml_document()` 固定加载 `schemas/system-simulation-v3.xsd`,不会按 `schemaVersion` 自动切换或迁移 v1/v2;新文件必须使用 v3。
### 7.5 XML v3 会锁定模型版本
**[已实现]** `Component/@modelVersion` 是 v3 必填属性,语义检查要求它与当前注册模型版本完全一致。例如 `amesim_forc` 当前是 `0.2.0`,旧版本号不会被静默当成新方程求解。工程 JSON v1 的每个节点也保存创建时的 `modelVersion`,编译和导出 XML 前必须先与当前目录核对。这种设计采取“发现不一致就拒绝”的策略,不表示系统已经提供自动模型迁移器。
### 7.6 不从旧格式推断当前行为
当前格式只看 `docs/standard/system-xml-v3.md` 和 `schemas/system-simulation-v3.xsd`。旧格式中的 `Port`、布局字段、端点 `role`、`Simulation/@step` 和“旋转改变力方向”都不能继续套用到 v3。
## 8. 当前模型覆盖范围
不需要记住所有型号,只需知道它们仍归入前面三种插头标准。
| 用途 | 当前模型 |
| --- | --- |
| 实验气动 | `cylinder`、`tank`、`pipe`、`orifice`、`tee` |
| AMESim 气动 | `amesim_pnpl01`、`amesim_pnrp17`、`amesim_pnch023`、`amesim_pnch012`、`amesim_pnor001`、`amesim_pnvo001_fixed`、`amesim_pnvo001`、`amesim_pnl00r`、`amesim_pnl0001`、`amesim_pnl0002`、`amesim_pnl0003`、`amesim_pn3node2`、`amesim_p4node2` |
| AMESim 机械 | `amesim_f000`、`amesim_forc`、`amesim_mecmas21`、`amesim_lstp00a`、`amesim_lmechn1`、`amesim_pnrp17` |
| AMESim 信号 | `amesim_step0`、`amesim_ud00`、`amesim_forc`、`amesim_pnvo001` |
| 零端口介质定义 | `amesim_ideal_air_medium`、`amesim_helium_medium` |
两个介质模型虽然出现在组件目录中,却没有连接端口。它们在编译第一阶段登记 `gi=1..99` 的介质定义,之后不进入方程网络(`app/main.py:1206-1253`)。它们是配置节点,不是第四类接口。
## 9. 与接口表示相关的 HTTP API
这里的 API 可以理解为围绕上述四层数据提供的“入口按钮”。
| API | 人话解释 | 当前前端是否直接使用 |
| --- | --- | --- |
| `GET /api/components/catalog` | 获取后端产品说明书 | 是 |
| `GET /api/reactflow/projects` | 列出后端保存的工程 JSON v1 | 当前 UI 主路径不用 |
| `GET /api/reactflow/projects/{id}` | 读取并校验一个后端工程 | 当前 UI 主路径不用 |
| `POST /api/reactflow/projects/{id}` | 保存一个工程并保留单位/科学计数显示信息 | 当前 UI 主路径不用 |
| `POST /api/reactflow/system-xml` | 把工程 JSON 转成精简 XML v3 | UI 当前也能在浏览器内生成同一结构 |
| `POST /api/reactflow/compile-model` | 检查工程 JSON 并返回装配结果 | 可用于诊断 |
| `POST /api/system-xml/validate` | 只检查 XML 格式和语义 | 可用于诊断 |
| `POST /api/system-xml/parse` | 把 XML 变成规范化执行模型;不还原坐标、旋转等画布信息 | 可用于检查求解输入,不是无损工程导入 |
| `POST /api/system-xml/compile-model` | 检查 XML 并装配网络 | 求解前使用 |
| `POST /api/system-xml/simulate-stream` | 提交 XML 并持续接收进度/最终结果 | 当前 UI 的通用求解入口 |
| `POST /api/system-xml/simulate` | 同步返回完整结果 | 后端提供,UI 主路径不用 |
后端还提供 CSV 导出 API。当前 UI 的工程保存和读取主要发生在浏览器与用户选择的本地文件中,后端工程接口作为同一严格合同的可选持久化入口。
## 10. 已实现、约定和推断:最后再分一次边界
### [已实现]
- 当前实际有气动、机械、标量信号三类端口,模型加载快照为 27 个模型、61 个端口。
- 物理端口以进入组件为正,物理连接端点无序;信号方向由注册端口的 output/input 决定,XML 端点不写 source/target 角色。
- 注册器、JSON/XML 编译器、XML 语义层和网络层会分层检查接口。
- 气动压力/质量流量约束、焓传播、外部容积传播、机械连接约束和标量信号传播已有可执行代码。
- 工程 JSON 可导出 System XML v3;XML v3 可解析为执行模型并编译为带完整端口合同的网络。
- `amesim_forc` 用 `direction=+1/-1` 决定力方向;旋转/镜像只影响显示。
- MECMAS21 工程、目录、XML 和模型统一使用 AMESim 原生 `1/2` 选项编码,不再猜测或转换旧 `0/1` 值。
### [约定]
- 组件模型类的 `PORTS` 是权威端口合同;前端目录只是读取视图。
- XML 和执行参数使用 SI 基准值。
- 物理端口的 `nominalRole` 不限制实际流向。
- 新文件使用 System XML v3;每个组件显式写当前 `modelVersion`,仿真采样字段写 `sampleStep`。
### [推断/需要另行实测]
- 类型字段允许出现其他 `domain` 字符串,但新增液压、电气等域还需要变量定义、组件方程和专项求解器;只改字符串不能工作。
- 27 个模型和 61 个端口是本次加载快照。受控库清单改变后,数量也会改变。
- v1/v2 XML 明确不受支持;旧工程 JSON 也不会由当前前端自动猜测或迁移。
## 11. 关键文件与符号索引
读到具体疑问时,可从这里回到代码。前面的章节已经给出人话解释,本表用于精确定位。
| 主题 | 文件与位置 | 关键符号 |
| --- | --- | --- |
| 端口类型、变量、正号 | `app/simulation/core/ports.py:7-231` | `PortVariableDefinition`、`PortDefinition`、`PortState` |
| 组件事实来源 | `app/simulation/core/base.py:21-63` | `Component.PORTS`、`register_declared_port()` |
| 端口显示信息 | `app/simulation/core/catalog.py:27-71` | `PortDisplaySpec`、`ComponentDisplaySpec` |
| 注册发现与校验 | `app/simulation/registry.py:41-170, 416-490, 825-1027` | `ComponentModelSpec`、`_validate_port()`、`build_component_catalog()` |
| 网络接线与方程 | `app/simulation/systems/network.py:24-314` | `Connection`、`SimulationNetwork.connect()`、`connection_equation_residuals()` |
| 工程 JSON 与编译 | `app/main.py:86-155, 1181-1344` | `ReactFlowPortDefinition`、`compile_reactflow_network()` |
| JSON 转 XML | `app/main.py:947-1097` | `build_reactflow_system_xml()` |
| XML 解析和语义检查 | `app/system_xml.py` | `SystemXmlComponent`、`SystemXmlEndpoint`、`SystemXmlDocument`、`validate_system_xml_document()` |
| XML v3 当前格式 | `schemas/system-simulation-v3.xsd`、`docs/standard/system-xml-v3.md` | `sampleStep`、`modelVersion`、`Parameter`、`Endpoint` |
| 目录 JSON 格式 | `schemas/component-catalog-v1.schema.json:52-160` | `$defs.portVariable`、`$defs.port` |
| 前端端口和工程类型 | `frontend/src/App.tsx` | `PortDefinition`、`ReactFlowProjectPayload` |
| 前端生成 XML | `frontend/src/App.tsx` | `buildSystemXml()`、`projectConnectionMetadata()` |
| 接口核心测试 | `tests/test_component_interfaces.py` | 变量规则、端点中立、单连接限制 |
| XML 协议测试 | `tests/test_system_xml_protocol.py`、`tests/test_system_xml_parser.py` | v3 表示和语义诊断 |
## 12. 一句话复盘
SystemSimulationApp 用三种端口把组件组成网络:气动和机械端口负责“守恒与相容”,信号端口负责“有方向地传一个数值”;目录 JSON 定义型号,工程 JSON 保存画布,System XML v3 保存精简求解清单,编译结果则证明后端最终理解并装配出了什么。
@@ -0,0 +1,601 @@
# 求解器性能与鲁棒性优化任务清单
> 用途:记录求解器优化的现状、证据、实施顺序和验收结果,供后续开发前后对比与持续更新。
> 首次建立:2026-08-17
> 基线代码:`6bb0591d320d0c448ee8d224dd44127bfe3ce00f`(本地 `model-development`)
> 基线模型:`tests/data/test_mql-full-branches-01-04.xml`
> 模型 SHA-256:`2fb95e65f5de0c85a6a17802aef74ea004087323fd00fd8d01acf0184ff71d48`
## 1. 使用规则
本文档不是一次性的建议列表,而是优化工作的验收账本。
- 状态统一使用:`未开始`、`进行中`、`部分实现`、`已完成`、`阻塞`、`不采用`。
- 只有同时完成代码、自动测试、基准复测和本文档更新后,任务才可标记为“已完成”。
- 每次性能对比必须记录代码提交、工作树状态、输入哈希、解释器与依赖版本、硬件和运行参数。
- 正确性门槛先于速度收益。若结果越过误差契约,即使运行更快也不能合入默认路径。
- 容差、模型方程或输出字段发生变化时,必须单独说明;不得将其伪装成纯性能优化。
- 墙钟时间只在同一台机器、相同负载和相同环境下直接比较;跨环境以工作量计数和正确性指标为主。
- 每项优化都应保留明确的关闭开关或旧路径,直到新路径经过复杂模型和通用回归验证。
## 2. 当前结论与基线
### 2.1 关于 2.05 s 卡死
当前随附 XML 的磁盘配置是 `tStop=0.81 s`,因此原文件本身不会运行到 2.05 s。将停止时间仅在内存中改为 `2.10 s` 后,当前代码已经完整越过 2.05 s 并正常结束:
- `2.040432 s`:墙钟 `114.065 s`
- `2.046141 s`:墙钟 `120.746 s`,期间 CPU 时间持续增长
- `2.051691 s`:墙钟 `121.548 s`
- `2.100000 s`:完成积分并进入后处理
- 总运行完成,无重试、无非线性回退,也没有无进度死锁
因此,当前证据支持“此前的 2.05 s 卡死在现版本中没有复现”;该区间仍存在数秒级慢推进。`10 s` 尚未验证,不能由本次结果外推保证。
### 2.2 环境说明
仓库内 `.venv` 当前不完整,本次复测使用现有 `/opt/srm-trial-review/.venv`:
| 项目 | 本次值 |
| --- | --- |
| Python | 3.12.3 |
| NumPy | 2.4.6 |
| SciPy | 1.17.1 |
| 求解器 | BDF |
| 输出步长 | 0.01 s |
| 执行路径 | stream/cancel-check |
该环境满足仓库依赖范围,但并非已经锁定的正式项目环境。当前物理解哈希与历史调研文档不同,所以逐位结果基线必须在正式锁定环境中再次确认。
### 2.3 当前实测基线
| 指标 | 原始 `0.81 s` | 仅内存延长至 `2.10 s` |
| --- | ---: | ---: |
| 状态 | 完成 | 完成,越过 2.05 s |
| 墙钟时间 | 63.779 s | 126.211 s |
| 积分时间 | 62.116 s | 122.180 s |
| 后处理时间 | 1.118 s | 2.703 s |
| 最大 RSS | 165,464 KiB | 198,348 KiB |
| 输出样本数 | 82 | 213 |
| 状态数 | 74 | 74 |
| `nfev / njev / nlu` | 3763 / 253 / 761 | 6734 / 487 / 1507 |
| 接受步 | 1076 | 1857 |
| 分段启动 | 3 | 5 |
| 状态切换 | 0 | 2 |
| 重试 | 0 | 0 |
| 有限差分附加 RHS 估计 | 7843 | 15097 |
| 压力闭合次数 | 30,502 | 57,601 |
| 非线性/块/稠密回退 | 0 / 0 / 0 | 0 / 0 / 0 |
| 最大热流体外迭代 | 3 | 3 |
| Jacobian 稀疏度 | 1284 nnz / 31 色 | 1284 nnz / 31 色 |
补充观察:
- 积分占总耗时约 97%,当前首要瓶颈不是后处理。
- `2.10 s` 运行中,估计总 RHS 工作量约为 `6734 + 15097 = 21831`;有限差分扰动约占 69%。
- 压力闭合约为每次估计 RHS 2.64 次,但全部走已播种的因果路径,没有触发 `least_squares`。
- 全局因果执行已启用:快速执行 22,216 次,完整残差审计 351 次,审计失败 0 次,旧路径回退 0 次,审计间隔为 64。
- 当前结果哈希仅作为本次环境的诊断记录:`0.81 s` 为 `c6354c97...`,`2.10 s` 为 `efef49f8...`;它们暂不作为跨环境验收标准。
### 2.4 当前模型结构基线
| 项目 | 数量 |
| --- | ---: |
| XML 组件 / 编译组件 | 99 / 98 |
| 连接 | 106 |
| 连续状态 | 74 |
| 代数未知量 / 方程 | 472 / 472 |
| 原始关联矩阵非零元 / 方程块 | 919 / 58 |
| effort 未知量 / flow 未知量 | 272 / 200 |
| effort 等价组 / 可消去重复 effort | 68 / 204 |
| 显式 flow/force 赋值 | 200 |
| stream 块 / stream 未知量 | 9 / 192 |
| 结果变量 | 1,021 |
## 3. 总体验收协议
每个优化 PR 至少执行以下分层验证;高风险改动不得只用单点输出或单个哈希判断正确性。
### 3.1 快速结构检查(CI)
- [ ] 模型输入 SHA-256 与固定 fixture 一致。
- [ ] 组件、连接、状态、代数方程和 stream 结构数量符合预期。
- [ ] Jacobian 结构至少覆盖已知跨域依赖,并通过稠密数值扰动抽查。
- [ ] 因果计划覆盖率、回退原因和审计失败数可观测。
### 3.2 数值检查点
至少覆盖以下区间和模式边界:
- [ ] `0.68–0.71 s`:历史慢区。
- [ ] `0.79–0.81 s`:原始模型终点及信号事件附近。
- [ ] `2.00–2.10 s`:此前报告卡死区间和状态切换。
- [ ] `10 s`:长时间模式变化验证,完成 OPT-09 后启用。
每个检查点比较:连续状态、关键压力/流量/位移/速度、事件时刻与顺序、模式状态、有限性、最大缩放残差及守恒量。
### 3.3 性能记录
每次正式对比至少预热 1 次、测量 3 次并报告中位数,同时保存:
- 总时间、积分时间、后处理时间、CPU 利用率、峰值 RSS。
- `nfev`、`njev`、`nlu`、接受/拒绝步、分段和重试次数。
- SciPy 模式的有限差分 RHS 估计;callable 模式的真实扰动、基准和 Jv 审计 RHS 计数;Jacobian 颜色数与构建时间。
- 代数闭合次数、快速因果次数、完整审计次数和各类回退次数。
- stream/热流体迭代次数、物性缓存命中率、事件候选与定位次数。
- 输出标量数、编码字节数、传输字节数和后处理峰值内存。
## 4. 优化任务总览
优先级定义:`P0` 为基线或正确性前置,`P1` 为主要性能收益,`P2` 为第二阶段,`P3` 为战略性或条件性工作。
| ID | 优先级 | 任务 | 当前状态 | 难度 | 预期价值 | 主要依赖 |
| --- | --- | --- | --- | --- | --- | --- |
| OPT-00 | P0 | 固化复现、环境和回归基线 | 进行中 | 中 | 很高 | 无 |
| OPT-01 | P1 | 完成因果代数内核与坐标消元 | 部分实现 | 中高 | 中高 | OPT-00 |
| OPT-02 | P1 | 建立扁平数值 IR 和数组执行内核 | 未开始 | 很高 | 很高 | OPT-01 |
| OPT-03 | P1 | 稀疏 Jacobian 数值层与解析/半解析演进 | 部分实现 | 很高 | 很高 | OPT-00;解析链可与 OPT-02 分阶段 |
| OPT-04 | P1 | stream 拓扑传播与物性成组复用 | 部分实现 | 中高 | 中高 | OPT-00 |
| OPT-05 | P1 | 状态缩放、分量容差和步长策略 | 未开始 | 中高 | 中高 | OPT-00 |
| OPT-06 | P2 | 事件检测与 dense output 按需化 | 部分实现 | 中 | 中 | OPT-00 |
| OPT-07 | P2 | 输出、后处理和传输内存优化 | 未开始 | 中 | 中高(长仿真) | OPT-00 |
| OPT-08 | P2 | 进度、取消和服务并发鲁棒性 | 部分实现 | 中 | 中 | OPT-00 |
| OPT-09 | P0/P2 | 建立 10 s 长时验证与模式覆盖 | 未开始 | 中高 | 很高 | OPT-00 |
| OPT-10 | P3 | 明确高指数 DAE/强非光滑系统边界 | 未开始 | 很高 | 条件性 | OPT-09 |
推荐实施顺序:`OPT-00 → OPT-03/OPT-01 → OPT-04/OPT-05 → OPT-02 → OPT-06/OPT-07/OPT-08 → OPT-09`。其中 OPT-02 与 OPT-03 可先做最小原型,再根据端到端数据调整顺序。
## 5. 详细任务
### OPT-00 固化复现、环境和回归基线
**目标**:先让“是否更快、是否仍正确、是否又卡住”可以稳定复现和自动判断。
**当前状态**:已有手工 `0.81 s` 和 `2.10 s` 复测及若干结构回归;复杂 XML、正式运行环境、分层性能门槛尚未完整固化。
**工作项**:
- [ ] 将复杂 XML 作为正式测试 fixture 纳入版本控制,并在测试中校验哈希。
- [ ] 修复或重建项目 `.venv`,锁定 Python、NumPy、SciPy 及平台信息。
- [ ] 将临时探针整理为仓库内可重复运行的 benchmark,不依赖 `/tmp` 文件。
- [ ] 添加 `0.81 s` 和仅改 `tStop=2.10 s` 的标准运行入口。
- [ ] 添加模型结构快照断言;结构有意变化时显式更新原因。
- [ ] 定义 `physical-state-v2`:仅包含物理状态、关键代数量、事件与模式,不包含展示字段和易变诊断字段。
- [ ] 将完整 API 输出哈希与物理解哈希分开,分别用于输出契约和数值回归。
- [ ] 建立短 CI、夜间 `0.81/2.10 s`、定期 `10 s` 三层任务。
- [ ] 保存机器可读的 JSON 基准结果,避免只在文档中抄写数字。
**验收条件**:
- [ ] 干净环境一条命令可复现;失败时能区分超时、无进度、数值失败和服务失败。
- [ ] 正式环境连续 3 次完成 `2.10 s`,结果满足数值契约且无非预期回退。
- [ ] 性能报告完整记录环境、提交、工作树、输入哈希和统计口径。
**前后对比**:
| 指标 | 当前 | 完成后 |
| --- | --- | --- |
| 正式锁定环境 | 无 | 待填 |
| 复杂模型自动回归 | 部分 | 待填 |
| 物理解哈希 | 环境相关 v1 | 待填 |
| `2.10 s` 连续成功率 | 单次证据 | 待填 |
### OPT-01 完成因果代数内核与坐标消元
**目标**:在已存在的因果快速路径上,真正移除运行时冗余坐标和对象访问,而不是再次实现一套同类快速路径。
**当前状态**:主要思路已经实现。全局和 secondary stream 块可以执行显式因果计划,完整残差按 64 次间隔审计;`2.10 s` 中快速执行 22,216 次、审计 351 次、失败和旧路径回退均为 0。仍保留 472 个运行时未知量,204 个重复 effort 坐标尚未在执行层消除,且存在清零、复制、缩放、`getattr/setattr` 和完整对象遍历成本。
一次已预热的 A/B 微基准显示,整个 RHS 的因果快速模式中位数约 `200.8 ms/100 次`,强制完整检查约 `297.4 ms/100 次`,即现有路径已经取得约 `1.48×` 的整 RHS 收益。单纯继续增大审计间隔预计收益有限。
**工作项**:
- [ ] 将 68 个 effort 等价组压缩为独立运行时坐标,消除 204 个重复 effort 槽。
- [ ] 将 200 条显式 flow/force 规则预编译为稳定顺序和整数槽索引。
- [ ] 用预分配连续数组代替热路径对象读写、临时字典和重复缩放。
- [ ] 仅清理会被当前计划写入的槽,避免每次全量清零和复制。
- [ ] 保留初始化、事件后、接受步或固定间隔的完整残差审计。
- [ ] 自定义组件、声明缺失、审计失败或奇异结构必须自动回退旧求解器。
- [ ] 输出编译统计:消元数、显式规则覆盖率、审计率、失败原因和回退次数。
**验收条件**:
- [ ] 复杂模型因果覆盖率大于 98%,完整 `0.81/2.10 s` 运行审计失败为 0。
- [ ] 新旧路径的状态、事件、关键代数量和残差均满足统一数值契约。
- [ ] 自定义组件、接触模型和非因果结构的回退测试全部通过。
- [ ] 在完整模型上证明端到端收益;不得只提交代数微基准。
**风险与回滚**:别名写回、事件后模式改变和不完整依赖声明可能造成静默错误。新路径必须可通过配置关闭,并在审计失败时记录首个违规方程与变量。
| 指标 | 当前 | 完成后 |
| --- | ---: | ---: |
| 运行时代数槽 | 472 | 待填 |
| 重复 effort 槽 | 204 | 待填 |
| 已预热 Python 调用/单 RHS | 约 15,774 | 待填 |
| 因果审计失败 | 0 | 待填 |
| `0.81/2.10 s` 墙钟中位数 | 63.779 / 126.211 s(单次环境值) | 待填 |
### OPT-02 建立扁平数值 IR 和数组执行内核
**目标**:把组件对象、字典查找和端口读写转换成稳定的数值执行计划,为 NumPy、Numba 或原生后端提供共同基础。
**当前状态**:构建阶段已有一定预绑定,但 RHS 仍以 Python 对象和方法调用为主。已预热、启用物性缓存时,采样剖析约有 15,774 次 Python 调用/RHS;不同缓存上下文会明显改变该数字,所以后续必须统一测量口径。
**工作项**:
- [ ] 定义最小数值 IR:连续槽、常量、参数、状态、代数量、模式位和操作码。
- [ ] 将组件方程、因果规则、stream 传播和结果提取分成明确执行阶段。
- [ ] 先实现可逐项对照的纯 Python/NumPy 参考后端。
- [ ] 添加 IR 与当前对象执行器逐操作/逐阶段差分测试。
- [ ] 评估 Numba 与 C/C++ 后端;在 IR 稳定前不绑定单一编译技术。
- [ ] 对动态自定义组件保留对象适配层和明确的性能降级提示。
- [ ] 缓存编译结果,并以模型结构、组件版本和数值后端作为缓存键。
**验收条件**:
- [ ] 全部现有组件族通过新旧执行器差分测试。
- [ ] 事件切换后能正确重编译或选择预编译模式计划。
- [ ] 明显降低 Python 调用数、对象分配和 RHS 中位时间,并改善完整仿真墙钟。
- [ ] 不以牺牲异常信息、取消检查或回退能力换取速度。
| 指标 | 当前 | 原型后 | 完成后 |
| --- | ---: | ---: | ---: |
| Python 调用/单 RHS | 约 15,774 | 待填 | 待填 |
| 临时分配字节/单 RHS | 待测 | 待填 | 待填 |
| RHS 中位时间 | 约 2 ms(现有微基准口径) | 待填 | 待填 |
| `2.10 s` 积分时间 | 122.180 s | 待填 | 待填 |
### OPT-03 稀疏 Jacobian 数值层与解析/半解析演进
**目标**:先建立可审计、可回滚的 callable sparse Jacobian 数值层,再逐步把组件、因果代数计划、stream 和物性的局部导数传播进来。完整稀疏有限差分、受审计 secant 和真正的解析/半解析 Jacobian 是三个不同阶段,必须分别记录和验收。
**当前状态**:数值层基础与实验候选已经实现;首批“证明门控”的三活塞 6 列半解析切片已经接入,但通用组件、stream SCC 和其余状态列仍未覆盖,因此 OPT-03 总体继续标记为“部分实现”。默认执行路径继续使用 SciPy `jac_sparsity`,半解析路径只允许通过 `SIMULATION_ODE_JACOBIAN_MODE=semi-analytic` 显式启用。
现有实现包括:
- direct 和 stepwise BDF/Radau 均可接收 callable `jac`;信号断点、状态事件和可恢复重启会清空 Jacobian 数值状态并重新构建,显式积分器完全忽略该对象。
- 新增独立的 sparse numerical Jacobian 内核,隔离并检查 SciPy 私有 `num_jac/group_columns` 接口。
- 每个 solver segment 记录完整构建、有限差分扰动、基准 RHS、Jv 审计、secant 复用/失败和装配时间;SciPy 模式的估计值不再伪装成 callable 模式的真实计数。
- 4 条无离散端挡模式歧义的机械运动学行直接装配为 `d(x')/d(v)=1`;带端挡的行继续数值差分。
- callable 内核新增 `exact_columns=(indices, provider)`:已提供精确导数的列从分组有限差分中移除,其余列仍按原始保守结构做 subset FD;原始色数、剩余色数、单次真实 FD、精确列构建及回退次数/原因都进入分段诊断。
- 精确列提供器用类型化 `ExactColumnsUnavailable` 表达当前点不可用;同一次构建会恢复原始 seed 0 的完整数值 Jacobian,避免把未知导数静默当成 0。模型编译证明失败、SciPy 私有接口不兼容或配置关闭时则直接保留原生 SciPy 路径。
- 首批目标是三条同构活塞支路的 6 个机械状态列 `(20, 21, 38, 39, 54, 55)`。编译器只有在组件类型、连接拓扑、因果赋值计划、机械等价组和 stream 影响范围都满足证明条件时才启用;该 XML 中共覆盖 34 条 reachable assignments,FD 颜色由 31 降至 25,另由提供器装配 6 列。
- 已增加 Ideal/PR 介质 `m/U/V` 物性线性化,以及 PNRP、PNCH012、PNL0001、LSTP、MECMAS 的局部切向原语;每个原语都返回 `valid/reason`,以便在非光滑接触、临界流动或不支持的模式上拒绝解析近似。
- Jacobian 内部每次 RHS 都执行取消检查;不安全的共享模型基准缓存已经撤销。随后实现的一次性 generation/dirty token 安全版本在正式 `0.81 s` 中 `253` 次 Jacobian 请求命中 `0` 次:BDF 首次构建前会做初始步长试算,后续构建前也会留下 Newton 试探状态,模型并不位于请求的基准点。该版本没有节省 RHS,最终也已删除。
- `SIMULATION_ODE_JACOBIAN_MODE=scipy` 是默认和回滚路径;小型全稠密结构或 SciPy 私有接口不兼容时也回到该路径。
显式 `SIMULATION_ODE_JACOBIAN_MODE=optimized` 仍构造完整稀疏有限差分 Jacobian,不使用 secant。最终实现严格固定 SciPy seed 0,并从原始 `1284 nnz` 保守结构生成 31 色扰动批次;移除精确行不会重新着色。曾试验的 seed 54 为 30 色,结构虽未删边,却改变了事件敏感模型的运行轨迹,因此多 seed 自动择优已经从代码中删除。
历史 30 色候选有性能收益,但没有通过事件/状态等价验收:
- 最终安全版本的 `0.81 s` 单次相邻 A/B 中,optimized 积分 `55.034 s`、总墙钟 `56.957 s`,SciPy 基线积分 `59.924 s`、总墙钟 `61.953 s`,分别约改善 `8.2% / 8.1%`。
- `0.81 s` 中 callable 实际 Jacobian RHS(扰动加基准)为 `7,103`,SciPy 估计为 `8,096`,约减少 `12.3%`;`nfev/njev/nlu` 为 `3467/228/670`,基线为 `3763/253/761`。
- 两条 `0.81 s` 轨迹具有相同结果键、采样时刻、0 次状态切换和约 `1e-16` 的最大代数残差,但最终 74 维状态的最大差异为 `51.59 × (atol + rtol·|y|)`,最差状态相对差约 `5.2e-5`,超过当前拟定的严格等价门槛。
- `2.10 s` optimized 仍成功越过 2.05 s,积分 `115.928 s`,而 SciPy 基线为 `122.180 s`;但 optimized 出现 `4` 次状态切换、`7` 次 solver 启动和 `215` 个样本,基线为 `2 / 5 / 213`。因此该候选的事件等价验收失败,不能设为默认。
- 短变体隔离显示:seed 0 callable(有或没有 4 条精确行)在 `0.01 s` 的最终 74 维状态与 SciPy 逐项一致;轨迹分叉来自 30 色 seed 54,而不是精确运动学行。这提示事件敏感模型需要更多运行中 Jacobian 漏边/弱依赖审计,不能只依赖初始点结构测试。
最终 seed 0 安全候选的正式 `0.81 s` 探针与 SciPy 基线具有相同的物理解哈希 `c6354c97...`、`3763/253/761` 的 `nfev/njev/nlu`、`1076` 个接受步、3 次 solver 启动、0 次状态切换和 `30,502` 次压力闭合。实际 Jacobian 内部 RHS 为 `7,872 + 253 = 8,125`;安全 token 缓存命中为 0。积分时间 `60.972 s`、探针总墙钟 `62.945 s`,相邻 SciPy 基线为 `59.924/61.953 s`,没有净收益并略有退化。因此安全缓存已删除,seed 0 callable 只保留为后续解析行接入与诊断基础,不进入默认路径;无需为一个已经失败收益门槛的候选继续做 `2.10 s` 性能复测。
`SIMULATION_ODE_JACOBIAN_MODE=hybrid` 另提供实验性的数值 secant 原型:最多连续复用一次,复用前执行确定性方向 Jv 审计,失败或审计无信息量会在同一次调用中完整刷新。目标模型的早期探针中候选审计普遍失败;用 seed 0 的旧完整 Jacobian 做 `0.01 s` 探针时,39 次复用审计全部失败,额外产生 39 次 Jv RHS,实际复用仍为 0。因此它目前既不是解析 Jacobian,也没有可声明的端到端收益。
**已完成的数值层工作**:
- [x] direct/stepwise BDF、Radau callable `jac` 接线;显式方法隔离。
- [x] breakpoint、事件、可恢复重启后的强制重建与分段计数。
- [x] 完整稀疏有限差分内核、严格 seed 0 着色、4 条安全精确行。
- [x] exact-columns subset FD、类型化同次完整回退和原始/剩余色数及回退诊断。
- [x] 真实 RHS/装配计数,以及 SciPy 估计口径分离。
- [x] Jacobian 内部有界取消检查;撤销不安全缓存及命中为 0 的安全 token 缓存。
- [x] 稠密结构、兼容问题和配置关闭时保留 SciPy 路径。
- [x] 最多一次复用、Jv 审计、无信息审计拒绝和失败完整刷新测试。
- [x] 复杂 XML `0.81/2.10 s` 单次性能与事件探针。
- [x] 同一代码版本完成 3 组相邻 `0.81 s` A/B,报告中位数与范围。
- [ ] 为事件敏感模型定义并通过状态、事件时刻/顺序和模式等价契约。
- [ ] 在正式锁定环境完成独立预热后的 3 次 A/B,复核中位数与离散度。
**解析/半解析后续工作**:
- [x] 为首批 Ideal/PR、PNRP、PNCH012、PNL0001、LSTP、MECMAS 路径定义带有效性诊断的局部切向契约。
- [x] 对目标三活塞 6 列沿 34 条可证明因果赋值传播导数,并从 FD 分组中排除这些列。
- [ ] 将局部导数/JVP 契约扩展到其余基础与自定义组件。
- [ ] 将因果传播推广到目标切片以外的状态列和代数计划。
- [ ] 对 stream SCC 推导显式或隐式小块导数。
- [ ] 对物性函数提供解析导数、可靠自动微分或受控局部差分接口。
- [ ] 在接触、饱和、开关和临界模式附近使用分段导数与局部回退。
- [ ] 对自定义组件缺失的导数声明生成明确诊断,不得静默置零。
- [ ] 在 `0.68–0.71`、`0.79–0.81`、事件两侧和 `2.00–2.10 s` 检查点执行稠密数值漏边审计与随机方向 JVP。
**验收条件**:
- [x] 历史 external-volume 跨域结构护栏与初始点稠密数值漏边测试继续通过。
- [x] callable 接线、分段重置、取消、显式方法隔离、secant 上限和审计失败回退有自动测试。
- [ ] `0.81/2.10 s` 的连续状态、事件时刻/顺序、模式和残差满足统一契约;当前 30 色候选未通过。
- [ ] 默认候选在锁定环境的 3 次中位墙钟有净收益,小模型无显著退化。
- [x] 首批目标切向原语和 6 列通过逐列中心差分、模式分支与局部回退验证。
- [ ] 通用组件级解析/半解析导数通过随机方向 JVP、逐列抽查和局部回退验证。
**风险与回滚**:历史 external-volume 漏边说明“颜色更少”本身不是正确性证据。不同合法颜色组合也可能暴露保守结构中未声明的弱依赖,并改变非光滑接触附近的事件序列。默认保持 `scipy`;`optimized/hybrid` 仅显式实验。非光滑点的解析或 secant 近似未必可靠,事件分段重置、审计和旧路径必须长期保留。
| 历史实验指标 | SciPy 基线 | 已撤销的 30 色候选 | 验收 |
| --- | ---: | ---: | --- |
| 保守结构 / 实际 FD 颜色 | 1284 nnz / 31 | 1284 nnz / 30(seed 54) | 结构不删边 |
| 精确装配行 | 0 | 4 条运动学行 | 短变体证明不改变轨迹 |
| `0.81 s` Jacobian RHS(含基准) | 估计 8,096 | 实际 7,103 | `-12.3%` |
| `0.81 s` `nfev/njev/nlu` | 3763 / 253 / 761 | 3467 / 228 / 670 | 工作量下降 |
| `0.81 s` 积分 / 总墙钟 | 59.924 / 61.953 s | 55.034 / 56.957 s | 单次约 `-8.2% / -8.1%` |
| `0.81 s` 最大最终状态误差尺度 | 参考 | 51.59 | 未通过 |
| `2.10 s` Jacobian RHS(含基准) | 估计 15,584 | 实际 14,556 | `-6.6%` |
| `2.10 s` `nfev/njev/nlu` | 6734 / 487 / 1507 | 6606 / 468 / 1469 | 工作量小幅下降 |
| `2.10 s` 积分时间 | 122.180 s | 115.928 s | 单次约 `-5.1%` |
| `2.10 s` 状态切换 / solver 启动 / 样本 | 2 / 5 / 213 | 4 / 7 / 215 | 未通过 |
| 最终安全候选指标(`0.81 s`) | SciPy 基线 | seed 0 callable | 验收 |
| --- | ---: | ---: | --- |
| 保守结构 / FD 颜色 | 1284 nnz / 31 | 1284 nnz / 31(seed 0) | 相同扰动批次 |
| `nfev/njev/nlu` | 3763 / 253 / 761 | 3763 / 253 / 761 | 相同 |
| 接受步 / solver 启动 / 状态切换 | 1076 / 3 / 0 | 1076 / 3 / 0 | 相同 |
| 压力闭合 | 30,502 | 30,502 | 相同 |
| 物理解哈希 | `c6354c97...` | `c6354c97...` | 通过 |
| 安全基准 RHS 缓存命中 | 不适用 | 0 / 253 | 无收益,代码已删除 |
| 积分 / 探针总墙钟 | 59.924 / 61.953 s | 60.972 / 62.945 s | 略有退化,未通过收益门槛 |
#### 2026-08-17 / 工作树基于 `6bb0591d`
- 状态:未开始 → 部分实现(数值接入层完成;30 色候选未通过事件等价,seed 0 候选未通过收益门槛;解析/半解析传播未开始)
- 代码备份:`backup/jacobian-before-20260817-6bb0591`,精确指向 `6bb0591d320d0c448ee8d224dd44127bfe3ce00f`。该分支只备份 tracked 代码基线,不包含当时未跟踪的本文档。
- 运行环境:Python 3.12.3、NumPy 2.4.6、SciPy 1.17.1;输入 SHA-256 `2fb95e65...`;`2.10 s` 仅内存覆盖停止时间,磁盘 XML 未修改。
- 正确性结果:Jacobian 内核、core solver、Generic sparsity 和 Generic XML 共 57 项通过;压力因果、stream 块、机械接触、PNRP17、代数稀疏与方程块另 52 项通过,external-volume 漏边护栏继续通过。热流体闭合计划 13 项中 12 项通过,剩余 1 项因用户已将 fixture 移至 `tests/data/fixtures/`、旧测试仍读取 `tests/fixtures/` 而报既有 `FileNotFoundError`,与本次改动无关。30 色候选在 `2.10 s` 的事件数由 2 变为 4;最终 seed 0 候选在 `0.81 s` 恢复相同物理解哈希与求解统计。
- 性能结果:见上表。数字均为同机相邻单次结果,不是 3 次中位数;最终 seed 0 候选没有减少求解工作并略慢。
- 卡死结果:历史 30 色探针在 `2.040187 s @ 106.499 s`、`2.051323 s @ 113.996 s`、`2.065299 s @ 115.472 s` 持续推进并完成到 2.10 s;默认 SciPy 的正式探针同样越过 2.05 s 并完成,无重试、无死锁。
- 安全收口:默认保持 SciPy;移除多 seed 自动择优、不安全共享缓存和零命中的安全 token 缓存;Jacobian 内部保留取消检查;无信息 Jv 审计强制刷新;显式 solver 不观察或重置 Jacobian。
- 决策:保留严格 seed 0 的 callable/诊断/精确行基础和显式实验开关;30 色、基准缓存与 secant 均不进入默认路径。下一阶段优先建立多检查点弱依赖审计和组件级局部导数,不再以颜色数或数值缓存单独作为优化成功标准。
- 证据文件:`app/simulation/solvers/jacobian.py`、`app/simulation/solvers/solver.py`、`app/simulation/systems/generic.py`、`tests/test_sparse_secant_jacobian.py`、`tests/test_core_solver.py`、`tests/test_generic_jacobian_sparsity.py`、`tests/test_generic_system_xml_simulation.py`
#### 2026-08-17 / 首批三活塞半解析 6 列切片
- 状态:部分实现 → 部分实现(首批目标切片完成并通过局部导数验证;OPT-03 的通用解析/半解析覆盖尚未完成)。
- 实现范围:新增 exact-columns subset FD 接口、类型化同次完整数值回退和分段诊断;为三条目标活塞支路编译状态列 `(20, 21, 38, 39, 54, 55)`,沿 34 条可达因果赋值传播切向量,使剩余 FD 颜色从 31 降到 25。
- 局部导数:实现 Ideal/PR 介质 `m/U/V` 物性线性化,以及 PNRP、PNCH012、PNL0001、LSTP、MECMAS 的几何、压力、质量/能量、流量/力和接触模式切向原语;原语显式报告 `valid/reason`。
- 证明与回退:组件类型、连接拓扑、因果计划、机械组和静态 stream 影响范围必须全部满足编译证明。causal/stream/custom/兼容性证明不成立时不安装 callable,继续使用原生 SciPy;运行点进入非光滑接触边界、临界流动、陈旧 primal 或其他不支持模式时抛出类型化 `ExactColumnsUnavailable`,同一次构建恢复原始 seed 0 完整数值 Jacobian。任何不可证明项都不会静默填 0。
- 配置边界:默认仍为 `SIMULATION_ODE_JACOBIAN_MODE=scipy`;首批路径仅通过 `semi-analytic` 显式 opt-in,不替换生产默认值。
- 自动测试:focused 套件 86 项、adjacent 套件 164 项,共 250 项通过。热流体 closure 计划另为 12/13 项通过;唯一失败仍是旧测试读取 `tests/fixtures/`、而 fixture 已被用户移至 `tests/data/fixtures/` 导致的既有 `FileNotFoundError`,与本轮 Jacobian 改动无关。
- 局部正确性:在平滑检查点,SciPy 分组有限差分漏掉 `J[19,20] ≈ -3201.486`;半解析列相对独立中心差分的最大相对误差为 `1.897e-8`。inactive/active 接触分支、过期 primal、非因果计划和不支持拓扑均覆盖了成功或回退路径。
- 轨迹正确性:默认容差下,两条 `0.81 s` 轨迹最差点为 `t=0.65 s` 的能量状态 `state[33]`,原始相对差 `8.24e-5`,缩放误差 `82.36`;事件数和顺序一致,但尚未满足拟定的严格逐点轨迹门槛。提高精度后互差收敛:`rtol=1e-7` 时最大绝对/相对差为 `0.081965 / 1.592e-6`,`rtol=1e-8` 时为 `0.0175357 / 3.09062e-7`,分别缩小约 `4.67× / 5.15×`,且两组事件均一致。这支持“求解路径差异随容差收敛”,但不足以把候选升为默认。
- 性能口径:`0.81 s` 已在同机、同一工作树连续完成 3 组相邻 A/B;表中时间为中位数,括号给出 3 次范围。测试使用现有 `/opt/srm-trial-review/.venv`,没有独立预热且依赖版本未由项目锁文件固定,因此仍需在正式锁定环境复核,不能单独作为切换默认值的依据。`2.10 s` 为最终 one-shot primal 捕获版本的单次复跑;此前数学路径相同的预备运行墙钟为 `111.068 s`,本次为 `116.512 s`,长程时间仍需重复测量。
| 最终 `0.81 s` 三次指标 | SciPy 基线 | `semi-analytic` 候选 | 变化/说明 |
| --- | ---: | ---: | --- |
| FD 颜色 / 精确状态列 | 31 / 0 | 25 / 6 | 目标列为 20、21、38、39、54、55 |
| `nfev/njev/nlu` | 3763 / 253 / 761 | 3650 / 228 / 711 | 求解工作下降 |
| 接受步 / solver 启动 / 状态事件 / 样本 | 1076 / 3 / 0 / 82 | 1056 / 3 / 0 / 82 | 事件和输出网格一致 |
| Jacobian RHS | 8,096(估计) | 5,985(实计) | `-26.1%` |
| 精确列构建 / 类型化回退 | 不适用 | 224 / 4 | 4 次恢复完整数值构建 |
| 压力闭合 | 30,502 | 25,672 | `-15.8%` |
| 积分时间中位数(范围) | 59.725 s(59.568–60.188) | 55.631 s(55.432–56.307) | 中位数 `-6.85%` |
| 总墙钟中位数(范围) | 61.203 s(61.070–61.704) | 56.708 s(56.508–57.410) | 中位数 `-7.34%`;逐组改善 6.96%–7.47% |
| 延长至 `2.10 s` 单次指标 | SciPy 基线 | `semi-analytic` 候选 | 变化/说明 |
| --- | ---: | ---: | --- |
| 状态 | 完成,越过 2.05 s | 完成,越过 2.05 s | 最终版本越过 2.05 s 的墙钟为 111.660 s |
| `nfev/njev/nlu` | 6734 / 487 / 1507 | 6246 / 445 / 1328 | 求解工作下降 |
| 接受步 | 1857 | 1753 | `-104` |
| solver 启动 / 状态切换 / 样本 | 5 / 2 / 213 | 5 / 2 / 213 | 事件计数和输出网格一致 |
| Jacobian RHS | 15,584(估计) | 11,771(实计) | `-24.5%` |
| 类型化回退 | 不适用 | 26 | 非平滑/不支持点恢复完整数值构建 |
| 压力闭合 | 57,601 | 48,248 | `-16.2%` |
| 积分时间 | 122.180 s | 112.825 s | 单次 `-7.7%` |
| 总墙钟 | 126.211 s | 116.512 s | 单次 `-7.7%` |
- 卡死复核:最终 `semi-analytic` 候选在墙钟 `111.660 s` 越过模拟时刻 `2.05 s`,随后于 `116.512 s` 完成到 `2.10 s`;与 SciPy 基线一样未出现无进度死锁。
- 未覆盖范围:通用 stream SCC 导数、目标三支路以外的组件/状态列、自定义组件导数契约、正式锁定环境的独立预热复测,以及 `10 s` 长时模式覆盖。
- 决策:保留首批半解析切片和自动回退作为显式实验路径;OPT-03 继续为“部分实现”,默认继续使用 SciPy。完成上述通用覆盖、严格轨迹契约和重复基准前,不切换默认值。
- 代码备份:仍使用进入 Jacobian 优化前建立的 `backup/jacobian-before-20260817-6bb0591`,精确指向 `6bb0591d320d0c448ee8d224dd44127bfe3ce00f`。
- 证据文件:`app/simulation/solvers/jacobian.py`、`app/simulation/solvers/tangent.py`、`app/simulation/solvers/solver.py`、`app/simulation/systems/generic.py`、`app/simulation/core/medium.py`、`app/simulation/components/amesim/media/mediums.py`、`app/simulation/components/amesim/mechanical/pistons.py`、`app/simulation/components/amesim/storage/chambers.py`、`app/simulation/components/amesim/flow/pipes.py`、`app/simulation/components/amesim/mechanical/translational.py`、`tests/test_sparse_secant_jacobian.py`、`tests/test_analytic_tangent_primitives.py`、`tests/test_three_piston_tangent.py`
### OPT-04 stream 拓扑传播与物性成组复用
**目标**:让无环 stream 网络一次传播,只对真正的强连通块迭代;同一状态反算的物性量成组计算和复用。
**当前状态**:stream 求解器已预绑定组件、端口和连接,物性层也有单次运行精确缓存;但每次求解仍构造临时字典/列表、重复调用连接焓计算,尚未编译 SCC/DAG。热流体外层固定点最多 25 次,本模型实测最多 3 次。
**工作项**:
- [ ] 构建 stream 图的 SCC,并将缩点图编译为拓扑顺序。
- [ ] 对单节点和无环段使用一次传播,仅在循环 SCC 内迭代。
- [ ] 使用预分配数组和原地误差统计,避免每轮临时字典/列表。
- [ ] 缓存同一求解阶段的连接焓结果,避免返回前重复计算。
- [ ] 将 `p/T/rho/h/s` 等同源物性组织为状态包,按精确输入键成组复用。
- [ ] 增加缓存命中、SCC 迭代、失效原因和物性调用次数指标。
- [ ] 评估脏标记传播,但必须证明事件和反向流切换时不会复用陈旧值。
**验收条件**:
- [ ] 无环、单环、多环、反向流和事件后拓扑测试全部通过。
- [ ] 复杂模型的最大 stream/热流体迭代不增加,残差不恶化。
- [ ] 量化减少物性调用、临时分配、压力闭合或 RHS 时间。
| 指标 | 当前 | 完成后 |
| --- | ---: | ---: |
| stream 块 / 未知量 | 9 / 192 | 待填 |
| 最大热流体迭代 | 3 | 待填 |
| `2.10 s` 压力闭合 | 57,601 | 待填 |
| 物性调用 / 缓存命中率 | 待测 | 待填 |
### OPT-05 状态缩放、分量容差和步长策略
**目标**:减少量纲差异造成的不必要小步和 Jacobian 重建,同时维持事件与守恒精度。
**当前状态**:模型中不同物理量的量级差异大。历史试验显示机械绝对容差放宽可能带来约 16% 收益,但属于精度策略变化;热流体固定点容差的简单放宽曾使表现变差,不能直接采用。
**工作项**:
- [ ] 按状态物理量、标称值和工程容差建立分量 `atol`/缩放规则。
- [ ] 为未提供标称值的组件定义安全默认值并输出诊断。
- [ ] 分开积分误差、代数残差、stream 固定点和事件定位容差。
- [ ] 统计限制步长的状态分量、误差拒步和 Jacobian 重建原因。
- [ ] 对事件前后、接触临界区和稳态区分别评估步长上限策略。
- [ ] 建立严格/标准/快速配置,但默认配置必须有明确精度契约。
**验收条件**:
- [ ] 每个配置都有状态、事件、残差和守恒误差界限。
- [ ] 标准配置在复杂模型上减少拒步或分解工作,不引入模式遗漏。
- [ ] 所有收益报告同时给出误差变化,禁止只报告墙钟。
### OPT-06 事件检测与 dense output 按需化
**目标**:避免在绝大多数没有事件候选、也不跨输出采样点的接受步上创建 dense output。
**当前状态**:已有事件候选筛选和部分非事件优化,但只要存在状态转换处理器,接受步仍可能构造 dense output。`2.10 s` 有 1857 个接受步而只有 2 次状态切换,存在减少插值构造的空间。
**工作项**:
- [ ] 在构造 dense output 前执行低成本端点符号/模式候选检查。
- [ ] 仅在跨输出采样点或存在事件候选时创建插值器。
- [ ] 将输出插值与事件定位的生命周期和精度需求分离。
- [ ] 统计候选数、误报数、定位次数、dense output 构造数和耗时。
**验收条件**:
- [ ] 同时事件、擦边事件、抖动防护和多模式顺序测试通过。
- [ ] 事件时刻误差不超契约,事件顺序和最终模式不变。
- [ ] 完整模型 dense output 构造数与耗时明显下降。
### OPT-07 输出、后处理和传输内存优化
**目标**:在长仿真中控制结果生成、JSON 编码、前端复制和峰值内存。
**当前状态**:当前模型有 1,021 个结果变量;`10 s / 0.01 s` 约产生 1,022,021 个标量。现路径会对每个样本重新闭合、追加全部结果,并把完整结果作为一个 NDJSON 消息发送。它不是本次 2.05 s 慢推进的主因,但会成为长时间运行的显著成本。
**工作项**:
- [ ] 支持结果变量白名单、分组和按需派生量。
- [ ] 将积分内部采样、结果存储采样和显示采样分离。
- [ ] 对显示路径提供服务端降采样,同时保留可选完整数据模式。
- [ ] 分块编码和传输结果,或返回 `resultId` 后分页/流式获取。
- [ ] 评估前端 TypedArray/列式数据,减少嵌套对象和重复复制。
- [ ] 避免后处理中对每个样本重复执行不必要的完整闭合。
- [ ] 记录原始标量数、编码/传输字节数、后处理时间和峰值 RSS。
**验收条件**:
- [ ] 完整输出模式保持现有 API 契约,或通过显式版本升级迁移。
- [ ] 精简模式的变量选择和降采样行为可预测、可测试。
- [ ] `10 s` 基准中后处理时间、传输字节和峰值 RSS 有量化改善。
### OPT-08 进度、取消和服务并发鲁棒性
**目标**:区分“内部慢步”和“真正无进度”,并让长任务可取消、可限流、不会拖垮服务进程。
**当前状态**:已有 stream 进度和取消检查;前端无进度阈值约 60 s。本次 2.05 s 附近可见最大间隔约 7.5 s,且中间有接受步与 CPU 活动,因此没有触发真实无进度条件。
**工作项**:
- [ ] 分别上报模拟时间、接受步、内部 RHS/闭合活动和墙钟心跳。
- [ ] 将“运行中但步很慢”与“求解器无活动”使用不同状态和超时策略。
- [ ] 在代数闭合、stream 迭代、Jacobian 构建和后处理内加入有界取消检查。
- [ ] 限制并发仿真 worker、队列长度和单任务 CPU/内存预算。
- [ ] 超时报告最后活动阶段、模拟时刻、步长和关键计数,而非只返回通用错误。
- [ ] 添加故意慢 RHS、死循环防护、客户端断连和多任务竞争测试。
**验收条件**:
- [ ] 正常慢步不会被误判为死锁,真实无活动能在约定时间内终止并给出诊断。
- [ ] 取消请求在每个主要阶段都能在有界时间内生效。
- [ ] 并发压力下服务仍能响应健康检查和新请求拒绝/排队逻辑。
### OPT-09 建立 10 s 长时验证与模式覆盖
**目标**:用实测替代“0.81 s 或 2.10 s 可以外推到 10 s”的假设。
**当前状态**:`2.10 s` 已成功;`10 s` 尚未运行和建立资源预算。模型可能在后续出现新的事件、模式、接触切换或数值尺度问题。
**工作项**:
- [ ] 在正式锁定环境运行未优化基线 `10 s`,设置心跳、资源上限和可恢复日志。
- [ ] 保存事件、模式、步长、拒步、Jacobian、闭合和内存随模拟时间的时间线。
- [ ] 为长跑设置阶段性检查点,支持定位首次偏差而非只比较终点。
- [ ] 将每项 P1 优化分别加入 `10 s` A/B,不把多个改动混成一个结果。
- [ ] 根据首次基线制定合理的 CI 频率和资源门槛。
**验收条件**:
- [ ] 连续 3 次完成 `10 s`,没有无解释回退、NaN/Inf 或资源失控。
- [ ] 全程模式、事件、关键状态和守恒量满足契约。
- [ ] 可从日志快速判断任何慢区属于积分、Jacobian、闭合、事件还是输出。
### OPT-10 明确高指数 DAE 和强非光滑系统边界
**目标**:明确当前通用求解能力的工程边界,并决定是否值得引入真正的 DAE/互补问题求解器。
**当前状态**:当前架构更适合结构明确、可唯一闭合、状态较连续的规则 index-1 类系统。超硬非光滑接触、临界抖动、近奇异代数系统、更高指数 DAE 和依赖声明不完整的自定义组件仍是薄弱点。
**工作项**:
- [ ] 建立小型基准族:刚性接触、反复开闭、近奇异闭合、尺度跨越、自定义漏依赖和 index-2/3 示例。
- [ ] 对每类系统定义“支持”“降级支持”“明确拒绝”,并给出诊断。
- [ ] 评估质量矩阵 DAE、指数约简、互补/半光滑方法与现有架构的成本。
- [ ] 只有真实模型需求和基准证明必要时,才启动通用 DAE 后端项目。
**验收条件**:
- [ ] 文档与运行时错误能明确说明能力边界,不出现静默错误。
- [ ] 若启动新后端,有独立设计、基准和迁移计划,不与普通 RHS 性能优化混合。
## 6. 统一回归矩阵
| 场景 | 结构 | 数值状态 | 事件/模式 | 回退 | 性能 | 长时内存 |
| --- | --- | --- | --- | --- | --- | --- |
| 小型线性组件 | 必测 | 必测 | 不适用 | 必测 | 冒烟 | 不适用 |
| 非线性压力/流量 | 必测 | 必测 | 可选 | 必测 | 必测 | 可选 |
| stream 无环/成环/反向流 | 必测 | 必测 | 必测 | 必测 | 必测 | 可选 |
| 接触与模式切换 | 必测 | 必测 | 必测 | 必测 | 必测 | 可选 |
| 自定义组件与漏依赖 | 必测 | 必测 | 可选 | 必测 | 可选 | 不适用 |
| 本文复杂 XML `0.81 s` | 必测 | 必测 | 必测 | 必测 | 必测 | 必测 |
| 本文复杂 XML `2.10 s` | 必测 | 必测 | 必测 | 必测 | 必测 | 必测 |
| 本文复杂 XML `10 s` | 必测 | 必测 | 必测 | 必测 | 必测 | 必测 |
当前相关回归套件包括:
- `tests/test_sparse_secant_jacobian.py`
- `tests/test_generic_jacobian_sparsity.py`
- `tests/test_pressure_flow_causal_execution.py`
- `tests/test_stream_pressure_block_solver.py`
- `tests/test_core_solver.py`
这些测试目前覆盖部分关键机制,但不能替代复杂 XML 的端到端数值和长时回归。
## 7. 单项更新模板
完成一个原型或 PR 后,在对应任务下追加以下记录:
```markdown
#### YYYY-MM-DD / <commit-or-branch>
- 状态:未开始 → 进行中 / 部分实现 → 已完成
- 实现范围:
- 未覆盖范围:
- 运行环境:
- 输入与配置:
- 正确性结果:
- 性能结果(中位数与离散度):
- 回退/审计结果:
- 风险或已知退化:
- 决策:合入默认路径 / 继续实验 / 回滚 / 不采用
- 证据文件或 CI 链接:
```
## 8. 总体更新记录
| 日期 | 代码/分支 | 任务 | 变化 | 正确性 | 性能 | 决策 |
| --- | --- | --- | --- | --- | --- | --- |
| 2026-08-17 | `6bb0591d` | 基线 | 原始 `0.81 s` 完成;内存延长 `2.10 s` 完成并越过 2.05 s | 无卡死;当前环境哈希与历史不同,待正式环境复核 | 63.779 s / 126.211 s(单次) | 建立任务清单,先完成 OPT-00 |
| 2026-08-17 | 工作树基于 `6bb0591d`;备份 `backup/jacobian-before-20260817-6bb0591` | OPT-03 | callable sparse Jacobian、真实计数、分段重置、取消、严格 seed 0 与实验 secant | 121 项相关测试通过;另 1 项既有 fixture 路径错误;30 色候选事件不等价,seed 0 候选恢复相同哈希 | 30 色历史候选有收益但不正确;seed 0 候选略慢且缓存 0 命中 | 默认 SciPy;移除多 seed/缓存;保留接入基础;解析/半解析继续后续 |
| 2026-08-17 | 工作树基于 `6bb0591d`;同一备份分支 | OPT-03 首批半解析切片 | exact-columns subset FD、类型化回退/诊断、三活塞 6 列与 34 条因果赋值;31→25 个 FD 颜色;新增 Ideal/PR、PNRP、PNCH012、PNL0001、LSTP、MECMAS 切向原语 | focused 86 + adjacent 164 = 250 项通过;closure 12/13,唯一失败为既有 fixture 路径;局部列对中心 FD 最大相对误差 `1.897e-8`;默认容差轨迹仍超严格逐点门槛,但随 rtol 收紧约 4.67×/5.15× 收敛且事件一致 | `0.81 s` 三次墙钟中位数 61.203→56.708 s,Jac RHS 8096(估计)→5985(实计);最终 `2.10 s` 单次 126.211→116.512 s,正常越过 2.05 s,事件/启动/样本均与基线一致 | 首批目标切片完成,OPT-03 总体仍部分实现;默认 SciPy,`semi-analytic` 显式 opt-in;待通用 stream/其余列、正式锁定环境独立预热和 10 s 验证 |
| 2026-08-17 | 同一 OPT-03 工作树;3 组相邻 A/B | OPT-03 重复性能复核 | 原始 `0.81 s`,每组先 SciPy 后 `semi-analytic`,运行期间无并发仿真负载 | 三组求解统计、哈希、事件和输出网格各自完全稳定;Jacobian RHS 8096(估计)→5985(实计) | 总墙钟中位数 61.203→56.708 s(`-7.34%`),积分中位数 59.725→55.631 s(`-6.85%`) | 保持显式 opt-in;仍需正式锁定环境独立预热、严格轨迹契约和 10 s 验证 |
## 9. 相关文档
- [后端求解逻辑与效率优化调研](./后端求解逻辑与效率优化调研.md)
- [仿真性能评估-2026-08-15](./仿真性能评估-2026-08-15.md)
- [文档目录说明](../README.md)