优化 Jacobian 确定性复用并补充性能剖析与平台依赖文档

在单次 Jacobian 构建内按完整输入精确复用储气物性、PH 反算、密度和管路求根结果,保持原有求值副作用、差分政策与失败回退。八路模型求解 CPU 中位数减少 19.27%,循环和不循环的完整原始采样均与恢复基线一致。

增加独立的跨平台时间剖析工具,记录互斥阶段耗时、Newton/LU 统计、矩阵复用与内核复用,保存 UD00 两种工况的调查报告和机器可读汇总。

补充 Windows/Linux 运行、测试、原生编译和剖析所需依赖文档及索引,不修改依赖清单、版本锁或安装环境。

验证:8 项新增专项回归通过;2270 次完整 Jacobian 核对零差异;16 次剖析配对及预热运行保持完整数值一致。既有固定样本哈希失败和 Linux 实机验收限制见报告。
This commit is contained in:
ljz committed 2026-09-16 13:39:53 +08:00
1 parent 2b07d996cf
commit 1aac220084
26 files changed
+2293 -13

No files matched your search

@@ -0,0 +1,105 @@
# Jacobian 确定性复用与 UD00 验收
日期:2026-09-15。
## 结论与版本
已将方向一的工作区改动(含未跟踪源文件)保存到 Git stash,再从干净基线独立实施方向二。方向二在当前八路模型上将求解 CPU 时间减少 **19.27%**,完整原生工作进程耗时减少 **17.56%**。循环和不循环的完整原始采样均与基线逐字节一致。
- 方向一备份:`stash@{0}`,固定对象 `06b161c309644bb01aa402ff79f34749ff8e889a`。
- 备份名称:`codex-direction-one-purpose-evaluation-before-jacobian-2026-09-15`。
- 恢复后的基线:`2b07d996cff295d9f401dd32edd10472238bdf2d`,分支 `system-optimization`。
- 本轮修改保留在工作区,尚未提交;未重新应用方向一。原有忽略目录中的验收数据仍保留。
- 所有“优化前”均指上述恢复基线。交付核对确认两种优化后构建的 36 个源文件哈希、输入哈希及重新生成的 `model.c` / `model.h` 与实测冻结版本一致。
## 实施范围与通用性
在一次 Jacobian 构建中记录未扰动基准的确定性结果。分组扰动后,完整输入的浮点位全部一致才复用;输入变化时调用原始计算。每次 Jacobian 请求重新清空工作区。
覆盖储气物性、压力—焓温度反算、密度,以及管路标量求根。保留原有物性缓存的查询/填充顺序、温度观察、数值有效性检查和失败行为。普通 RHS、接受步检查、输出计算继续使用原路径,原有着色、差分步长和 Dense LU 不变。
复用依据实际物理输入与完整介质参数,不依赖模型实例名称、支路数或固定拓扑。使用已有内核的新模型自动接入,无需增加注册参数或维护另一套方程;新增底层内核需要审查完整输入和副作用后才能加入复用。
工作区使用堆内存。标量缓存为 1024 槽、每次至多探测 32 槽;内存不足、表满或未命中时执行原计算。分组计算失败时沿用原有未缓存逐列差分恢复。独立矩阵核对发现差异时采用参考矩阵并关闭后续着色/复用。
本轮未引入解析 Jacobian 或自动微分。现有切换、限位及隐式求根需要先明确导数政策,再评估这些方法。实现细节见 [技术说明](../standard/native-jacobian-reuse.md)。
## 正式性能对比
环境为同一台 Windows 11 机器、CVODE 7.4 BDF。输入 `tests/data/test-mql-8-corrected.json`,132 个状态、1784 个输出,两个 UD00 均不循环。仿真 0–10 s,采样间隔 0.01 s,rtol 为 1e-8,保持原有 atol 和求解设置。
编译及重型测试结束后,每个版本预热一次,排除预热,交替串行测量三次,报告中位数。完整工作进程计时包含原始数据写盘、输出回放和 JSON 写出,排除编译、API 及浏览器。带矩阵核对的运行不参与计时统计。
| 指标 | 优化前 | 优化后 | 耗时减少 |
|---|---:|---:|---:|
| 求解 CPU | 7.296875 s | 5.890625 s | 19.27% |
| 求解实际耗时 | 7.242260 s | 5.876553 s | 18.86% |
| 完整原生工作进程 | 7.852573 s | 6.473363 s | 17.56% |
六次正式运行的完整状态文件、输出文件、最终值、物性告警及原有求解计数全部相同:1002 个采样,nfev 44467,接受步 10840,拒绝步 918,Jacobian 896,LU 3106,状态转换 1,求解器启动 4。nfev 保持不变;收益来自每次求值内部减少了昂贵计算。
该工况的 Jacobian 优化路径计数如下,范围仅为基准/分组探针,不代表整个求解器的计算量:
| 内核 | 实际计算 | 复用 |
|---|---:|---:|
| 储气物性 | 150568 | 1254360 |
| 压力—焓温度反算 | 85283 | 601820 |
| 密度 | 440295 | 2479392 |
| 管路求根 | 196345 | 884176 |
原生可执行文件由 400314 字节增至 411613 字节。构建缓存状态未配平,不据此报告冷编译性能变化。以上性能收益限于当前机器及工况,不推断所有模型的加速比例。
## UD00 循环与不循环
按实际项目 JSON 修改两个 UD00 的 `iscyclic`,通过正常生成器分别构建。每个版本、每种工况运行一次 0–21.7 s,覆盖 10.8 s 和 21.6 s 的周期边界。
| 两个 UD00 的配置 | 采样数 | 优化前 CPU | 优化后 CPU | 全部原始数据及原有计数 |
|---|---:|---:|---:|---|
| 均不循环 | 2172 | 8.093750 s | 6.453125 s | 完全一致 |
| 均循环 | 2175 | 10.796875 s | 8.750000 s | 完全一致 |
这些时间是单次工况观察,正式性能结论使用上一节的三次中位数。
- 不循环:nfev 50092,接受步 11006,拒绝步 884,Jacobian 1073,LU 3287,状态转换 1,求解器启动 5。
- 循环:nfev 66495,接受步 15731,拒绝步 1334,Jacobian 1374,LU 4700,状态转换 4,求解器启动 10。
- 完整比较包含所有状态和输出采样、最终状态/输出、完整物性告警和上述计数;并非只比较曲线外观或最终值。本轮没有改变 UD00 信号语义。
## 独立 Jacobian 验证
`--verify-jacobian` 使用未缓存的 canonical 逐列差分,核对矩阵每个元素。
| 工况 | 核对矩阵数 | 不匹配数 | 与对应普通运行的数值结果 |
|---|---:|---:|---|
| 不循环,0–10 s | 896 | 0 | 完全一致 |
| 循环,0–21.7 s | 1374 | 0 | 完全一致 |
共 **2270 次矩阵核对,零差异**。核对运行的 nfev 增量分别严格等于 `896 × 132` 与 `1374 × 132`;原步数、事件、完整采样及复用计数不变。这避免把重复调用缓存路径误当作独立验证。
## 回归与验收限制
新增内核/生成器测试覆盖精确输入键、各介质参数的 1 ULP 变化、正负零、失败、冲突、容量耗尽、记录关闭、观察与缓存副作用、反向管流和活动容积。测试理想气体和氦气、热状态和多变储气;全模型 RHS/输出在所有状态逐项扰动及非有限输入下与原路径逐位对照。
新增运行时测试使用 SUNDIALS 公共类型编译生产 `cvode_solver.c`,覆盖基准重置、原始差分步长、无可选工作区、分组失败恢复、取消、故意破坏复用结果后由独立核对捕获并停用优化。
相关回归包括物性、管路、生成器、依赖排序、守恒、告警、分块存储、摩擦及 Jacobian。unittest 汇总为 **Ran 52 tests,FAILED (errors=1, skipped=1)**,已实际执行的用例通过,但不能称全量验收通过:
1. `NativeJacobianProbeTests.setUpClass`:旧固定状态样本的输入 SHA 与当前物理输入不符。当前输入为 `60ed16c71b72be4ae71ca7980f4f8cdcdf6b4c4c4c8950863c2390960b71ced4`,样本要求 `670977bef67e62d9c66e8af497bada208bd72a7301be45128d185d47282cf288`。这是基线已有问题,输入及样本均未在本轮修改,也未更新样本以绕过保护;已排除换行差异。
2. `NativeJacobianRuntimeTests`:旧测试使用 Linux SUNDIALS 7.4 私有 ABI/静态库,在 Windows 跳过。新增公共类型运行时测试在 Windows 通过,但不替代真实 Linux 验收。
当前没有可用的 Linux 容器或 WSL 运行环境,未完成 Linux 编译/运行验收;本轮也未重新执行完整 Amesim 对照或浏览器端全流程。数值一致结论限定为同平台恢复基线与方向二的前后比较。
## 复现与证据
- 比较工具:[benchmark_native_jacobian_reuse.py](../../tests/manual/benchmark_native_jacobian_reuse.py)。`prepare` 冻结对应源码构建,`run` 保存单次测量及原始数据,`report` 断言全部对比项。
- 本地证据目录:`test/jacobian-reuse-20260915/`(项目忽略的验收数据目录,不随源码提交)。
- `comparison.json`:六次正式测量、两种 UD00 工况、矩阵验证和数值一致性断言。
- `before/`、`after/`、`before-cyclic/`、`after-cyclic/`:冻结构建、输入/源文件哈希与构建清单。
- 各运行子目录:`states.bin`、`outputs.bin`、`result.json`、`measurement.json`、日志。
- `regression.log`:相关回归完整输出;`delivery-verification.json`:交付源文件与实测生成文件的一致性核对。
| 构建 | buildKey |
|---|---|
| 基线,不循环 | `80cf19d21e41cb5662f934232c7a2c5302eb7fc5b91f82da3cb23f14e3f9b885` |
| 方向二,不循环 | `7fe268c97557c3e1c5a511ea5e28cf0f0479a087031abe1a38d7007dde3c4ca0` |
| 基线,循环 | `a74d52ffe2daef7ccba5030dec75209f96d39f1bc7b47436ba5351fab08a3a92` |
| 方向二,循环 | `a50f302b77a49d1aa28a4ee305239a36e8c9281ecdc93756b729183b8f1e0a0a` |
@@ -0,0 +1,464 @@
{
"scope": "Native worker: model initialization through result encoding and cleanup. Excludes build, process launch, API and browser. Error estimation remains inside CVODE controller. Dense LU has no symbolic factorization. Explicit Newton uses the same library implementation.",
"cases": [
{
"name": "noncyclic",
"meanTotalSeconds": 7.6562506666667405,
"phases": {
"residual_evaluation": {
"seconds": 2.0878067666756883,
"percent": 27.269310496395295
},
"jacobian_assembly": {
"seconds": 2.739998700024747,
"percent": 35.78773500656092
},
"numerical_factorization": {
"seconds": 0.23652539999102373,
"percent": 3.089311077820268
},
"linear_solve": {
"seconds": 0.17955199999232718,
"percent": 2.3451687752864254
},
"linear_system_setup_other": {
"seconds": 0.10677826666005785,
"percent": 1.3946547900389645
},
"newton_overhead": {
"seconds": 0.012538333336427362,
"percent": 0.16376597217507388
},
"cvode_controller_including_error_estimation": {
"seconds": 0.021977399989888607,
"percent": 0.28705172997499034
},
"event_detection": {
"seconds": 0.001956633334733245,
"percent": 0.025556025003882137
},
"accepted_property_checks": {
"seconds": 1.1713370666645158,
"percent": 15.29909504875804
},
"sampling_and_storage": {
"seconds": 0.0096981666655059,
"percent": 0.12666992092786503
},
"output_replay": {
"seconds": 0.2987757666667979,
"percent": 3.9023770207470787
},
"result_encoding": {
"seconds": 0.777298566666559,
"percent": 10.152470190803813
},
"poll_and_progress": {
"seconds": 0.006268433334904937,
"percent": 0.0818734078573996
},
"framework_other": {
"seconds": 0.005739166663564295,
"percent": 0.0749605376499895
}
},
"meanScopes": {
"total": {
"inclusiveSeconds": 7.6562506666667405,
"exclusiveSeconds": 0.00013766666703910838
},
"integration": {
"inclusiveSeconds": 6.578948899999887,
"exclusiveSeconds": 0.005601499996525187
},
"cvode_controller": {
"inclusiveSeconds": 5.389218199991167,
"exclusiveSeconds": 0.021977399989888607
},
"rhs": {
"inclusiveSeconds": 2.0841626000111924,
"exclusiveSeconds": 2.082771766674341
},
"nonlinear_residual": {
"inclusiveSeconds": 2.0881416333457614,
"exclusiveSeconds": 0.005035000001347119
},
"jacobian": {
"inclusiveSeconds": 2.7426492000089033,
"exclusiveSeconds": 0.02242556667806639
},
"jacobian_probe": {
"inclusiveSeconds": 2.720223633330837,
"exclusiveSeconds": 2.7175731333466806
},
"linear_setup": {
"inclusiveSeconds": 3.085952866659985,
"exclusiveSeconds": 0.10677826666005785
},
"numerical_factorization": {
"inclusiveSeconds": 0.23652539999102373,
"exclusiveSeconds": 0.23652539999102373
},
"linear_solve": {
"inclusiveSeconds": 0.17955199999232718,
"exclusiveSeconds": 0.17955199999232718
},
"newton": {
"inclusiveSeconds": 5.366184833334501,
"exclusiveSeconds": 0.012538333336427362
},
"event_detection": {
"inclusiveSeconds": 1.181901733331415,
"exclusiveSeconds": 0.001956633334733245
},
"accepted_property_checks": {
"inclusiveSeconds": 1.1713370666645158,
"exclusiveSeconds": 1.1713370666645158
},
"sampling": {
"inclusiveSeconds": 0.00887103333207051,
"exclusiveSeconds": 0.0008239000012508768
},
"append": {
"inclusiveSeconds": 0.008658133330906518,
"exclusiveSeconds": 0.000971733330970892
},
"flush": {
"inclusiveSeconds": 0.007902533333284131,
"exclusiveSeconds": 0.007902533333284131
},
"output_replay": {
"inclusiveSeconds": 0.2987757666667979,
"exclusiveSeconds": 0.2987757666667979
},
"result_encoding": {
"inclusiveSeconds": 1.076074333333357,
"exclusiveSeconds": 0.777298566666559
},
"poll": {
"inclusiveSeconds": 0.006268433334904937,
"exclusiveSeconds": 0.006268433334904937
}
},
"symbolic_factorization": {
"seconds": 0,
"status": "not applicable: dense LU"
},
"error_estimation": {
"seconds": null,
"status": "included in cvode_controller_including_error_estimation"
},
"counters": {
"accepted_steps": 11006,
"residual_evaluations": 20048,
"linear_rhs_evaluations": 0,
"jacobian_evaluations": 1073,
"linear_setups": 3287,
"error_test_failures": 884,
"newton_iterations": 20037,
"newton_failures": 978,
"nonlinear_step_failures": 179,
"counter_segments": 5,
"jacobian_reuses": 2214,
"jacobian_refresh_setups": 1073,
"linear_setup_failures": 0,
"LU_factorization_failures": 0,
"LU_solve_failures": 0,
"event_model_evaluations": 0,
"scheduled_boundary_count": 3,
"rejected_steps": 1063,
"application_accepted_steps": 11006,
"same_time_returns": 0,
"max_same_time_streak": 0,
"nonlinear_residual_calls": 20037,
"jacobian_probe_evaluations": 30044,
"LU_factorizations": 3287,
"LU_solves": 20037,
"newton_solve_calls": 12069,
"event_count": 1,
"event_detection_calls": 11006,
"solver_starts": 5,
"all_counted_model_evaluations": 50092,
"accepted_property_checks": 11007,
"sample_count": 2172,
"jacobian_kernel_reuse": {
"gasEvaluations": 180304,
"gasReuses": 1502160,
"phEvaluations": 105403,
"phReuses": 732566,
"densityEvaluations": 531697,
"densityReuses": 2979837,
"pipeEvaluations": 235272,
"pipeReuses": 1063254
}
},
"medians": {
"control": {
"solveSeconds": 6.583620599999904,
"solveCpuSeconds": 6.59375,
"processWallSeconds": 7.7777962999998635
},
"profiled": {
"solveSeconds": 6.638836199999787,
"solveCpuSeconds": 6.6875,
"processWallSeconds": 7.830034699999942
}
},
"observedOverheadPercent": {
"solveSeconds": 0.8386813784482694,
"solveCpuSeconds": 1.4218009478673022,
"processWallSeconds": 0.671634971978885
},
"allRunsFullParity": true,
"samples": 3
},
{
"name": "cyclic",
"meanTotalSeconds": 9.711948900000152,
"phases": {
"residual_evaluation": {
"seconds": 2.8323357333368526,
"percent": 29.163412642511005
},
"jacobian_assembly": {
"seconds": 3.431086966648157,
"percent": 35.32851132122517
},
"numerical_factorization": {
"seconds": 0.3293585666582051,
"percent": 3.391271618595521
},
"linear_solve": {
"seconds": 0.24083636668046893,
"percent": 2.479794417786374
},
"linear_system_setup_other": {
"seconds": 0.14310710000260465,
"percent": 1.4735157842789146
},
"newton_overhead": {
"seconds": 0.01622606665841886,
"percent": 0.1670732293331836
},
"cvode_controller_including_error_estimation": {
"seconds": 0.028731599965188554,
"percent": 0.2958376352782097
},
"event_detection": {
"seconds": 0.002641499996722511,
"percent": 0.02719845443917512
},
"accepted_property_checks": {
"seconds": 1.6209957666577186,
"percent": 16.69073615757691
},
"sampling_and_storage": {
"seconds": 0.009674700005386208,
"percent": 0.09961646323516032
},
"output_replay": {
"seconds": 0.29708673333334445,
"percent": 3.058981635841801
},
"result_encoding": {
"seconds": 0.7434664000000643,
"percent": 7.655172073650971
},
"poll_and_progress": {
"seconds": 0.008574800045759426,
"percent": 0.0882912393181897
},
"framework_other": {
"seconds": 0.007826600011261084,
"percent": 0.08058732692941743
}
},
"meanScopes": {
"total": {
"inclusiveSeconds": 9.711948900000152,
"exclusiveSeconds": 0.00014803333336506816
},
"integration": {
"inclusiveSeconds": 8.670142566666678,
"exclusiveSeconds": 0.007678566677896015
},
"cvode_controller": {
"inclusiveSeconds": 7.026683966654825,
"exclusiveSeconds": 0.028731599965188554
},
"rhs": {
"inclusiveSeconds": 2.8279145666924705,
"exclusiveSeconds": 2.825953500006411
},
"nonlinear_residual": {
"inclusiveSeconds": 2.832391033356089,
"exclusiveSeconds": 0.006382233330441522
},
"jacobian": {
"inclusiveSeconds": 3.4341274666670265,
"exclusiveSeconds": 0.027501033304664208
},
"jacobian_probe": {
"inclusiveSeconds": 3.406626433362362,
"exclusiveSeconds": 3.403585933343493
},
"linear_setup": {
"inclusiveSeconds": 3.9065931333278363,
"exclusiveSeconds": 0.14310710000260465
},
"numerical_factorization": {
"inclusiveSeconds": 0.3293585666582051,
"exclusiveSeconds": 0.3293585666582051
},
"linear_solve": {
"inclusiveSeconds": 0.24083636668046893,
"exclusiveSeconds": 0.24083636668046893
},
"newton": {
"inclusiveSeconds": 6.9960466000228125,
"exclusiveSeconds": 0.01622606665841886
},
"event_detection": {
"inclusiveSeconds": 1.632206199992955,
"exclusiveSeconds": 0.002641499996722511
},
"accepted_property_checks": {
"inclusiveSeconds": 1.6209957666577186,
"exclusiveSeconds": 1.6209957666577186
},
"sampling": {
"inclusiveSeconds": 0.008811533338606145,
"exclusiveSeconds": 0.0008969333368137692
},
"append": {
"inclusiveSeconds": 0.008516533335447699,
"exclusiveSeconds": 0.0009090666676456749
},
"flush": {
"inclusiveSeconds": 0.007868700000926765,
"exclusiveSeconds": 0.007868700000926765
},
"output_replay": {
"inclusiveSeconds": 0.29708673333334445,
"exclusiveSeconds": 0.29708673333334445
},
"result_encoding": {
"inclusiveSeconds": 1.0405531333334086,
"exclusiveSeconds": 0.7434664000000643
},
"poll": {
"inclusiveSeconds": 0.008574800045759426,
"exclusiveSeconds": 0.008574800045759426
}
},
"symbolic_factorization": {
"seconds": 0,
"status": "not applicable: dense LU"
},
"error_estimation": {
"seconds": null,
"status": "included in cvode_controller_including_error_estimation"
},
"counters": {
"accepted_steps": 15733,
"residual_evaluations": 28023,
"linear_rhs_evaluations": 0,
"jacobian_evaluations": 1374,
"linear_setups": 4700,
"error_test_failures": 1334,
"newton_iterations": 28003,
"newton_failures": 1218,
"nonlinear_step_failures": 229,
"counter_segments": 10,
"jacobian_reuses": 3326,
"jacobian_refresh_setups": 1374,
"linear_setup_failures": 0,
"LU_factorization_failures": 0,
"LU_solve_failures": 0,
"event_model_evaluations": 0,
"scheduled_boundary_count": 5,
"rejected_steps": 1563,
"application_accepted_steps": 15731,
"same_time_returns": 2,
"max_same_time_streak": 1,
"nonlinear_residual_calls": 28003,
"jacobian_probe_evaluations": 38472,
"LU_factorizations": 4700,
"LU_solves": 28003,
"newton_solve_calls": 17296,
"event_count": 4,
"event_detection_calls": 15731,
"solver_starts": 10,
"all_counted_model_evaluations": 66495,
"accepted_property_checks": 15732,
"sample_count": 2175,
"jacobian_kernel_reuse": {
"gasEvaluations": 230872,
"gasReuses": 1923560,
"phEvaluations": 136587,
"phReuses": 940359,
"densityEvaluations": 685389,
"densityReuses": 3819723,
"pipeEvaluations": 301626,
"pipeReuses": 1368464
}
},
"medians": {
"control": {
"solveSeconds": 8.631554299999607,
"solveCpuSeconds": 8.65625,
"processWallSeconds": 9.813595800000257
},
"profiled": {
"solveSeconds": 8.620837400000255,
"solveCpuSeconds": 8.640625,
"processWallSeconds": 9.760674299999664
}
},
"observedOverheadPercent": {
"solveSeconds": -0.12415956184568255,
"solveCpuSeconds": -0.18050541516245744,
"processWallSeconds": -0.5392671664813387
},
"allRunsFullParity": true,
"samples": 3
}
],
"evidence": {
"revision": "2b07d996cff295d9f401dd32edd10472238bdf2d",
"compiler": "gcc.exe (x86_64-posix-seh-rev0, Built by MinGW-W64 project) 8.1.0",
"platform": "win32",
"stop": 21.7,
"settings": {
"method": "BDF",
"start": 0,
"sample_step": 0.01,
"rtol": 1e-08,
"max_step": 1e+30
},
"directionOneStash": "06b161c309644bb01aa402ff79f34749ff8e889a",
"normalSourcesUnchanged": true,
"generatedModelIdentical": true,
"runsWithFullParity": 16,
"repeats": 3,
"warmups": 1,
"builds": [
{
"name": "noncyclic",
"ud00Count": 2,
"buildKey": "7fe268c97557c3e1c5a511ea5e28cf0f0479a087031abe1a38d7007dde3c4ca0",
"inputSha256": "f16fc251c10073a4155b56060625e622d81cbc141779dc52fa44746ae8654b5b",
"controlSha256": "ee51ac31f625391ede4457d909968dae856471e66f19f1ae6d614a0735dc20d6",
"profiledSha256": "06c56f6963e7e3ea6930b1fc1f832231b97eb9feb231d60a3b0bed5b8d28fd01"
},
{
"name": "cyclic",
"ud00Count": 2,
"buildKey": "a50f302b77a49d1aa28a4ee305239a36e8c9281ecdc93756b729183b8f1e0a0a",
"inputSha256": "38189b2d3608ca8dfc2691dd8e60b634de499b8d7c35f94ea1a3e72e9b58eae3",
"controlSha256": "02041b6b16a2aca2d26f3ef727d995e67595ff094387615f66e8b7f759626f3d",
"profiledSha256": "705948a62df23999084223e8e96269b65741de3f913c731041f42eecaaa8de27"
}
]
}
}
@@ -0,0 +1,163 @@
# 求解器时间剖析与计数调查
日期:2026-09-16。
## 结论
方向二优化后的当前求解器,主要成本仍是 **Jacobian 基准/扰动求值、普通 residual 求值,以及接受步物性检查**。两种工况下它们合计占完整原生运行约 78%–81%。Dense LU 分解与线性求解合计约 5%–6%;Newton 控制、事件检测及步长/阶数控制所占比例很小。
本轮交付独立诊断工具和调查结果。计时加入 `test/` 下的源码副本,当前生产求解器未额外修改。方向一仍保存在 `stash@{0}`(`06b161c309644bb01aa402ff79f34749ff8e889a`)。
## 工况、测量与数值保持
- 当前八路模型:132 个状态、1784 个输出;Windows 11,CVODE 7.4 BDF。
- 两个 UD00 均不循环、两个 UD00 均循环,分别运行 0–21.7 s,覆盖 10.8 / 21.6 s 边界。
- 采样间隔 0.01 s,rtol 1e-8,保持既有 atol,max_step 1e30。
- 所有构建结束后,每种工况各进行一组预热和三组正常/插桩串行配对测量;预热排除。
- 16 次运行的完整结果 JSON(仅排除计时字段)、全部状态/输出二进制、警告及原有计数均通过一致性检查。
- 两种工况每次重复的新计数也完全一致。原始源码哈希及生成 C/头文件已核对。
- 计时器嵌套、提前返回、goto 清理及源码锚点保护测试:2 项通过。
以下 Total simulation 指原生进程内从初始化到结果写出和清理的完整工作,不含构建、进程启动、API 和浏览器。各阶段采用单调墙钟,使用扣除子调用后的互斥时间;三次取平均,分项可加总。求解/进程总时长中位数另列。
## 时间树
### UD00 不循环
```text
Total simulation 7.656251 s (100%)
├── symbolic factorization N/A(Dense LU)
├── error estimation 独立计时 未分离,包含在下方 CVODE controller 组
├── residual evaluation 2.087807 s 27.27%
├── Jacobian assembly (含探针) 2.739999 s 35.79%
├── numerical factorization 0.236525 s 3.09%
├── linear solve 0.179552 s 2.35%
├── linear setup / 矩阵缩放等 0.106778 s 1.39%
├── Newton overhead 0.012538 s 0.16%
├── CVODE controller / error estimation 等 0.021977 s 0.29%
├── event detection 0.001957 s 0.03%
├── accepted-step property checks 1.171337 s 15.30%
├── sampling / storage 0.009698 s 0.13%
├── output replay 0.298776 s 3.90%
├── result encoding / 写出 0.777299 s 10.15%
├── poll / progress 0.006268 s 0.08%
└── framework / other 0.005739 s 0.07%
```
其中积分循环平均耗时为 **6.578949 s**。
### UD00 循环
```text
Total simulation 9.711949 s (100%)
├── symbolic factorization N/A(Dense LU)
├── error estimation 独立计时 未分离,包含在下方 CVODE controller 组
├── residual evaluation 2.832336 s 29.16%
├── Jacobian assembly (含探针) 3.431087 s 35.33%
├── numerical factorization 0.329359 s 3.39%
├── linear solve 0.240836 s 2.48%
├── linear setup / 矩阵缩放等 0.143107 s 1.47%
├── Newton overhead 0.016226 s 0.17%
├── CVODE controller / error estimation 等 0.028732 s 0.30%
├── event detection 0.002641 s 0.03%
├── accepted-step property checks 1.620996 s 16.69%
├── sampling / storage 0.009675 s 0.10%
├── output replay 0.297087 s 3.06%
├── result encoding / 写出 0.743466 s 7.66%
├── poll / progress 0.008575 s 0.09%
└── framework / other 0.007827 s 0.08%
```
其中积分循环平均耗时为 **8.670143 s**。
### 关键边界
- residual 行是普通 RHS 加非线性 residual 的代数组装,不包含 Jacobian 探针。
- Jacobian 行包含基准/扰动模型求值和矩阵装配;普通 residual 与它不重复相加。
- symbolic factorization 不适用于当前稠密 LU,并非已经测出一个稀疏符号分解耗时为零。
- 误差估计在预编译 SUNDIALS 内部无独立计时入口,记录为 null;CVODE controller 还包含预测、历史更新、步长/阶数控制等,不能把整组冒称为误差估计。
- Newton overhead 已扣除 residual、Jacobian、线性 setup、分解和回代;包括剩余收敛判断、控制和接口开销。
- 事件检测已扣除物性检查及普通采样。result encoding 包含 JSON 序列化和文件写出,不能解释为纯编码算法的 CPU 耗时。
## 求解计数
| 参数 | 不循环 | 循环 |
|---|---:|---:|
| `accepted_steps` | 11006 | 15733 |
| `application_accepted_steps` | 11006 | 15731 |
| `same_time_returns` | 0 | 2 |
| `rejected_steps` | 1063 | 1563 |
| `error_test_failures` | 884 | 1334 |
| `nonlinear_step_failures` | 179 | 229 |
| `residual_evaluations` | 20048 | 28023 |
| `nonlinear_residual_calls` | 20037 | 28003 |
| `jacobian_probe_evaluations` | 30044 | 38472 |
| `jacobian_evaluations` | 1073 | 1374 |
| `jacobian_reuses` | 2214 | 3326 |
| `LU_factorizations` | 3287 | 4700 |
| `LU_solves` | 20037 | 28003 |
| `newton_solve_calls` | 12069 | 17296 |
| `newton_iterations` | 20037 | 28003 |
| `newton_failures` | 978 | 1218 |
| `event_count` | 1 | 4 |
| `scheduled_boundary_count` | 3 | 5 |
| `event_detection_calls` | 11006 | 15731 |
| `accepted_property_checks` | 11007 | 15732 |
| `solver_starts` | 5 | 10 |
| `all_counted_model_evaluations` | 50092 | 66495 |
| `sample_count` | 2172 | 2175 |
两种工况的 linear_setup_failures、LU_factorization_failures、LU_solve_failures、linear_rhs_evaluations、计数接口错误均为 0。
### 三处容易误读的计数
1. **rejected_steps 不等于旧 rejectedSteps。** 旧字段仅含误差检验失败:884 / 1334;加上非线性求解导致的步失败 179 / 229,本次完整拒绝尝试计数为 1063 / 1563。Newton 内部失败 978 / 1218 可能通过刷新 J 重试恢复,不全部导致拒绝步。
2. **循环的内部成功步 15733 与应用接受步 15731 都保留。** 差额对应两次 same_time_returns;原应用对时间未推进的返回不执行接受步事件/采样流程。这不是插桩造成的轨迹变化。
3. **jacobian_reuses 是矩阵复用。** 统计成功线性 setup 前后 J 刷新计数未增加的次数,分别为 2214 / 3326;它与计算新 J 时的物性内核复用不同。LU_solves 直接统计 Dense solve 调用,不能以 Krylov 迭代次数代替。
额外一致性关系在本次两种工况均成立:
- Newton solve 调用数 = CVODE 内部成功步 + 两类拒绝步。
- LU 分解次数 = Jacobian 刷新 setup + 沿用 J 的 setup。
- 原 nfev = 普通 RHS + Jacobian 探针(本模型无额外摩擦求值)。
- 累加计数段数 = solver_starts;覆盖所有事件及预定时间边界的 ReInit。
### 方向二内核复用
| 内核 | 不循环:计算 / 复用 | 循环:计算 / 复用 |
|---|---:|---:|
| gas | 180304 / 1502160 | 230872 / 1923560 |
| ph | 105403 / 732566 | 136587 / 940359 |
| density | 531697 / 2979837 | 685389 / 3819723 |
| pipe | 235272 / 1063254 | 301626 / 1368464 |
这些内核计数仅覆盖新 Jacobian 的基准/分组探针,不代表整个运行的全部物性调用。
## 插桩扰动
| 工况 | 原求解中位数 | 插桩求解中位数 | 观测差值 | 原进程中位数 | 插桩进程中位数 |
|---|---:|---:|---:|---:|---:|
| noncyclic | 6.583621 s | 6.638836 s | +0.84% | 7.777796 s | 7.830035 s |
| cyclic | 8.631554 s | 8.620837 s | -0.12% | 9.813596 s | 9.760674 s |
观测扰动约为百分之一或以内;负差值属于运行波动,不代表插桩带来优化。没有从各阶段强行扣除估算的计时器成本,小于毫秒级的项目不宜过度解释。时间树采用三次平均,此表采用中位数,且进程时长含启动,三者不应直接相减当作某个新阶段。
## 下一步优化判断
1. **Jacobian 仍是第一热点。** 约 35%–36% 的完整运行时间位于该阶段;扣除子调用的装配/分组管理仅约 0.022 / 0.028 s,绝大多数成本仍在模型探针。下一步应继续研究受扰动依赖范围及昂贵内核的导数计算;单纯优化矩阵填充循环收益有限。
2. **普通 residual 与接受步物性检查值得分别优化。** 普通 residual 占约 27%–29%,物性检查占约 15%–17%。这支持减少诊断用途完整重算的方向,但不等于已暂存的方向一方案具有正收益;必须对具体裁剪/计算调度的新增成本再次实测。
3. **完整运行还存在结果处理成本。** 输出回放与 JSON 写出合计约 11%–14%。若目标是用户等待时间,应把这部分一起衡量;若目标只限积分内核,则排除这部分。
4. **本规模下暂不优先更换线性求解器。** 分解+回代约 5%–6%,加上线性 setup 其他工作约 7%。这限定了只优化线性代数的当前收益空间;不能推广到状态数更大的模型。
5. **事件与控制框架当前不是主要瓶颈。** Newton 控制和 CVODE controller 各不足 0.3%,事件检测约 0.03%。应首先减少昂贵物理求值成本,而不是从这些小项着手。
## 工具、证据与限制
- [使用与参数口径](../standard/native-solver-profiling.md)
- [可复用调查工具](../../tests/manual/profile_native_solver.py)
- [随文保存的机器可读汇总](assets/2026-09-16/solver-profile-summary.json)
- 完整原始证据:项目 `test/solver-profile-20260916/`,含冻结程序、每次原始文件、profile.json、measurement.json、summary.json 和构建日志。该目录按项目约定被 Git 忽略。
- 本轮没有重新执行全仓库、Amesim 或浏览器全流程验收;这里的正确性结论是插桩与当前生产版本的完整数值保持。
- Windows 实测通过;计时与构建代码提供 Linux 路径,真实 Linux 运行未验收。
- 使用同一 SUNDIALS 库的 Newton 实现和公共操作表;诊断版显式挂接 Newton 便于观测,并用完整数值/计数对照检验等价性,不访问 CVODE 私有结构。
计数定义参考 [SUNDIALS 7.4 CVODE 可选输出接口](https://sundials.readthedocs.io/en/v7.4.0/cvode/Usage/index.html#optional-output-functions)。
+4 -1
View File
@@ -1,6 +1,6 @@
# 现行规范索引
索引版本:1.1.2;整理/复核日期:2026-09-13。
索引版本:1.1.3;索引更新日期:2026-09-16。
新增组件从[注册流程](component-registration-workflow-v1.md)开始,再按涉及的能力读取专项规范。[注册示例](component-registration-example-v1.md)包含实际失败阶段、修订对照和复现方法。本目录保存现行版本,修订时更新正文版本、日期及变更说明;历史实现报告仍在 `docs/other/`,不覆盖其历史结论。
@@ -23,6 +23,9 @@
| --- | --- | --- |
| [System XML v3](system-xml-v3.md) | 1.1.1 / 2026-09-13 | 纠正求解方法的 XSD 描述;说明示例可执行边界、参数输入及连接检查层次 |
| [优化基准模型](optimization-benchmark-model.md) | 1.1.0 / 2026-09-12 | 计时口径、当前/历史输入隔离、按需 XML 与可执行测试入口 |
| [平台依赖说明](platform-dependencies.md) | 1.0.0 / 2026-09-16 | Windows/Linux 的运行、测试、原生编译和时间剖析依赖 |
| [Jacobian 确定性复用](native-jacobian-reuse.md) | 2026-09-15 | 单次矩阵构建中的输入键、复用边界与回退 |
| [求解器时间剖析](native-solver-profiling.md) | 2026-09-16 | 互斥耗时、求解计数、独立诊断副本及复现入口 |
以上版本仅表示文档,XML Schema 和用户指定的八路基准未改变。
@@ -1,5 +1,8 @@
# C 求值的依赖排序与局部求解
2026-09-15:Jacobian 的分组探针现可在一次构建内复用完整输入未改变的确定性内核结果。
普通求值、依赖排序和原有有限性检查保持原规则,详见 [Jacobian 构建内复用](native-jacobian-reuse.md)。
文档版本:1.1.1
修订日期:2026-09-12
核对代码基线:`22579e5`;本次版本号仅标注文档,不改变模型版本或协议版本。
+67
View File
@@ -0,0 +1,67 @@
# Jacobian 构建内的确定性结果复用
日期:2026-09-15。
## 目的与边界
一次 Jacobian 构建会反复计算基准状态和分组扰动状态。多数扰动只影响局部输入;其余部分的昂贵计算可以复用。当前实现保留原有结构着色、差分增量、canonical 基准、矩阵装配及 Dense LU,只复用经过审查的确定性内核结果。
普通 RHS、接受步物性检查、摩擦事件及采样输出继续使用完整求值路径。本功能不依赖“按用途裁剪”实现,不删除任何原有输出或导数的有限性检查。
## 生命周期
1. 生成器为有分组收益且使用相关内核的模型生成 `ModelJacobianWorkspace` 和 `model_eval_jacobian_reuse`。
2. CVODE 每次请求 Jacobian 时,`model_jacobian_begin` 清空工作区。
3. 同一条 canonical 路径重算基准。此时记录确定性结果;该次求值完成后关闭记录。
4. 分组扰动查询基准结果。输入原始浮点位完全一致才复用;改变的输入重新计算,不覆盖基准条目。
5. 下次 Jacobian 请求重新开始。事件、后续 Newton 迭代和其他仿真运行不会沿用旧基准。
工作区在 C 工作进程内部通过堆内存分配,随运行释放。分配失败时继续采用未缓存的 canonical 着色差分,不把可选优化变成求解失败。
## 可复用的计算
| 计算 | 完整输入键 | 复用内容 | 保留的原有行为 |
|---|---|---|---|
| 储气物性 | 内核函数、质量、热状态、实际容积、全部介质参数 | `NativeGas` 全部字段 | 仅用于 property cache 为 NULL 的 canonical 内核 |
| 压力—焓温度反算 | 压力、焓、全部介质参数 | 温度 | 先查询当前求值的 PH 缓存;仍执行温度观察及本次缓存填充 |
| 密度 | 压力、温度、全部介质参数 | 密度 | 仍进行物性观察、字段有效性判断和本次字段填充 |
| 管路标量求根 | K、相对粗糙度、流量换算系数 | 原方程的收敛根 | 方程、容差、流向和失败判定不变 |
键使用完整浮点位,区分正负零。介质使用参数值而非对象地址。所有数值均来自原始公式或原始求根器,不使用量化、近似相等、邻近状态插值或结果钳制来提高命中率。
这也是运行时对“是否受扰动影响”的判定:机械位置引起的容积变化、耦合储气状态投影以及流向变化,都先计算实际物理输入,再查询完整键。复用不依赖实例名称、支路编号、固定流向或预设拓扑。
## 为什么不能直接共享原有物性缓存
原有 `NativePropertyCache` 的 PT/PH 条目和填充顺序会影响浮点舍入选择。直接跨扰动共享整个缓存,可能改变同一函数的实际数值路径,差分又会放大这类差异。
新机制仅在原路径本来需要执行纯函数时替代该函数的返回值。本次求值自己的缓存、观察、条件分支和错误检查仍按原顺序执行。例如 PH 已在当前求值缓存中命中时,直接使用原来那条命中路径;不会用跨扰动记忆覆盖它。
## 容量与恢复
- 储气条目按生成的物性调用分配,每个调用保留一份基准;非基准输入不驱逐它。
- 标量缓存固定为 1024 个槽。冲突时比较完整键,不把哈希相同视为输入相同。
- 每次查找至多探测 32 个槽,避免大型模型或表满时扫描整张表。放不下或找不到时执行原计算。
- 只记录成功的储气结果和有限标量结果。非有限标量不进入缓存。
- 分组扰动失败后使用未缓存的完整逐列差分。取消或超时不会触发额外重试。
- `--verify-jacobian` 使用未缓存的 canonical 完整差分核对每个矩阵元素。发现差异后使用参考矩阵,并为后续请求关闭着色/复用路径。
## 新模型接入
使用已有储气和气动计算函数的模型自动参与复用,不需要新增用户参数或维护第二份方程。结构分析不能证明分组安全时,原有差分回退继续生效。
新增底层函数默认按原方法执行。要让它参与复用,需要一次性说明完整输入、确定性、输出和副作用边界;新增介质参数也必须纳入完整键。不能仅凭函数名称或某个算例中数值未变就判为可复用。
## 解析 Jacobian 与自动微分的评估
本轮先实现可与原差分逐位对照的复用。当前物理内核包含限位/摩擦切换、`fmax/fmin` 分支、PH 反算和带区间保护的管流求根。若进一步使用解析或自动微分,需要明确切换点的导数政策,并对隐式方程求导;直接对迭代程序逐条求导不等于取得收敛物理解的导数。这些工作不包含在本轮实现中。
## 诊断与验证
构建清单的 `jacobianStructure.reuse` 描述启用条件、容量、范围和核对策略。结果的 `jacobianReuse` 分别记录 gas、ph、density、pipe 的实际计算与复用次数,范围是优化后的基准/分组探针,不包含独立参考差分。
RHS、Jacobian、步数等原有计数含义不变。复用减少的是一次 RHS 内部的昂贵计算,不应表现为虚减 RHS 次数。
主要测试:`tests.test_native_jacobian_reuse`、`tests.test_native_jacobian_reuse_runtime`,并结合原有物性、管路、生成器、守恒、事件、告警、存储及 Jacobian 测试。
前后比较工具:`tests/manual/benchmark_native_jacobian_reuse.py`。正式计时须在编译及重型验证结束后串行执行;带 `--verify-jacobian` 的运行只用于正确性核对。
+72
View File
@@ -0,0 +1,72 @@
# 原生求解器时间剖析
诊断入口:`tests/manual/profile_native_solver.py`。它冻结当前正常构建,在 `test/` 下的独立源码副本加入计时,生成分阶段耗时、求解计数、完整数值对照和插桩开销。适用于当前具有自动着色 Jacobian 的 BDF 模型;不支持的模型明确拒绝,不把无法观测的阶段报告为零。
```powershell
.\.venv-win\Scripts\python.exe tests/manual/profile_native_solver.py --run
```
默认使用 `tests/data/test-mql-8-corrected.json`,将所有 UD00 分别设为不循环、循环,仿真至 21.7 s,每个版本预热一次并测量三次。可用 `--input`、`--output`、`--stop`、`--warmups`、`--repeats` 调整。输出目录须是项目 `test/` 下的新目录,以保护既有证据。所有构建先完成,随后串行计时。省略 `--run` 只准备构建。
已有完整测量可用 `--output test/solver-profile-20260916 --report-only` 重新检查计数和生成汇总,不重新编译或运行仿真。
## 计时口径
- 总计时覆盖原生进程内的初始化、积分、采样写盘、输出回放、JSON 编码及资源释放,排除构建、进程启动、API 和浏览器。
- 采用单调墙钟:Windows 为 QueryPerformanceCounter,Linux 为 CLOCK_MONOTONIC。阶段耗时不是 CPU 采样百分比,不能与 CPU 秒数直接混用。
- 每个作用域记录包含子调用的 `inclusiveSeconds`,以及扣除子调用后的 `exclusiveSeconds`。只有互斥的 exclusive 值可相加。
- 三次阶段耗时取算术平均,确保分项之和等于总耗时;完整求解和进程耗时另报中位数。计时/统计开销仍包含在诊断结果中,以正常构建的配对运行估计扰动。
- Jacobian 阶段包含基准和分组差分的模型求值;这些求值不重复计入普通 residual 阶段。保留探针、装配及普通 RHS 的子作用域数据。
- Dense LU 没有稀疏矩阵的 symbolic factorization,记为不适用。
- 当前预编译 SUNDIALS 关闭内部 profiler。误差估计无法独立直接计时,记录为 `null`,包含在 `cvode_controller_including_error_estimation` 中。该组还包括预测、历史更新、步长/阶数控制及其他未观测内部工作,不将其整体冒称为误差估计。
- `newton_overhead` 为 Newton solve 扣除 residual、线性 setup、LU 和线性求解后的剩余时间,包括收敛判断、迭代控制及接口开销,不涵盖所有外层 CVODE 控制成本。
- 事件计时扣除了接受步物性检查、普通采样和存储;保留事件探测、定位所用插值及模式更新。物性检查独立列出,以免淹没在框架耗时中。
## 观测接口与数值核对
使用 SUNDIALS 公共 SUNLinearSolver / SUNNonlinearSolver 操作表包裹原函数。诊断副本显式挂接同一库的 `SUNNonlinSol_Newton`,保持库的 Newton 实现及默认策略,便于计时与观测线性 setup。没有读取 CVODE 私有内存布局。正常构建仍使用原来的隐式默认创建过程。
每次运行比较正常构建与诊断构建的完整结果 JSON(仅排除原有两个耗时字段),并核对状态/输出二进制文件 SHA。任何数值、警告、事件或原有求解计数变化均中止调查。计数读取在每次 CVodeReInit 之前及最终清理时累计,检查累计段数等于 solver_starts,避免仅报告最后一段。
工具对单个独立进程内的一次 BDF 求解设计;诊断全局状态不作为并发库接口使用。生产源码、生产可执行文件和模型公式不被插桩脚本改写。
## 参数定义
| 参数 | 定义 |
|---|---|
| `accepted_steps` | 累加 CVodeGetNumSteps,CVODE 成功内部步数 |
| `application_accepted_steps` | 原应用接受步数,可能受同时间返回处理影响,另行保留 |
| `same_time_returns` | CVODE 成功返回但返回时间与上一次相同的次数;此时应用保留求解器历史并继续推进 |
| `error_test_failures` | 局部误差检验失败次数,对应原 `rejectedSteps` |
| `nonlinear_step_failures` | 因非线性求解失败而拒绝的步尝试 |
| `rejected_steps` | 上述两类步失败之和,不等同于原 `rejectedSteps` |
| `residual_evaluations` | CVODE 普通 RHS 次数,包含初值/步长准备;排除 Jacobian 差分探针和事件求值 |
| `nonlinear_residual_calls` | 实际 Newton 非线性 residual 回调次数,其代数部分的时间也计入 residual 阶段 |
| `jacobian_probe_evaluations` | 自定义 Jacobian 基准/扰动 RHS 次数 |
| `linear_rhs_evaluations` | CVODE 内置线性差分 RHS 次数,与自定义探针分别记录 |
| `jacobian_evaluations` | Jacobian 矩阵刷新次数 |
| `jacobian_reuses` | 成功的线性 setup 中,没有新增 Jacobian 计算、沿用已有 J 的次数;直接比较该次 setup 前后的累计刷新计数 |
| `jacobian_kernel_reuse` | 新计算一个 J 时,对 gas/PH/density/pipe 确定性结果的计算/复用次数;与矩阵复用不同 |
| `linear_setups` | CVODE 线性求解器 setup 调用次数,对应原 `nlu` 的实际口径 |
| `LU_factorizations` | 原 Dense setup 的实际调用次数,失败次数另记 |
| `LU_solves` | 原 Dense solve 的实际调用次数,失败次数另记;不使用直接法恒为零的 Krylov 迭代数代替 |
| `newton_iterations` | 累加 CVodeGetNumNonlinSolvIters |
| `newton_failures` | 累加 CVodeGetNumNonlinSolvConvFails;一次步尝试内可能刷新 J 重试,不等于拒绝步数 |
| `newton_solve_calls` | Newton solve 入口次数,与 Newton 迭代数分别记录 |
| `event_count` | 原状态转换计数,同一时刻多个转换可合为一次;不是被发现的每个零点数量 |
| `scheduled_boundary_count` | UD00 等预定时间边界导致的额外重启次数,独立于状态转换事件 |
| `event_detection_calls` | 接受步事件检查入口次数 |
| `all_counted_model_evaluations` | 原 nfev,含普通 RHS、自定义 Jacobian 探针与摩擦事件求值;不含全部诊断/输出回放求值 |
| `accepted_property_checks` | 接受步及初始化物性检查入口次数 |
计数含义参考 [SUNDIALS 7.4 CVODE 可选输出接口](https://sundials.readthedocs.io/en/v7.4.0/cvode/Usage/index.html#optional-output-functions)。实际统计以本项目的调用边界和上述分项定义为准。
## 输出
- `prepared.json`:平台、编译器、模型参数、冻结构建哈希、原始/插桩源码哈希。
- `noncyclic/`、`cyclic/`:正常构建、诊断构建及各次运行的完整结果。
- 每次诊断的 `profile.json`:原始作用域次数、inclusive/exclusive 耗时和累计计数。
- `measurement.json`、`measurements.json`:计时、原始数据哈希及数值一致性检查结果。
- `summary.json`:互斥耗时分解、计数、分项平均值、总耗时中位数及观测扰动。
计时器及锚点保护测试:`python -m unittest tests.test_solver_profile -v`。Windows 与 Linux 使用同一源码;实际平台验收情况应以具体调查报告为准。
+117
View File
@@ -0,0 +1,117 @@
# Windows / Linux 依赖说明
文档版本:1.0.0;更新日期:2026-09-16。
适用范围:当前原生求解器、Jacobian 确定性复用及时间剖析工具。本次更新依赖文档,不安装环境或升级版本。依赖声明和锁文件仍以仓库既有文件为准。
## 依赖分层
| 用途 | Windows x64 | Linux x86_64 | 声明或入口 |
|---|---|---|---|
| 后端编排 | CPython 3.12,项目环境通常为 `.venv-win` | CPython 3.12,项目环境通常为 `.venv` | [.python-version](../../.python-version) |
| 后端运行包 | FastAPI、lxml、Pydantic 2、Uvicorn standard | 同左,包含平台相应的传递依赖 | [requirements.txt](../../requirements.txt) |
| 前端开发/构建 | Node.js 20.19+ 或 22.12+、npm | 同左 | [package.json](../../frontend/package.json)、[package-lock.json](../../frontend/package-lock.json) |
| 模型原生编译 | GCC/MinGW,支持 C11 和当前 GCC 编译参数 | GCC、标准 C 开发环境 | [原生构建器](../../app/simulation/native_codegen/build.py) |
| BDF 及共用原生程序 | SUNDIALS 7.4.0 开发头文件、导入库和 DLL | SUNDIALS 7.4.0 开发头文件及静态库 | [原生后端说明](../../native/README.md) |
| 后端数值回归 | 上述工具链,加 NumPy | 同左 | [requirements-test.txt](../../requirements-test.txt),NumPy 范围 `>=1.26,<3` |
| 求解器时间剖析 | 上述原生工具链与 Python 标准库 | 同左 | [profile_native_solver.py](../../tests/manual/profile_native_solver.py) |
| 浏览器回归 | 前端开发依赖中的 Playwright,以及对应浏览器 | 同左;另需浏览器要求的系统运行库 | 前端 `npm ci` 与 Playwright 浏览器安装 |
NumPy 属于测试依赖,不是后端运行依赖。当前原生数值计算不依赖 SciPy;本轮 Jacobian 优化没有引入自动微分库。时间剖析也不需要额外的 Python profiler 包、Linux perf 或开启 SUNDIALS 内部 profiler。
## Python 版本与平台安装口径
跨平台直接依赖参考版本为 FastAPI 0.141.1、lxml 6.1.1、Pydantic 2.13.4、Uvicorn 0.52.3,来源为 [python312-direct.txt](../../constraints/python312-direct.txt)。`.python-version` 记录参考补丁版本 3.12.3。支持范围、参考版本和实际安装环境是不同概念;历史性能结果对应当时的实际环境,不能由文档更新推断环境已升级。
### Windows
在已有 Python 3.12 环境中,运行包使用范围文件配合直接依赖约束:
```powershell
.\.venv-win\Scripts\python.exe -m pip install -r requirements.txt -c constraints/python312-direct.txt
.\.venv-win\Scripts\python.exe -m pip check
```
运行后端测试时改用:
```powershell
.\.venv-win\Scripts\python.exe -m pip install -r requirements-test.txt -c constraints/python312-direct.txt
```
Windows 目前没有完整的传递依赖 wheel 哈希锁。直接依赖约束不等于完整锁,不能拿 Linux wheel 锁安装到 Windows。
### Linux
发布和参考 CI 的运行依赖使用现有完整锁:
```bash
.venv/bin/python -m pip install -r constraints/python312-linux-x86_64.lock
.venv/bin/python -m pip check
```
此锁适用于 CPython 3.12、兼容 manylinux_2_28 的 Linux x86_64,锁定传递依赖和 wheel SHA-256。必须作为 `-r` 输入;它启用 binary-only 和哈希校验。
测试依赖在运行环境安装完成后单独安装:
```bash
.venv/bin/python -m pip install -r requirements-test.txt -c constraints/python312-direct.txt
.venv/bin/python -m pip check
```
`requirements-test.txt` 中 NumPy 仅规定范围,不是完整测试环境锁。正式跨版本性能比较还应记录实际 Python/NumPy/工具链版本,不能声称这一步实现了全部测试依赖的精确锁定。
## Windows 原生依赖
构建器接受 GCC 风格参数,当前不提供 MSVC 构建入口。本机已用 GCC 8.1 验证;这只是实测版本,不是对所有较早/较新 GCC 版本的兼容承诺。现有 Windows CI 使用 conda-forge 的 `sundials=7.4.0` 和 `m2w64-gcc`。
`SUNDIALS_ROOT` 指向包含 `include`、`lib`、`bin` 的开发文件根目录。需要以下五个库:
| 模块 | Windows 导入库 | 随程序部署的 DLL |
|---|---|---|
| CVODE | `lib/sundials_cvode.lib` | `bin/sundials_cvode.dll` |
| Core | `lib/sundials_core.lib` | `bin/sundials_core.dll` |
| Serial NVector | `lib/sundials_nvecserial.lib` | `bin/sundials_nvecserial.dll` |
| Dense Matrix | `lib/sundials_sunmatrixdense.lib` | `bin/sundials_sunmatrixdense.dll` |
| Dense Linear Solver | `lib/sundials_sunlinsoldense.lib` | `bin/sundials_sunlinsoldense.dll` |
还需保留发行包依赖的运行库,例如 `vcruntime140.dll`。构建器会把找到的上述 DLL 及该运行库复制到模型程序旁边。单独安装同名 Python 包不能提供这些 C 开发文件。
发现规则:`SIMULATION_NATIVE_CC` 覆盖编译器,否则从 PATH 查找 `gcc`;`SUNDIALS_ROOT` 覆盖库根目录,否则从当前 Python 基础环境的 `Library` 查找。若 Python 与 SUNDIALS 安装在不同环境,需要显式设置覆盖变量;文档不要求固定盘符或机器路径。
## Linux 原生依赖
执行 [setup-native-linux.sh](../../bat/setup-native-linux.sh) 前,系统应已提供:
- Bash、GCC 和标准 C 头文件/链接工具。
- CMake 及所选生成器对应的构建工具,默认通常为 GNU Make。
- curl、tar/gzip、sha256sum,以及 HTTPS 下载所需的系统 CA 证书。
脚本下载并校验固定的 SUNDIALS 7.4.0 源码,构建 Release 静态库,默认安装到项目 `.venv/native/sundials-7.4.0`。脚本本身不安装系统软件包。
```bash
bash bat/setup-native-linux.sh
```
需要 `libsundials_cvode.a`、`libsundials_core.a`、`libsundials_nvecserial.a`、`libsundials_sunmatrixdense.a`、`libsundials_sunlinsoldense.a`。构建器在依赖根目录的 `lib`、`lib64`、`lib/x86_64-linux-gnu` 下探测这组静态库。
如果设置 `SYSTEM_SIMULATION_NATIVE_ENV` 改变安装位置,应相应将 `SUNDIALS_ROOT` 指向该位置下的 `sundials-7.4.0`。默认情况下构建器先查当前 Python 环境的 `native/sundials-7.4.0`,再查 `/usr/local` 和 `/usr`。Linux 的 `start-all.sh` 另外要求 Bash 4.3+。
## 本轮新增功能的依赖边界
- Jacobian 复用使用现有 C11 标准库和上述五个 SUNDIALS 库,没有新增链接库。
- 时间剖析使用 SUNDIALS 7.4 公共接口,包括 Newton 操作表、非线性失败计数与步失败计数;需保留完整开发头文件。
- 诊断副本使用 GCC 的 cleanup 属性,Windows/Linux 都沿用项目 GCC 工具链。Windows 使用 QueryPerformanceCounter,Linux 使用 CLOCK_MONOTONIC。
- 当前 SUNDIALS 内部 profiler 关闭时,误差估计没有独立计时;报告按不可分离的内部控制组记录,不要求重新构建 SUNDIALS 来制造该阶段的独立数字。
在已有环境中核对本轮新增功能:
```text
python -m unittest tests.test_native_jacobian_reuse tests.test_native_jacobian_reuse_runtime tests.test_solver_profile -v
python tests/manual/profile_native_solver.py --output test/my-solver-profile --run
```
`python` 替换为对应平台的项目环境解释器。第二条命令会编译独立诊断副本并运行两种 UD00 工况,不安装依赖。详细计数与计时口径见 [时间剖析说明](native-solver-profiling.md)。
## 实际验证状态
本轮 Jacobian 优化与时间剖析已在 Windows 的现有环境实测,包含 UD00 循环/不循环以及完整数值保持核对;Linux 有对应构建、计时和测试路径,尚未完成本轮真实 Linux 运行验收。依赖文档覆盖两平台,不代表两平台验收均已完成。