Replace Python numerical kernels with native C execution

This commit is contained in:
ljz committed 2026-09-10 01:12:18 +08:00
1 parent 48da6be21c
commit 3b38f73fe0
227 files changed
+16801 -75499

No files matched your search

@@ -0,0 +1,66 @@
# C 内核第一版实施计划与记录
> 本文记录迁移当时的阶段结果;Python 参考实现已于 2026-09-10 退役,当前入口与保留范围见 [退役记录](Python数值实现退役记录.md)。
2026-09-09。状态:第一版已完成并验证;尚未将全组件库切换为原生默认后端。
## 分步计划
| 顺序 | 工作 | 第一版交付 | 状态 |
| --- | --- | --- | --- |
| 1 | 冻结边界与模型合同 | skill-test 的组件能力表、输入与运行设置、共同 XML 前端 | 已完成 |
| 2 | 开发 C 组件内核与模型编译器 | 由 XML 拓扑生成状态、端口、计算顺序及系统 C 源码;不依赖固定组件编号 | 已完成 |
| 3 | 接入原生积分执行 | 独立 EXE,RK45 与 CVODE BDF;进度、取消、错误、结果和编译缓存 | 已完成 |
| 4 | 接入 Python 并精简重复代码 | 统一后端分派;保留模型定义、有效的 Python 参考与兼容路径,移除被替代的重复入口逻辑 | 已完成 |
| 5 | 验证第一版 | 同状态 Python/C 方程对照、拓扑通用性、API 回归、skill-test 完整求解与计时 | 已完成 |
## 本版范围
- 首批覆盖 skill-test 使用的 12 类组件:氦气介质、PNCH023、PNCH012、PNVO001、PNPL01、STEP0、UD00、FORC、PNRP17、MECMAS21、F000、LSTP00A。具体支持的参数模式由编译器检查,不将组件类型覆盖误报为所有模式覆盖。
- Python 负责 XML 校验、网络检查、代码生成、编译缓存与任务管理;生成程序中的物性、流量、机械、积分与事件处理不回调 Python。
- 用户要求本轮不调整雅可比:现有 Python 雅可比、着色与切向代码保持不变;原生 BDF 使用本机 SUNDIALS 7.4.0 默认稠密数值雅可比,不添加自定义雅可比或矩阵优化。
- `test/skill-test.json` 是前次 RK45 / maxStep=0.001 s 测试副本;下载原件是 BDF / maxStep=0.02 s。本轮分别标注两组设置,不混用时间;仿真均为 0–10 s,仅数值验证,无绘图。
- C 组件从现有 Python 方程及已验证的 C 原型迁移,核对单位、符号、事件和模式;不直接复制原型中的固定模型连接或编号。
- 本版能力以显式后端开关启用。不支持的模型在积分开始前给出明确诊断;默认 Python 路径继续服务尚未迁移的组件。
## 后续版本
1. 扩大到管路、分支节点、更多物性和摩擦/接触模式;增加通用代数环执行。
2. 完善参数与结构分离缓存、跨平台发布和更大模型验收。
3. 用户另行授权后开展雅可比、稀疏矩阵和局部导数优化。
## 实施结果
### 代码与精简内容
- 新增 `app/simulation/native_codegen/`:JSON/XML 输入适配、能力检查、模型 C 生成、构建缓存、隔离运行与 CLI。
- 新增 `native/components/`、`native/runtime/`、`native/include/`:物性/阀流量/信号/接触/端挡原语,RK45 与 CVODE BDF,事件、采样和结果输出。
- `app/main.py` 改为共用后端分派,移除入口内重复的求解器配置与 Python 系统构造。
- 将通用结果和准备错误合同从 `generic.py` 抽到 `results.py`,C 结果适配不再为获取数据类型而导入整个 Python 求解系统;原模块保留导入兼容性。
- 活动跟踪器增加原生汇总进度接口,直接接收真实 RHS/接受步计数,避免每次 C 求值都回调 Python。
- Python 组件定义、仍在使用的示例 API 和参考求解器继续保留;没有把被其他模型调用的代码误判为无用代码删除。
- 现有 Python 雅可比、着色、切向和矩阵求解逻辑未修改。
### 验证
执行 `python -m unittest tests.test_native_codegen tests.test_generic_system_xml_simulation tests.test_system_xml_v3 -q`,33 项测试全部通过。覆盖组件方程、逆向流、端挡试探、组件重命名/重排、增加独立支路、缓存、移除 Python PATH 后独立运行 EXE、取消、XML API 和既有 Python 路径。
当前 Python 的完整 0–10 s RK45 对照已重跑;175 个变量、502 个时间点及求值次数、接受步、事件数全部与 C 对应,序列比较采用 `rtol=1e-9, atol=1e-9`。所有样本有限、系统质量守恒检查通过。
首轮通用接口测试中的毫秒级心跳用例曾出现一次 Windows 时序波动;独立复核通过,最终上述完整 33 项测试也通过。未修改生产心跳行为或放宽断言。
### skill-test 求解时间
同机、预热一次后测三次取中位数,关闭轨迹采样与绘图;初始化、编译、结果投影和文件写入不计入求解时间。
| 配置 | 最大步长 | rtol | 求解中位时间 | RHS 次数 |
| --- | --- | --- | ---: | ---: |
| 原生 RK45 | 0.001 s | 1e-6 | 0.2947735 s | 60,200 |
| 原生 CVODE BDF | 0.02 s | 1e-6 | 0.0222788 s | 3,915 |
| 原生 CVODE BDF,收紧精度检查 | 0.02 s | 1e-8 | 0.0152101 s | 3,097 |
BDF 默认设置相对更细 RK45 参考的最大压力偏差约 2.38 kPa、温度偏差 0.0863 K、位移偏差 2.04e-7 m;收紧到 `rtol=1e-8` 后分别降至约 3.09 Pa、2.83e-5 K、1.72e-8 m。本例收紧误差限同时减少了求值/拒绝步,不能据此推广“越严格越快”。
第一版尚未声称覆盖全部物理模式或与 Amesim 所有曲线一致。BDF 使用另一种积分实现,不能把它的耗时与 Python RK45 相除后归因于语言。
使用说明见 [native/README.md](../../native/README.md),完整证据见 [第一版测试报告](../../test/native-v1/report.md)。
@@ -0,0 +1,63 @@
# C 内核组件库覆盖记录
> 本文记录迁移当时的阶段结果;Python 参考实现已于 2026-09-10 退役,当前入口与保留范围见 [退役记录](Python数值实现退役记录.md)。
日期:2026-09-10。范围是当前注册表中的全部模型:Amesim 库 22 类,实验库 5 类。这里的“覆盖”指项目现有模型方程有对应的 C 实现,不代表复刻完整 Siemens 元件库,也不代表任意拓扑、任意刚性模型均能快速求解。
## 实施步骤
1. 核对注册表、模型版本、全部活动端口、状态与结果变量,建立显式 C 合同白名单。
2. 补充空气物性、阀模式、管路、节点、换热、摩擦与反弹限位的 C 公式。
3. 扩展系统 C 生成:压力分组、串联阻力闭合、焓传播、常系数力/流量约束消元、刚性质量合并、兼容管路容腔状态投影。
4. 对照 Python 的状态导数和全部结果变量,执行 RK45/BDF 事件回归及实际 JSON 测试。
四步已经落实到代码。完整运行中的超时限制单独记录,不作为通过项。
## 组件清单
| 类别 | 已覆盖模型 | 主要补充 |
| --- | --- | --- |
| 介质,2 类 | Ideal Air Medium、Helium Medium | 编译期选择空气理想气体或氦气 Peng–Robinson C 物性;包括能量反解、密度、等熵因子和黏度。 |
| 气动边界,1 类 | PNPL01 | 零流量、连接侧焓传播。 |
| 信号,2 类 | STEP0、UD00 | 跳变、分段斜坡、循环与事件分段;纯信号模型也可运行。 |
| 机械,6 类 | F000、FORC、MECMAS21、LSTP00A、LMECHN1、PNRP17 | 力/速度/位移约束、可变端口节点、刚性质量合并、活塞容积与压力力。 |
| 气腔,2 类 | PNCH023、PNCH012 | 固定/可变容积、质量与内能守恒、换热与边界功。 |
| 阀,3 类 | PNOR001、PNVO001 Fixed、PNVO001 Signal | 面积/Cv/Kv 模式、限幅开度、正反向流动、近等压平滑及诊断。 |
| 管路,4 类 | PNL00R、PNL0001、PNL0002、PNL0003 | 无储能/单容腔/双容腔、摩阻与层流过渡、换热、流动诊断、兼容容腔合并。 |
| 气动节点,2 类 | PN3NODE2、P4NODE2 | 等压与质量守恒,端口 2 温度参考、平滑能量分配。 |
| 实验组件,5 类 | Cylinder、Tank、ResistivePipe、Orifice、Tee | 储能、Darcy 阻力、孔口和混合节点。 |
模型版本逐项固定于 `app/simulation/native_codegen/contracts.py`。回归检查注册表与此表完全一致;新增模型、未移植的版本和自定义子类会明确拒绝,不会因继承了一个 Python 类就被视为已移植。
## 参数模式与物理范围
- MECMAS21:`stoptype=1/2/3/4`,塑性、柔性、反弹及无限位;黏性摩擦、库仑摩擦和风阻使用现有方程。反弹使用 `restcoeff`/`restdvel`,刚性组冲击按活动端挡的最小反弹系数处理,塑性端挡优先。
- LSTP00A:接受 `stiffmode=1/2` 和两种 `discContactOption`。当前 Python 的接触力始终使用 `kcont`、`rcont`、`Pdis`,C 保持一致。几何模式参数目前未生成另一套刚度公式。
- 当前 Python 中 MECMAS21 的 `theta`、`fstick`、`strib`、`frictionType` 等参数没有对应的额外动力学分支,C 沿用其实际行为;不能把接受这些参数解释为已增加重力斜面、静摩擦锁止或完整 Stribeck 模型。
- 管路继续沿用当前 Python 的公式:PNL0003 是 Darcy 压降反解;PNL00R/0001/0002 是已有的可压缩摩擦流及过渡拟合。未更换为另一套 Amesim 库公式。
## 系统生成与运行
Python 读取/校验 XML、创建模型对象、整理连接与常量并生成 C。质量/内能初值也在预处理阶段计算。EXE 内执行物性反解、流量/焓闭合、机械力计算、状态导数、积分、事件定位和采样,不调用 Python 方程。
保留原来针对简单储能拓扑的紧凑生成路径;新增 `extended.py` 覆盖完整目录及扩展连接。两条路径都是 C。编译器不使用 Python 数值后端作为隐式备用。
兼容的 PNL0001/PNL0003 固定容腔可直接连接:初始压力/温度需一致,同组质量与内能按体积投影,导数按体积分配。独立气腔之间的无阻力直连仍按项目既有规则拒绝;无压力锚点、欠定机械约束及无法收敛的闭合也明确报错。
积分器仍为原生 RK45、CVODE BDF;没有修改 ODE 雅可比、稀疏性或线性求解策略。C 网络中的常系数连接消元属于系统组装。扩展编译器上限为 1024 状态、16384 输出;平台仍为 Windows x64。
## 验证及重要限制
回归入口:
```powershell
.venv-win/Scripts/python.exe -m unittest tests.test_native_catalog tests.test_native_codegen tests.test_simulation_warmup -v
```
27 项回归通过,包含空气/氦气、正反向流动、阀参数模式、管路换热/光滑壁、节点、串联阻力、兼容管路容腔、机械摩擦/柔性限位、接触和信号。状态导数及输出对照容限为 `rtol=2e-8, atol=2e-7`,原 skill fixture 的更严格对照仍保留。RK45/BDF 均验证了反弹解析解和纯信号运行。另补充四个质量块刚性相连的解析校验:总质量 10 kg、外力 30 N,C 得到共享加速度 3 m/s²;该测试通过。
实际 JSON、运行日志与报告存于 `test/native-catalog/`。最终构建的 `skill-test` 完成 10 s 仿真,RK45、最大步长 0.001 s、rtol=1e-7;预热后一次求解计时为 0.322429 s,进程墙钟 0.743252 s。它是功能回归期间的单次记录,不是严格性能基准。`test-mql-8` 的 132 个状态导数、1784 个输出量在初始、信号变化及保存的瞬态状态上与 Python 对照通过。
**test-mql-8 的完整 BDF 运行未通过**:`rtol=1e-7`、最大步长 0.02 s、60 s 求解超时,推进至约 0.0153307 s;记录为受控超时,不是完成 10 s 的耗时。当前 CVODE 多次重建默认数值雅可比,属于仍需处理的完整系统求解性能问题。本次保留用户要求的雅可比策略,不用降低刚度、放宽误差或更换物理方程来掩盖它。之前固定模型的 C BDF/NDF 程序与当前通用 CVODE 是不同求解实现,其既有完成时间不能直接套用到这里。
Python 参考方程暂保留以支撑这些数值对照,且模型描述、公共配置、XML/API 与旧示例仍有依赖。组件 C 覆盖完成并不等于可以整目录删除 Python。
@@ -2,6 +2,7 @@
> 文档状态:待评审实施方案
> 建立日期:2026-09-02
> 补充日期:2026-09-09;第 10 节结合最新实测,细化 XML → 系统专用 C → 原生 EXE 路线。前九节保留原阶段方案与历史评估;本次仅补充设计,未完成生产内核替换。
> 适用分支:`model-development`
> 关联文档:[求解器性能优化任务清单](求解器性能优化任务清单.md)、[后端求解逻辑与效率优化调研](后端求解逻辑与效率优化调研.md)
> 范围:后端数值执行内核;不包含前端重写,也不主张把整个 Python 后端改写为 C
@@ -404,3 +405,137 @@ Numba 可以在完整数组 IR 后用于 1–2 周的架构验证,但不作为
6. 评审通过后,再建立最小 C ABI 和纵向切片。
第一批的退出条件不是“已经写了多少 C”,而是:当前基准可信、同一计算能被无回调 IR 完整表达、差异能定位到具体阶段和槽位。只有达到这个条件,后续 C 工作才具有可预测的收益和可控的回滚成本。
## 10. 2026-09-09 补充:XML 自动生成系统专用 C / EXE
### 10.1 新证据与本次目标
用户本次希望解析 XML 后,自动生成对应系统的 C 求解程序。建议把“模型专用代码生成 + 原生积分运行”作为目标架构;开发时仍保留“相同输入逐阶段比较”和“相同 SciPy 积分器仅替换完整 RHS/Jacobian”的诊断阶段,用来分别测量执行语言与积分算法的影响。后者是验证手段,不是最终交付边界。
最新证据不能把速度差异全部归因于 Python:
- 先前特定模型的完整 C EXE 求解仍约 44.82 s;C 剖析中模型求值占 97.07%,雅可比构造占 87.17%。雅可比包含模型求值,两项不能相加。
- 该 C 版本采用 132 状态的逐列差分,每次雅可比约需 133 次完整 RHS,累计 124,089 次。这个结论针对测试 C 实现;当前 Python 引擎已经有稀疏结构、着色和部分半解析路径,不能把它也描述成始终采用全稠密差分。
- Amesim 仅关闭 optimized solver,进程 CPU 从 2.890625 s 增至 13.890625 s,结果文件字节、函数/雅可比计数、成功步数均相同。这证明执行优化层本身很重要,但没有揭示其专有优化实现。
- C 管流中的固定次数二分及频繁未收敛退出是已知热点,不能作为成熟组件内核直接复制到生产生成器。
依据:[C 剖析报告](../../test/native-mql8-profile/performance-analysis.md)、[Amesim 优化开关对照](../../test/native-mql8-profile/amesim-optimizer-comparison.json)。这是特定模型对照,不是受控的生产 Python/C 语言收益测量,也不保证新引擎达到 Amesim 的速度。
### 10.2 最终执行边界
```text
网页导出 System XML
→ Python:协议校验、组件版本/参数/连接校验
→ ModelIR:方程/可信组件原语、连续状态、代数变量、事件、依赖
→ 结构编译:合并等价变量、确定计算顺序、划分代数环、生成稀疏结构
→ C emitter:model.c + model.h + manifest.json
→ 编译并链接:组件 C 库 + 原生积分运行库
→ 独立 native worker / 模型 EXE:完成初始化、积分、事件、数值采样
→ Python:读取结果、任务状态、现有 API 编码
```
编译和 EXE 均在后端机器执行。Python 可以继续完成低频管理工作;一次积分中的 RHS、代数闭合、物性、Jacobian、Newton 和事件处理全部留在原生进程,不能在每次求值时回调 Python。
首个交付可生成模型专用 EXE,便于独立运行、复现和隔离崩溃。之后如实测编译或启动成本成为问题,再用通用 worker 加载缓存模型 DLL;DLL 同样加载在隔离进程中。原生运行时与组件库预先编译,每个新拓扑只生成和编译模型装配部分。
### 10.3 XML 不包含完整方程,必须建立 C 组件库
现行 XML 保存组件类型、版本、参数和连接,不包含各组件的物理公式。自动编译依赖一份可信映射:
```text
(model_type, model_version, 支持的结构选项)
→ 参数校验 + 状态/端口布局 + 方程或 C kernel + 局部导数 + 事件/reset
```
`app/simulation/registry.py` 的注册定义可以继续作为编译前端的数据来源,逐个增加原生能力描述。普通公式可用受限表达式 IR 表达,同时生成数值计算和导数;复杂物性与非线性管流使用经过验证的手写 C 原语,并声明完整输入输出及导数合同。不要依赖自动翻译任意 Python 类。
例如“气室 A—管道—气室 B”编译后固定为状态和参数数组下标:求两边物性、算这根管道的流量、按端口方向累加质量/能量导数。运行时不再寻找组件对象、端口名称或连接边。系统专用代码可以直接调用 `pipe_kernel(...)`,无需把物性库和积分器源码重复展开进每一个模型。
现有 `EquationResidual` 保存变量名称及已经计算出的 `value`,并没有保存完整公式表达式;`causal_ir.py` 提供部分结构基础,但其执行绑定仍有 Python 回调。因此需要补齐完整数值 IR。当前 `app/simulation/ir/` 仅发现缓存文件,不能视为已有可维护的完整 IR 源码。
`test/native-mql8-bdf/generate_model.py` 是验证原型:仍限定特定阀动作、机械组件编号、部分参数和连接形式,并拒绝某些循环闭合。正式编译器应由拓扑和能力合同推导这些内容,不能直接把这份脚本改成 XML 读入便宣称通用。
### 10.4 编译器要提前完成的计算
| 编译工作 | 运行时收益或正确性要求 |
| --- | --- |
| 合并等压、等速度等别名变量;组装流量/力平衡 | 消除多余未知量,统一端口方向与 SI 单位 |
| 对方程和未知量做匹配,将相互依赖部分划成小块 | 能顺序计算的直接计算,仅对真正代数环迭代;无法配平时定位到组件 |
| 固定状态、代数变量、参数、工作区和输出下标 | 每次求值使用连续数组,避免字典、字符串查找和临时分配 |
| 编译 stream 图及受温度影响的物理子网 | 保留现有无环传播和局部闭合能力,避免退回每轮全网重算 |
| 计算各入口的依赖集合与所有支持模式的稀疏结构 | RHS、事件、Jacobian 和输出按需计算;求 RHS 不自动附带所有输出 |
| 提取几何常量、公共表达式和物性状态包 | 参数不变的部分只初始化一次;相同精确输入下共享结果 |
| 声明事件、离散模式、缓存失效和状态复位 | 阀门跳变、流向切换、接触不依赖碰巧足够小的积分步长 |
跨积分调用的缓存必须按输入和模式有效性管理,不能仅以时间为键;同一时间可能存在多个 Newton 试探状态或雅可比扰动。试探工作区与已接受状态分开,拒绝步与事件回退不得提交试探产生的模式或缓存。
### 10.5 积分库与代数环的选择
当前引擎在 RHS 内闭合代数变量,对外给出 ODE。第一版原生全流程建议使用 **SUNDIALS CVODE 的 BDF 模式**。CVODE 提供 C 接口、1–5 阶变阶变步长 BDF,以及稠密、带状、稀疏线性求解接口。它的 Adams/BDF 由调用方选择,不能将其描述成 Amesim/LSODA 那样自动切换两种方法。[CVODE 数学方法](https://sundials.readthedocs.io/en/latest/cvode/Mathematics_link.html)
生成器负责模型方程、结构和导数,成熟积分库负责步长/阶数、误差控制和求解器历史。不要为每份 XML 重新生成一套 BDF 算法。先锁定可复现的 SUNDIALS 版本、C 接口、构建工具和依赖;Windows 用原生 C 构建链产出 EXE,Linux 对应 ELF 程序。
稀疏矩阵可配合 KLU,但先提供正确稀疏 Jacobian,再根据模型规模比较稀疏与稠密线性代数;132 状态案例的 LU 仅占约 2.19%,只换 LU 库难以改变总体耗时。[CVODE 线性求解选择](https://sundials.readthedocs.io/en/latest/cvode/Mathematics_link.html)
如果某类代数环难以可靠消去,可另行建立 `F(t, y, ydot) = 0` 的 DAE 后端,评估 C 接口的 IDA。此时必须处理代数变量、一致初始化和系统指数;高指数约束不能直接交给 IDA 期待自动解决。[IDA 官方介绍](https://sundials.readthedocs.io/en/latest/ida/Introduction_link.html)
消去代数变量后,Jacobian 不能只拼接相邻组件的直接偏导。对
```text
ydot = f(t, y, z)
0 = g(t, y, z)
```
在当前光滑分支、`g_z` 可逆时,需通过线性求解得到 `g_z * dz/dy = -g_y`,再组装 `J = f_y + f_z * dz/dy`,无需显式计算逆矩阵。这样才包含“状态变化 → 代数环重新平衡 → 导数变化”的影响。结构编译也要考虑此过程产生的新依赖。
第一版可用保守结构着色差分建立正确基线,再逐类增加局部解析导数或自动微分。旧实测的非零图和 27 色只是某次运行观察,不能直接当作全部流向/接触模式的可靠结构。迭代求解器的导数应针对其收敛方程,不宜把固定次数迭代轨迹直接当作物理方程导数。
### 10.6 项目接入点和生成产物
| 位置 | 具体接入方式 |
| --- | --- |
| `app/system_xml.py` | 复用现有校验和 `SystemXmlDocument`;补充明确的误差设置传递合同 |
| `app/main.py:compile_system_xml_network` | 抽取共同的版本、参数、介质与连接规范化前端;避免 Python/C 分别解释模型 |
| `app/simulation/solvers/causal_ir.py` 与现有分块/stream 计划 | 复用结构分析结果,转换为无 Python 回调的数值计划 |
| 建议新增 `app/simulation/native_codegen/` | 完整数值 IR、组件原生能力注册、结构编译、C emitter、编译缓存 |
| 建议新增 `native/runtime/`、`native/components/`、`native/include/` | CVODE 适配、代数求解、组件与物性原语、版本化 C ABI |
| 建议新增 `app/simulation/backends/` | Python 与原生 worker 的统一运行接口、结果适配和诊断 |
| `app/main.py:_run_system_xml_simulation_profiled` | 在现有任务/进度/取消接口内选择后端,保持结果变量标识和单位一致 |
每个模型的缓存目录建议包含:
```text
<build-key>/
model.c / model.h # 状态、端口布局和模型专用计算入口
manifest.json # 来源、版本、布局、组件映射、支持能力、构建哈希
model.exe # 链接原生运行库的可执行模型
```
每次运行另建目录,保存数值参数、运行选项、结果和诊断。模型 ABI 至少区分 `initialize`、`rhs/residual`、`jacobian`、`roots`、`apply_event`、`outputs`,通用运行库提供 `run`。RHS 返回导数,事件函数返回零点函数值,输出函数只在需要物理投影时调用。每个运行拥有独立 context,禁止共享可变全局状态。
缓存键包括规范化拓扑、组件/介质实现版本、结构参数、IR/ABI 版本、精度、平台、编译器和编译选项。普通数值参数以数组传入,时长、容差、最大步长与输出间隔由运行选项传入;这些修改通常无需重新编译。改变端口数量、状态数量或方程分支结构的参数必须触发重新编译。若把某个普通参数特化进源码,也必须将其值纳入缓存键。
生成器只输出可信原语与合法数值,用户组件 ID 通过映射表定位,不作为任意 C 源码或编译命令片段。未支持的组件、模式或算法在编译/启动阶段明确报告;强制原生测试不得静默改用 Python。
### 10.7 精度、采样与性能口径
本次检查发现两个实际差异:
- 网页入口 `app/main.py` 的 `rtol` 当前写死为 `1e-6`;此前测试 C 为 `1e-7`。XML v3 尚无 `rtol/atol` 字段,应通过明确的协议演进同时更新 XSD、解析、导出和执行设置,不可只修改某一后端。
- 当前 `tests/data/test-mql-8.xml` 仍为 `maxStep=0.001`,而此前 JSON 测试副本使用过 `1e30` 和 `0.02`。后续应由同一份规范化模型与独立运行配置驱动比较,不能用文件同名推断设置相同。
`rtol` 相同也不等于总体精度相同:需同时固定每类状态的 `atol`、单位/缩放、代数闭合容差、初始化及事件处理。首阶段保持状态定义和方程一致;改变质量/内能为压力/温度等状态形式属于单独的数值方案。
`sampleStep` 与内部积分步长分开:使用积分库的插值能力取得输出采样点,不为每个输出点重新启动求解器。已知不连续时刻仍要显式分段,状态事件要定位零点并执行复位/重启。`maxStep=1e30` 只放宽上限,不会使求解器忽略精度或事件自动迈大步。
主指标单列求解墙钟与求解 CPU;XML 解析、代码生成、冷编译、初始化、输出投影、文件写入和 HTTP 序列化分别计时。性能模式只计推进所需的求值与事件定位,不计绘图及额外结果投影;正确性模式另行采样曲线验证。积分器要求的初始 RHS/Jacobian 也计入求解工作量,避免从计时中漏掉。
### 10.8 推荐实施顺序
1. **冻结一份共同输入和运行配置。** 以 `test-mql-8.xml` 为首个完整目标,列出其全部组件类型/模式、公式与原生能力;对齐当前 Python、旧 C 原型和 Amesim 的参数、单位、状态与事件差异。
2. **完成一条闭环支路的完整 IR 和 C kernel。** 以同一 `t/y/mode/parameters` 比较初始化、物性、闭合、RHS、Jacobian 与事件左右值;覆盖正常、反向流、近零压差和接触状态。
3. **生成该支路的 C 并链接 CVODE,跑通独立 EXE。** 同时以“相同 SciPy + Python/C RHS”作为执行开销对照,分开判断 C 迁移收益与更换积分器的影响。
4. **覆盖首个完整模型,并接入现有 API 的显式原生选项。** 将当前 Python 的结构消元、stream 局部执行、事务回滚和稀疏能力迁入;优先修复管流收敛与雅可比重复求值,再考虑低占比线性代数优化。
5. **验证通用性和再逐步默认启用。** 用组件重命名、连线变化、不同支路数量、反向流和其他受支持参数模式证明生成器不依赖原测试拓扑;按现有物理验收合同检查 Amesim 投影、事件时刻和守恒残差。
首个交付应能由任意命名、符合已声明支持范围的 XML 稳定生成可运行 EXE,同时报告实际后端、求解器版本、模型/二进制哈希、计时及求值/迭代计数。本次文档没有执行这项迁移,也没有新增性能测试;提速倍率需待上述共同输入对照完成后报告。
@@ -0,0 +1,26 @@
# Python 数值实现退役记录(2026-09-10)
网页与 CLI 统一采用 C 数值内核。删除旧 Python 积分器及模型数值实现;Python 保留输入校验、模型元数据、C 生成、构建缓存、任务与结果管理。
## 删除与保留
- 删除 `app/simulation/solvers/`、`systems/generic.py`、Python 状态/物性求值、组件流量/受力/导数方法、物性缓存及对应的逐次物性计时。
- 删除旧固定算例求解器、旧 Python 基准执行脚本和依赖它们的比较脚本;保留 Amesim 结果读取器与历史数值文件。
- `config.py` 保存配置与进度,`sampling.py` 保存采样合法性检查,`results.py` 保存结果合同。
- 组件 Python 文件保留参数、端口、显示、结果、方程结构以及几何预处理。方程结构声明不再包含运行时残差值。
- 气体质量/能量初值由 C `native_medium_init()` 计算,兼容实验气瓶/贮箱原有初值定义。
- 后端默认且仅支持 `native`;`native-c` 是别名,`python` 会明确报错。旧固定算例 HTTP 接口返回 410,统一 XML 接口继续使用。
- 后端运行依赖移除 NumPy/SciPy。NumPy 仅用于回归断言,列在 `requirements-test.txt`。
- C RK45/CVODE BDF 和既有雅可比策略保持不变。无时间信号机械模型的生成代码补充未使用参数声明,修复严格编译警告导致的构建失败。
## 回归依据
删除前从旧 Python 内核捕获 `tests/data/native-python-reference.json`:50 个网络、112 组状态的初值、导数和输出。覆盖空气/氦气、正反向流、换热、节点、串联阻力、管路容腔、机械模式、信号、重命名/重排及重复支路;删除后由生成的 EXE 对照冻结值,容差 `rtol=2e-8, atol=2e-7`。另有四质量块解析校验、反弹与纯信号 RK45/BDF、XML/任务取消和接口测试。
历史 Python MQL 基准的原始 XML/JSON 移到 `tests/baselines/simulation/test_mql_8/sources/`,保留原始哈希与仿真设置,不用新内核覆盖历史结果。当前 `tests/data/test-mql-8.json` 和 XML 同步采用已选择的最大步长 0.02 s。
`skill-test` 对应的 `tests/data/native-skill-test.xml` 完成 0–10 s:RK45、最大步长 0.001 s、rtol 1e-7、不记录轨迹。预热后单次纯求解 **0.381517 s**,60224 次导数求值、10022 个接受步、14 个拒绝步。该次同时进行其他回归工作,不作为新的性能优劣结论。
当前版本仍仅提供 Windows x64 原生构建。此前已发现大型 `test-mql-8` 的 CVODE BDF 完整仿真过慢;本次删除旧实现不解决该问题,不能据组件覆盖或短时测试宣称其长时仿真已通过。详见 [组件覆盖记录](C内核组件库覆盖记录.md)。
本地生成的 EXE、DLL、计时日志在 `/test/`,不提交编译产物。源码和冻结基准进入 Git,可重新生成。
@@ -1,15 +1,15 @@
# 组件模型建模规范 v1
状态:已在 `experimental` 临时组件库实施
适用对象:人工开发者、代码生成工具和 AI 编程助手
状态:2026-09-10 更新为 Python 声明、C 数值实现
适用对象:人工开发者、代码生成工具和 AI 编程助手
配套读取规范:[组件库分类、发现与读取规范 v1](component-library-spec-v1.md)
## 1. 文档目标
本文档规定一个 Python 仿真元件应如何创建、修改、测试和注册。完成后的模型必须
本文档规定采用 Python 元数据与 C 数值内核的元件应如何创建、修改、测试和注册。完成后的模型必须
同时满足四个使用方:
1. 求解器能够实例化模型并调用方程。
1. C 编译器能够读取模型声明、生成系统代码并调用 C 方程。
2. System XML 能够根据稳定类型找到模型。
3. React Flow 能够自动显示图标、端口和参数。
4. 结果页面能够根据结构化元数据展示变量。
@@ -43,11 +43,7 @@
### 2.3 新增内部模型
仅供固定算例或研究代码使用、不进入前端目录的模型,不加入 `library.py`。这类模型
应放在对应 `examples/` 或专用系统目录,不能与公开模型混放后依赖扫描规则排除。
当前示例是
[`app/simulation/examples/testmodel/dynamic_pipe.py`](../../app/simulation/examples/testmodel/dynamic_pipe.py)。
不进入前端目录的研究模型不加入 `library.py`。需要执行时仍应编写 C 内核并显式纳入编译器支持合同;不要恢复旧 Python 求解路径。
### 2.4 新增物理域
@@ -111,7 +107,6 @@ app/simulation/components/experimental/junctions/tee.py
```python
MODEL_TYPE = "example_component"
MODEL_VERSION = "1.0.0"
PRESSURE_FLOW_DEPENDS_ON_STREAM = False
PORTS = (...)
PARAMETERS = (...)
RESULT_VARIABLES = (...)
@@ -138,62 +133,11 @@ def create(
## 6. 基类选择
### 6.1 `AlgebraicComponent`
`AlgebraicComponent` 表示没有积分状态的元件;`DynamicComponent` 表示有积分状态的元件;`ThermodynamicVolumeComponent` 提供标准 `m,U,p,T,rho,u,h` 结果声明。这些基类只描述模型,不再实现数值求值方法。
适用于没有积分状态、由当前端口变量和参数直接决定残差的元件,例如:
构造函数负责参数校验、几何预处理和端口注册。介质对象保存物性常量与模型选择。状态初值、流量、焓、受力和导数必须在 C 中计算。
- 孔板
- 阀门
- 阻性管段
- 理想三通
至少实现:
- 构造函数和端口注册。
- `create()`。
- `pressure_flow_equation_residuals()`。
- 需要传递 stream 变量时实现 `update_stream_outflows()`。
实现 `update_stream_outflows()` 或 `update_flow_temperature_references()` 的公开模型,
还应在该公开类自身显式声明 `PRESSURE_FLOW_DEPENDS_ON_STREAM`:构成压力/流量方程
会读取这些 hook 写入的焓或温度引用时设为 `True`,否则设为 `False`。省略声明、
声明非法值或由自定义子类仅继承父类声明时,求解器会保守使用全网热流闭合;不要
为了获得分块加速而错误声明 `False`。
`pressure_flow_equation_residuals()` 返回的每条 `EquationResidual.variables` 必须完整
列出该残差实际读取的全部代数端口量(`p/m_flow/x/v/f`),不能只写“主要变量”。
求解器会用这份声明编译稀疏 Jacobian 和独立方程块;漏写依赖可能让有限差分方向
不完整。仓库内置组件会接受结构与数值依赖回归,外部自定义组件当前仍保守使用
全网 dense 回退,直到具备同等的依赖验证边界。
### 6.2 `ThermodynamicVolumeComponent`
适用于包含质量和能量状态的气体容腔,例如:
- 气瓶
- 贮箱
- 有容积的管段
至少实现:
- `get_state_vector()`。
- `set_state_vector()`。
- `refresh_thermodynamic_ports()`。
- `state_derivative_from_ports()`。
- `pressure_flow_equation_residuals()`。
该基类已经提供标准热力学组件结果:
```text
m, U, p, T, rho, u, h
```
除非物理含义不同,不要重新复制这组结果声明。
### 6.3 其他基类
如果现有基类不能表达模型,应先评估是否缺少一种通用组件能力。不要为了一个模型
直接把专用判断塞入 `SimulationNetwork` 或求解器。
用 `EQUATIONS` 或 `equation_definitions()` 声明结构化连接约束,返回 `EquationDefinition`,包括关系、变量和归属,不包含运行时残差值。`__MODEL__` 占位符由基类替换为实例名。该声明供网络结构展示与检查使用,不替代 C 方程或构建支持白名单。
## 7. 端口建模规范
@@ -319,16 +263,7 @@ ResultVariableDefinition(
)
```
声明后必须在 `component_result_values()` 返回同名值:
```python
def component_result_values(self) -> Mapping[str, float]:
return {
"pressure_drop": self.port_a.p - self.port_b.p,
}
```
声明集合和返回键必须一致。
声明的每个输出必须在 C 生成器的输出布局中有对应值。测试应核对实际 EXE 输出键与 `result_variable_metadata()` 一致,不再实现 Python `component_result_values()`。
### 9.2 端口结果
@@ -407,168 +342,20 @@ def create(
`create()` 不应重复实现参数默认值和边界校验,也不能静默修改传入参数。
## 12. 方程实现要求
## 12. C 方程实现要求
模型方程必须满足:
1. 在 `native/components/kernels.c` 及 `native/include/kernels.h` 实现物性或元件数值公式。
2. 在 `native_codegen/extended.py` 注册状态、端口、参数、初始化与输出映射;符合简单拓扑的模型还应核对 `compiler.py` 快速路径。
3. 在 `native_codegen/contracts.py` 声明支持版本,不允许仅注册 Python 模型就声称具备 C 求解能力。
4. 当前状态与试探状态分离,求值不能覆盖已接受状态。无效物性、欠定连接和不收敛必须明确失败。
5. 信号跳变和限位事件接入 C 运行库;不能改动刚度、阻尼或容差来隐藏数值错误。
6. 保持 SI 单位和端口流入为正,核对逆流、质量/能量守恒及边界状态。
- 残差形式统一为“期望等式左侧减右侧”。
- 每条 `EquationResidual` 使用稳定、可定位的 `id`。
- `variables` 列出该残差实际涉及的端口量或状态。
- `role` 与方程主要约束的物理角色一致。
- 对零压差、零流量和反向流动给出有限结果。
- 必要正则化必须有物理解释,并通过边界测试保护。
- 不得用画布坐标、连接线方向或组件名称决定方程。
## 13. 模型实现示例
### 12.1 可因果执行的残差语义
可参照 [气瓶声明](../../app/simulation/components/experimental/storage/cylinder.py)、[气腔声明](../../app/simulation/components/amesim/storage/chambers.py)、[C 内核](../../native/components/kernels.c) 与 [系统 C 生成器](../../app/simulation/native_codegen/extended.py)。完整开发顺序见 [组件目录说明](../../app/simulation/components/example.md)。
后端只会对经过结构门控的内置模型启用完全因果执行。除完整声明
`variables` 外,这些模型还必须遵守以下可执行语义:
- `role="effort", relation="state"` 的残差写成
`端口 effort - 状态给定值`,被约束的端口量系数必须为 `+1`。
- `role="effort", relation="equal"` 的残差写成两个同类 effort 的差。
- `role="flow", relation="sumToZero"` 按“流入组件为正”的约定求和,待消元
flow 的系数必须为 `+1`。
- `role="flow", relation="constitutive"` 写成
`待消元 flow - 本构计算值`,待消元 flow 的系数必须为 `+1`。
- `pressure_flow_equation_values()` 必须是无副作用的只读计算,返回顺序和长度
必须与 `pressure_flow_equation_residuals()` 的编译结果永久一致;不得在求残差时
修改端口、状态或活动集缓存。
求解器仍会在首次闭合、离散事件之后和固定周期执行完整残差审计。结构不满足、
运行时覆盖不完整或审计不通过时,会立即熔断到原有残差/非线性求解路径。现场诊断
时可在启动进程前设置 `SIMULATION_CAUSAL_FAST_PATH=0`,一键关闭该优化而不改变模型
文件。
动态模型还必须:
- 状态向量长度稳定。
- `get_state_vector()` 和 `set_state_vector()` 互为逆操作。
- 状态导数满足质量和能量守恒约定。
- 初始化默认值能够产生有限介质状态。
## 13. 可复制的代数模型模板
下面是一个符合当前规范的两端口代数阻力模板。复制后必须根据真实物理模型修改
类型、参数、方程、名称和测试,不能只改类名就注册。
```python
from __future__ import annotations
from collections.abc import Mapping
from math import sqrt
from app.simulation.core.base import AlgebraicComponent
from app.simulation.core.catalog import ComponentDisplaySpec, PortDisplaySpec
from app.simulation.core.equations import EquationResidual
from app.simulation.core.metadata import ParameterDefinition
from app.simulation.core.medium import IdealGasMedium
from app.simulation.core.ports import PortDefinition
class ExampleRestriction(AlgebraicComponent):
MODEL_TYPE = "example_restriction"
MODEL_VERSION = "1.0.0"
PRESSURE_FLOW_DEPENDS_ON_STREAM = False
PORTS = (
PortDefinition.pneumatic("port_a", nominal_role="bidirectional"),
PortDefinition.pneumatic("port_b", nominal_role="bidirectional"),
)
PARAMETERS = (
ParameterDefinition(
name="K",
label="流量系数",
quantity="flow_coefficient",
unit="kg/(s*Pa^0.5)",
default=1e-5,
minimum=0.0,
),
)
RESULT_VARIABLES = ()
DISPLAY = ComponentDisplaySpec(
label="示例阻力元件",
library_id="experimental",
category_id="flow",
symbol="generic",
ports=(
PortDisplaySpec("port_a", "left", order=10),
PortDisplaySpec("port_b", "right", order=20),
),
order=90,
)
def __init__(self, name: str, K: float = 1e-5) -> None:
super().__init__(name)
self.set_parameter_values({"K": K})
self.K = K
self.port_a = self.register_declared_port("port_a")
self.port_b = self.register_declared_port("port_b")
@classmethod
def create(
cls,
*,
name: str,
medium: IdealGasMedium,
parameters: Mapping[str, float],
) -> ExampleRestriction:
return cls(name=name, K=parameters["K"])
def pressure_flow_equation_residuals(
self,
) -> tuple[EquationResidual, ...]:
pressure_difference = self.port_a.p - self.port_b.p
expected_flow = (
self.K
* sqrt(abs(pressure_difference))
* (1.0 if pressure_difference > 0.0 else -1.0)
if pressure_difference != 0.0
else 0.0
)
return (
EquationResidual(
id=f"{self.name}:mass_flow_balance",
owner="component",
owner_id=self.name,
relation="sumToZero",
variables=(
f"{self.name}.port_a.m_flow",
f"{self.name}.port_b.m_flow",
),
role="flow",
value=self.port_a.m_flow + self.port_b.m_flow,
),
EquationResidual(
id=f"{self.name}:pressure_flow_relation",
owner="component",
owner_id=self.name,
relation="constitutive",
variables=(
f"{self.name}.port_a.p",
f"{self.name}.port_b.p",
f"{self.name}.port_a.m_flow",
),
role="flow",
value=self.port_a.m_flow - expected_flow,
),
)
def update_stream_outflows(
self,
connected_h: Mapping[str, float],
) -> None:
self.port_a.h_outflow = connected_h["port_b"]
self.port_b.h_outflow = connected_h["port_a"]
```
真实现有模型可参考:
- 储能元件:
[`cylinder.py`](../../app/simulation/components/experimental/storage/cylinder.py)
- 阻性元件:
[`orifice.py`](../../app/simulation/components/experimental/flow/orifice.py)
- 多端口连接元件:
[`tee.py`](../../app/simulation/components/experimental/junctions/tee.py)
Python `create()` 只创建经校验的描述对象;C `model_init()` 生成质量、能量及机械状态,`model_eval()` 计算导数和输出。两者通过生成的状态/参数布局关联。
## 14. 注册模型
@@ -597,11 +384,11 @@ models=(
2. 默认参数创建测试。
3. 参数边界测试。
4. 端口与显示布局一致性测试。
5. 关键方程残差测试。
5. C 方程与独立解析解或冻结参考值对照。
6. 零流量或反向流动测试。
7. 目录输出测试。
8. 最小 XML 编译测试。
9. 能进入通用求解器的模型,再添加短时仿真测试。
9. 使用 C RK45/BDF 的短时仿真与事件测试。
推荐先运行:
@@ -0,0 +1,23 @@
# 更新日志 2026-09-09
## 21:37
- 完成第一版 C 数值后端:由规范化 XML 自动生成模型 C 代码,链接独立 RK45/CVODE BDF 程序,支持 skill-test 使用的 12 类组件及已声明模式。
- 接入现有 XML 仿真 API、编译缓存、独立进程、进度、取消与错误结果;共用 Python 后端入口和结果合同,保留仍在使用的 Python 参考及其他模型。未调整现有雅可比算法。
- 33 项相关测试通过。skill-test 的 RK45 全量结果与当前 Python 严格对照通过;三次纯求解中位时间为 RK45 0.2948 s、BDF 0.02228 s。BDF 收紧误差限后的收敛检查通过,详细偏差和限制见第一版报告。
- 程序、生成源码、验证数据与报告保存在 `test/native-v1/`;新增使用说明和分步实施记录。
## 22:19
- 在当前网页真实复现并修复 C 仿真完成后读取缺失 `pressureFlow.maxScaledResidual` 的错误。C 结果补齐采样点数和状态数,前端兼容可选的 Python 诊断以及旧 C 后端缺少的计数字段;结果可以保存和刷新恢复。
- 确认用户改后的默认后端实际为 `native-c`。3 项浏览器回归、1 项原生 API 诊断回归、TypeScript 检查及前端生产构建通过。
- 完成 skill-test 的 EXE、HTTP、生产网页及当前开发网页对照:两种方法共 96 次运行(含预热),完整数值结果逐点一致;5 次中位数中,RK45 纯求解 0.3196 s、完整 EXE 0.6326 s、当前网页 1.1846 s;BDF 分别为 0.02206 s、0.3673 s、0.8043 s。
- 另以固定结果流回放隔离进度交互开销:10 组配对中位数约增加 189.6 ms 的浏览器主线程任务时间,主要落在开发构建下工作台与组件库的重复 JSX 创建。该时间与后台执行重叠,不能直接作为额外等待时间相加。
- 详细记录和复现脚本位于 `test/native-web-20260909/`。本次未变更求解器、雅可比或进度交互性能结构。
## 默认 C 启动与 Python 依赖核对
- 统一默认内核选择,后端脚本默认设为 `native`,启动日志明确显示所选内核;显式 `python` 覆盖仍用于旧模型和数值对照。
- 启动预热按内核分支执行:C 模式检查工具链与 XML Schema,不再执行 Python/SciPy 数值预热;工具链失败不会静默回退。
- 10 项启动/默认 API 回归测试通过。额外在全新进程中移除内核环境变量,验证真实应用 lifespan 与 skill-test XML 执行:默认 native、未加载 scipy.integrate,成功计算至 10 s,502 采样点,12 状态。记录位于 `test/native-default-startup/result.json`。
- 核对并记录 Python 保留范围:C 编译仍依赖组件对象、注册表、公共配置与 XML 采样校验,旧示例和未覆盖模型仍依赖 Python 数值实现,未整批删除现有文件。
@@ -0,0 +1,15 @@
# 更新日志 2026-09-10
- 完成当前注册组件库的 C 方程覆盖:22 类 Amesim 公开组件(含两种介质定义)和 5 类实验组件,新增显式模型版本白名单。
- 补充空气物性、管路阻力/换热、阀参数模式、气动和机械节点、摩擦、柔性及反弹限位;扩展串联压力闭合、焓传播、刚性质量合并及兼容管路容腔投影。
- 数值循环全部位于生成的 C 程序中;保留原简单拓扑的紧凑生成路径、默认 C 启动及 Python 参考后端。未调整 ODE 雅可比策略。
- 27 项回归通过,另增加的四质量块合并解析校验通过。`skill-test` 完成 10 s,最终 RK45 回归的单次求解时间 0.322429 s;EXE 与记录位于 `test/native-catalog/skill-final-rk45/`。
- `test-mql-8` 在初始和瞬态状态下的 132 个导数、1784 个输出通过 Python 对照;完整 CVODE BDF 在 60 s 限额内仅推进至 0.0153307 s,明确记录为超时,完整运行性能仍待改善。
- 详细范围、已有参数的实际物理行为与连接限制见 `docs/other/C内核组件库覆盖记录.md`。
## 随后完成:退役旧 Python 数值实现
- 删除旧 Python 积分器、模型数值方法、物性缓存和固定算例求解器;Python 层保留配置、参数、校验、编译、进度与结果合同,C 同时负责状态初值。
- 固化 50 个网络、112 组 Python 参考状态,删除后用独立 EXE 回归;补充无需 NumPy/SciPy 导入的后台与代码生成检查。
- 原生 RK45 完成 `skill-test` 10 s,最大步长 0.001 s、rtol 1e-7,预热后纯求解 0.381517 s;前端生产构建通过。
- 当前生效范围与限制以 `docs/other/Python数值实现退役记录.md` 为准,前面的 Python 参考后端描述属于当日较早阶段。