# 求解器外层提前终止修复与验收 日期:2026-09-14。分支:`system-optimization`。 ## 结论 原先在 10.8 秒退出的八路模型,使用新封装完成 50 秒,纯求解墙钟时间 9.082 秒。旧封装在同一输入及当前组件实现下仍于 10.8 秒退出,确认本次修复针对外层判定,而非通过改模型参数避开问题。 新旧结果在前 10.8 秒的 1,082 个共同采样时刻、1,785 条序列(包括时间)上逐项完全一致。新封装共遇到 12 次成功返回但时间未变,最长连续 6 次,均自行恢复。当前四路、八路 corrected JSON 也均完成 50 秒。22 项相关自动测试通过。 ## 处理规则 旧代码将 `flag < 0` 和 `next <= t` 合并为失败。两者含义不同:负返回码表示 CVODE 报告错误;成功的内部步可能小于当前时间的浮点分辨率,因此外部读取的时间暂时不变。 | 情况 | 新行为 | | --- | --- | | CVODE 成功返回且时间增加 | 正常处理接受步、采样和事件 | | CVODE 成功返回但时间相同 | 保存 CVODE 当前状态与历史,继续调用;记录次数,不做零长度采样/事件处理 | | 连续时间相同 | 不设置次数门槛;继续检查取消与当前整次求解的实际墙钟时限 | | 超时发生且仍在上述停滞阶段 | 失败原因 `time-stagnation` | | 其他实际求解超时 | 失败原因 `timeout` | | CVODE 返回负错误码 | 立即报告 `solver-error`,保存失败操作、原始返回码和最后接受时间 | | 时间倒退、越过请求边界或非有限时间/状态 | 分别报告 `time-regression`、`time-overshoot`、`nonfinite-time`、`nonfinite-state` | | 接受步或状态切换超过既有保护上限 | 报告 `resource-limit`,明确具体上限类型 | | 用户取消 | 保持取消状态;不会被后续 CVODE 回调错误覆盖为求解失败 | | 积分器声称完成但未到请求终点 | 报告 `incomplete-result` | 时间相同的恢复路径不增加容差、不设置最小步长、不人为令时间增加,也不调用 `CVodeReInit` 跳过困难区间。既有信号分段边界和物理事件重启流程保留。 默认求解时限仍是 300 秒,Python 进程监控保留 5 秒退出宽限;这不是“某次停滞开始后再计 300 秒”。测试中的原模型使用 180 秒时限,实际远未触及。独立运行可通过已有 `--timeout` 设置时限。 CVODE 初始化、资源分配、配置 API,以及 BDF 步内采样与事件处理失败有独立操作/原因信息。CVODE 的正常停止返回码 `1` 不视为失败。返回码语义参见 [SUNDIALS 官方 CVODE 文档](https://sundials.readthedocs.io/en/latest/cvode/Usage/index.html)。 ## Python 进程封装 同时修正进程监控中的分类问题:此前取消后进程不响应,被强制结束时也抛出“超过时间限制”的异常。 现在,取消后不响应且达到退出宽限的进程仍返回 `cancelled`,网页任务映射为 `stopped`。如果强制结束导致没有完整结果文件,则明确返回空轨迹和空最终量,只保留最后收到的时间及进度计数,标记 `resultAvailable=false`、`statisticsComplete=false`;不读取可能写了一半的结果,也不伪造未知的最终统计。 真正超出进程时限且没有取消请求,仍报告进程超时;自行崩溃则报告退出码。已经退出的进程不会仅因监控线程尚在读取日志而被误判超时。 正常结果新增 `solverControl` 字段,网页后端保存在 `diagnostics.native.solverControl`。包含原因、操作、返回码、同时间返回次数、最长连续次数及结束时 CVODE 内部时间与步长;这些 CVODE 数值字段只对 BDF 有意义。非有限诊断数字使用 JSON `null`,受控失败输出保留最后有效的接受状态。 ## 模型验收 共同设置:BDF/CVODE 7.4.0,时长 50 秒,最大步长 `1e30` 秒,`rtol=1e-8`,采样间隔 0.01 秒,状态绝对误差限沿用生成器。原输入文件未修改;当前 corrected 文件仅在运行配置中延长时长。 旧封装对照使用临时 native 目录,仅将 `cvode_solver.c` 恢复为本轮修改前的 HEAD 内容;组件、生成器和其他当前运行库一致。因此该对照不是拿不同历史组件实现作比较。 | 输入与入口 | 结果 | 最终仿真时间 | 纯求解墙钟时间 | 同时间返回总数 / 最长连续数 | | --- | --- | ---: | ---: | ---: | | 原触发模型,旧 CVODE 封装 | 提前失败 | 10.8 s | 4.805 s | 旧封装不记录 | | 原触发模型,新封装独立进程 | 完成 | 50 s | 9.082 s | 12 / 6 | | 原触发模型,新封装网页后端入口 | 完成 | 50 s | 9.006 s | 12 / 6 | | 当前八路 corrected,新封装独立进程 | 完成 | 50 s | 6.792 s | 0 / 0 | | 当前四路 corrected,新封装独立进程 | 完成 | 50 s | 1.011 s | 0 / 0 | 上述时间是各次单次验收测量,不是速度基准测试;尤其旧封装只完成 10.8 秒,不能据此计算新旧加速比。纯求解计时包括积分、采样和事件处理,不含编译、进程启动及结果投影/文件写入。 原触发输入为 `test/mql8-50s-20260913/model-50s.json`,SHA-256: `7c48a380e465ba5ee8a15610586325d14ac36a8cda5b3caa02053a8efc27fe47` 当前输入为 `tests/data/test-mql-8-corrected.json`、`tests/data/test-mql-4-corrected.json`。原触发模型保留历史循环信号,以覆盖旧故障;它与当前 corrected 模型用途不同,不拿它直接判定与 Amesim 的一致性。 独立进程三组完整输出均为有限数,采样时间严格递增。按输出气体质量相加核对闭合系统总质量漂移:原触发模型 `4.53e-14 kg`,当前八路 `2.58e-14 kg`,当前四路 `2.75e-14 kg`。网页后端入口走 JSON→XML→模型生成→原生进程→原始序列传输,返回 5,010 个采样点,接受步统计与独立进程一致。 本轮没有重新启动 Amesim 或新增组件语义对照;摩擦及管路的既有 Amesim 数值基准回归已通过。本次验收目标是封装正确终止及不扰动已有轨迹。 ## 自动测试与故障注入 | 测试模块 | 方法数 | 验证范围 | | --- | ---: | --- | | `tests.test_native_solver_control` | 4 | 实际 CVODE + 临时注入:连续 1,000 次同时间成功返回后完整轨迹不变;负错误码、倒退、非有限时间/状态;持续停滞实际超时与取消;普通超时及既有资源保护 | | `tests.test_native_worker_control` | 3 | 实际子进程拒绝退出时的强制取消、普通/原始序列两种传输、网页 stopped 合同、超时与崩溃分类;半写入文件不作为结果 | | `tests.test_native_codegen` | 8 | RK45/BDF 完成与最大步长、取消、独立进程、缓存及默认 C 后端 XML 接口等 | | `tests.test_native_result_transport` | 6 | 实际 HTTP/流式结果合同、取消、序列与索引完整性等 | | `tests.test_native_friction` | 1 | 既有 8 组 Amesim 冻结数值基准,共 10 次 RK45/BDF 积分 | | 合计 | 22 | 全部通过 | 注入代码只存在于测试创建的临时 C 源码副本,生产运行库没有注入开关。连续 1,000 次恢复测试同时核对轨迹、接受步、事件和重启次数,防止以隐蔽重启或丢失事件的方式“跑完”。 实测平台为 Windows、GCC 8.1、SUNDIALS 7.4.0。本轮遇到既有 GCC 子进程启动偶发故障,重试构建后完成测试,没有改编译器路径或工具链配置。新增测试采用公共 SUNDIALS API、Python 可移植子进程接口和路径处理;已加入 Linux 原生 CI,Windows CI 的完整发现也会包含它们。本机没有执行 Linux 实测,Linux 通过情况待 CI 验证。 ## 交付与复现 - 运行库修改:`native/runtime/cvode_solver.c`、`common.c`、`main.c` 和 `native/include/runtime.h`。 - 进程封装:`app/simulation/native_codegen/runner.py`。 - 新增自动测试:`tests/test_native_solver_control.py`、`tests/test_native_worker_control.py`。 - 本机验收脚本:`test/verify_solver_wrapper_20260914.py`、`test/verify_solver_wrapper_backend_20260914.py`。 - 模型结果与摘要:`test/solver-wrapper-20260914/`;前缀逐项对照见 `prefix-comparison.json`。 - 测试日志:`test/solver-wrapper-control-20260914.log`、`solver-wrapper-worker-20260914.log`、`solver-wrapper-regression-20260914.log`、`solver-wrapper-friction-20260914.log`、`solver-wrapper-backend-20260914.log`。 `test/` 是本机忽略目录,包含大体积运行结果,不进入 Git;正式自动测试与本报告进入源码交付范围。重启后端后,新请求会使用更新的 Python 封装;C 运行库内容变化参与构建缓存键,正常构建会自动生成对应的新程序,无需手动清空缓存。此次未提交或推送远端,也未处理工作区已有的 Amesim 文件变更。