修复循环信号与事件采样并接入 LSTP 接触定位,补充八路验证及复用实验

相较上一版 Jacobian 确定性复用更新,本次补齐事件边界一致性、结果两侧采样及接触事件定位;保留已有物性复用和组件力学公式。

- 统一 UD00 信号求值与下一事件查询的绝对时间边界,修复循环边界浮点舍入导致的阶段错位、重复或漏报,并覆盖零时长、多阶段及长周期场景。
- 引入原生输出语义 v2:保留规则网格真实时间,补充内部时间事件和状态事件的左邻及事件后采样,按保存时间、状态和离散模式重放结果。
- 两条代码生成路径均发出 LSTP 接触描述,默认定位间隙过零及非负力模式的力截断;仅在接受事件时更新防重复记录,增加 contactEvents 诊断计数。
- 补充 MASS/LSTP 独立事件实验、八路全曲线与驱动阶段配对评估,以及 Amesim 不连续点输出对照和力差定位报告;MASS 新增释放机制仍保留为独立实验。
- 保存局部 probe、context 访问与回退、shadow replay、R288 real skip/typed replay 及阀门数值尾部诊断工具和报告;未证明净收益的实验不启用为生产默认优化。
- 更新原生运行说明和元件建模规范,补充信号边界、输出语义、接触事件和实验依赖回归测试。

验证:五组专项回归共 34 项全部通过;37 个待提交 Python 文件语法检查通过;git diff --cached --check 通过。
This commit is contained in:
ljz committed 2026-09-17 23:50:13 +08:00
1 parent 1aac220084
commit 7611f13208
85 files changed
+11358 -33

No files matched your search

@@ -373,6 +373,11 @@ def _compile_storage_anchored_program(network: SimulationNetwork) -> NativeProgr
raise NativeCapabilityError(f"Native output coverage is incomplete: {sorted(missing)}")
stops = [(si(c, "v"), c.xmin, c.xmax) for c in masses if int(c.stoptype) == 1]
from .contacts import contact_table
contact_source, contact_header = contact_table([
(si(mass_at(c, 'port_1'), 'v'), si(mass_at(c, 'port_2'), 'v'),
c.gap0, c.kcont, c.rcont, c.Pdis, int(c.discContactOption), 1)
for c in components if c.model_type == 'amesim_lstp00a'])
stop_c = ",".join(f"{{{v},{_number(lo)},{_number(hi)},0,0,0,0}}" for v, lo, hi in stops) or "{0,0,0,0,0,0,0}"
next_event = []
for index, c in signal_specs:
@@ -384,6 +389,7 @@ def _compile_storage_anchored_program(network: SimulationNetwork) -> NativeProgr
source = '\n'.join([
'#include "model.h"', '#include <math.h>', *declarations,
f"const NativeStop model_stops[{max(1,len(stops))}] = {{{stop_c}}};",
contact_source,
f"const double model_atol[NSTATES] = {{{','.join(map(state_absolute_tolerance, state_keys))}}};",
"const char *const model_output_keys[NOUTPUTS] = {" + ",".join(json.dumps(v.key, ensure_ascii=True) for v in variables) + "};",
"int model_init(double *y) {", *init, "return 1; }",
@@ -408,6 +414,7 @@ int model_property_temperatures(double t,const double *y,NativePropertyTemperatu
#define NSTATES {len(state_keys)}
#define NOUTPUTS {len(variables)}
#define NSTOPS {len(stops)}
{contact_header}
extern const NativeStop model_stops[{max(1,len(stops))}];
extern const double model_atol[NSTATES];
extern const char *const model_output_keys[NOUTPUTS];
+24
View File
@@ -0,0 +1,24 @@
"""Emit state-only LSTP event descriptors for both native lowering paths."""
from .compiler import _number
def contact_table(contacts):
"""Rows: velocity indices, gap, stiffness, damping, depth, force option,
subtract-first flag. Keep the force expression's floating operation order.
Position follows velocity in each reviewed mechanical state pair.
"""
rows = []
for v1, v2, gap, stiffness, damping, depth, option, subtract_first in contacts:
rows.append('{' + ','.join((str(v1), str(v2), _number(gap),
_number(stiffness), _number(damping), _number(depth),
str(option), str(subtract_first))) + '}')
count = len(rows)
header = f'''#define NCONTACTS {count}
typedef struct {{
int velocity1, velocity2;
double gap0, stiffness, damping, depth;
int signed_force, subtract_first;
}} NativeContact;
extern const NativeContact model_contacts[{max(1, count)}];'''
source = f'const NativeContact model_contacts[{max(1, count)}] = {{' + (','.join(rows) or '{0,0,0,0,0,0,0,0}') + '};'
return source, header
@@ -723,6 +723,12 @@ def compile_extended_program(network):
policy='immutable canonical baseline for gas, PH inversion, density and pipe roots; changed inputs recomputed',
verification='uncached canonical dense differences')
np,ng,nq=max(1,len(pgroups)),max(1,gas_count),max(1,len(pneu))
from .contacts import contact_table
contact_source, contact_header = contact_table([
(states[mass_groups[groups.find(ep(c, 'port_1'))][0].name, 'v'],
states[mass_groups[groups.find(ep(c, 'port_2'))][0].name, 'v'],
c.gap0, contact_stiffness(c), c.rcont, c.Pdis, int(c.discContactOption), 0)
for c in components if c.model_type == 'amesim_lstp00a'])
# Advanced-friction reference runs at 5 N and 7 N confirm Amesim 2404's
# 0.1% breakaway hysteresis. Simple friction uses the exact static threshold.
source='\n'.join(['#include "model.h"','#include <math.h>','#include <string.h>',*declarations,
@@ -731,6 +737,7 @@ def compile_extended_program(network):
num(sum(c.fstick*(1.001 if int(c.frictionType)==2 else 1) for c in dry))+'}'
for group,dry,threshold in friction_groups) or '{0,0,0,0}')+'};',
f'const NativeStop model_stops[{max(1,len(stops))}] = {{'+(','.join('{'+str(s[0])+','+','.join(num(v) for v in s[1:])+'}' for s in stops) or '{0,0,0,0,0,0,0}')+'};',
contact_source,
'const double model_atol[NSTATES] = {'+','.join(map(state_absolute_tolerance, state_keys or ['dummy']))+'};',
'const char *const model_output_keys[NOUTPUTS] = {'+(','.join(json.dumps(v.key,ensure_ascii=True) for v in variables) or '""')+'};',
*jacobian.source_lines(),
@@ -774,6 +781,7 @@ int model_property_temperatures(double t,const double *y,NativePropertyTemperatu
#define NOUTPUTS {max(1,len(variables))}
#define NSTOPS {len(stops)}
#define NFRICTIONS {len(friction_groups)}
{contact_header}
extern const NativeFriction model_frictions[{max(1,len(friction_groups))}];
int model_friction_drives(double t,const double *y,double *drives);
extern const NativeStop model_stops[{max(1,len(stops))}];
@@ -0,0 +1,151 @@
# LSTP 主路接入与八路力差定位
日期:2026-09-17。参照:本机 Amesim 2404、当前循环八路归档及上轮已审计的非循环归档。
## 1. 结论
**LSTP 事件已接入平台默认原生求解流程。循环、非循环八路的 238 条比较曲线均与上一轮“仅 LSTP”独立实验逐值相同,生成的原 RHS、雅可比结构和求解计数保持一致。MASS 的新增释放检查未合入。**
此前循环八路约 **128.55 万 N** 的最大力差,主要来自事件附近的保存时刻差异:平台配对点尚在驱动跳变边界,Amesim 普通输出点已经向前演化约 **2.20 ps**。两边驱动值相同,但连续速度已经不同,`1e11 N/(m/s)` 的阻尼将这个速度差放大。
在 Amesim 副本中只开启 **Discontinuities printout(不连续点输出)** 后,沿用原有共同网格及同驱动阶段配对,循环八路最大力差降为 **1.312403 N**,力的相对差超过 5% 的样本归零。平台没有再次改变积分结果,Amesim 原有所有样本也逐值不变,只新增了事件保存点。
另一个约 **−2.084e14 N** 的原始力峰,在 Amesim 的事件输出中同样存在,来自理想硬限位碰撞后的速度重置与大阻尼。这是当前参照工况本身产生的瞬态量级,不能直接当成平台独有的数值误差。
## 2. 默认流程改动及验证
- 两条生成路径均发出 `NativeContact` 描述,直接引用两端质量组的速度/位移状态,涵盖数值/几何刚度模式。
- 默认定位 LSTP 间隙过零;非负力模式还定位接触区内原始力过零。允许负力的模式不新增力截断事件。
- 沿用已测试的接受步密集插值、二分定位及相对速度换向分段检查。接触事件不重置位置或速度,不额外调用整模型 RHS。
- 防重复记录只在事件接受时更新,不参与力、雅可比或输出重放。保留已有输出语义 v2;结果增加 `contactEvents` 诊断计数。
- 两条生成路径原有间隙表达式的浮点运算顺序分别保留。移除新增常量描述表后,生成的 C 源码 SHA-256 与上轮原模型相同;雅可比仍为 132 状态、27 色和原物性复用方案。
实现:[事件描述](../../app/simulation/native_codegen/contacts.py)、[公共事件处理](../../native/runtime/common.c)、[接触定位](../../native/runtime/contact_events.h)、[组件接入规范 12.3](../standard/component-model-authoring-spec-v1.md#123-lstp-接触状态事件)。已有 LSTP 实例自动生效,无需工程 JSON 另加事件开关。
| 主路复测 | 循环 | 非循环 |
| --- | ---: | ---: |
| 与独立 LSTP 实验的全部比较数组 | 完全相同 | 完全相同 |
| 接受步 | 16,934 | 9,980 |
| RHS 求值 | 81,067 | 54,412 |
| 求解器启动 | 21 | 6 |
| 新增 LSTP 接触事件 | 8,同一时刻合并重启一次 | 8,同一时刻合并重启一次 |
| 接触力截断事件 | 0 | 0 |
| 同阶段配对 | 5,001/5,001 | 5,001/5,001 |
本轮未重新做五次性能测量,不将单次秒数解释为新性能变化;积分计数与上一轮独立实验一致。上轮性能结论见[MASS/LSTP 独立报告](MASS与LSTP事件独立实验及八路对比-2026-09-17.md)。
验证覆盖解析碰撞、守恒、两个最大步长、BDF/RK45、几何刚度、指数阻尼、力截断、同号间隙的中间接触/脱离、切触、无事件轨迹不变、事件输出及纯求解一致性,以及原有摩擦、硬限位和雅可比结构。
8 项 LSTP 专项及相关输出/信号、摩擦/结构回归合计 **27 个不同测试方法通过**。最初 Amesim 参考回路测试误用 2 ms 采样,参考含 1 ms 点,产生插值误差;恢复到参考的 1 ms 采样后原公差通过,没有放宽断言。另保留旧独立实验工具的兼容处理:使用新运行库时关闭主路接触注册,避免与实验事件重复执行。
测试日志:[LSTP 首批](../../test/lstp-mainline-unit.log)、[参考回路复测及输出/信号回归](../../test/lstp-mainline-regression.log)、[摩擦与结构](../../test/lstp-mainline-friction-structure.log)、[几何刚度](../../test/lstp-mainline-geometry.log)。八路原始结果:[主路汇总](../../test/lstp-mainline-20260917/baseline-summary.json)。
另有 1 项旧独立实验入口在新运行库上的[兼容回归](../../test/lstp-mainline-experiment-compatibility.log)通过;最终源码/结果隔离核验见[检查记录](../../test/lstp-mainline-20260917/final-verification.json)。
## 3. 将 32.4 s 的力差拆开
以第 2 路为例,接触力为:
`F = K*p + R*(1-exp(-p/Pdis))*(v_branch-v_common)`。
此时穿透约 4.6866 μm,`Pdis=0.1 μm`,阻尼系数已基本达到 `R=1e11 N/(m/s)`。由两侧保存的间隙和速度分别重建接触力,均与各自输出完全一致。
| 原普通输出的配对量 | 平台 | Amesim |
| --- | ---: | ---: |
| 实际时间,s | 32.400000000000006 | 32.40000000000221 |
| 公共质量块速度,m/s | 0 | −1.2883209101e-5 |
| 第 2 路相对速度,m/s | −3.3024983e-12 | 1.2854894891e-5 |
| 弹性力,N | 468,662.995301 | 468,662.655106 |
| 阻尼力,N | −0.330250 | 1,285,489.489088 |
| 总接触力,N | 468,662.665051 | 1,754,152.144194 |
**弹性项只差 0.340 N,约 128.55 万 N 的差异几乎全部是阻尼项。** 双方均在接触区,并不存在一边接触、一边脱离的模式错误。
此刻 UD00 驱动从 0 切换为 1e12 N,经负方向力源作用于公共质量块。质量为 170,000 kg,位于 0.37 m 上限,切换后向允许方向释放,加速度约 −5.882e6 m/s²。经过约 2.2e-12 s,速度变化达到约 1.3e-5 m/s,乘以大阻尼便得到百万牛级力变化。
本机参考代码中,[UD00](F:/AMESim2404/Amesim/libsig/submodels/UD00.c:593)更新周期阶段并调用时间不连续处理;[MECMAS21](F:/AMESim2404/Amesim/libmec/submodels/MECMAS21.c:547)将理想限位交给 `endst2_`;[LSTP00A](F:/AMESim2404/Amesim/libmec/submodels/LSTP00A.c:296)由穿透及其导数计算接触力。这些代码和本次输出支持上述因果链,但本报告不据此声称两套积分器内部算法完全相同。
## 4. 三种对照验证取样影响
### A. 使用相同实际时刻的连续状态
在独立运行库副本中增加 210 个只读密集输出查询,包括 Amesim 真正保存的时间。查询不改变步长、时间事件或接受状态;完整积分计数与主路运行一致。结果重放仍调用原模型公式。
| 第 2 路名义时刻 | 原配对绝对力差,N | 同一实际时刻绝对力差,N | 减少 |
| --- | ---: | ---: | ---: |
| 21.6 s | 387,825.741 | 7,758.635 | 98.00% |
| 32.4 s | 1,285,489.479 | 5,025.566 | 99.61% |
| 43.2 s | 29,254.603 | 3.685 | 99.99% |
同一实际时刻仍有小残差,不能据此宣称皮秒瞬态完全一致。32.4 s 附近 binary64 时间间距约 7.105e-15 s;按此刻加速度和阻尼估算,一格时间变化对应约 4,180 N。主路日志在这些驱动边界也记录了积分器成功步未推进外部浮点时间的情况。
进一步用**同一已保存的边界状态**,在独立程序中将局部时间从 0 开始,只继续到上述 2.20 ps;原力学、热力学公式及驱动阶段不变。rtol=1e-8 和 1e-10 的第 2 路末力均约 **1,761,496.860 N**;默认绝对时间密集输出为 **1,749,126.578 N**,Amesim 普通输出为 **1,754,152.144 N**。该试验表明这一极短瞬态对时间原点和启动处理敏感,单纯再收紧相对容差不能解释或消除全部残差。局部时钟没有合入生产。
证据:[逐支路力分解与同时间对照](../../test/lstp-mainline-20260917/force-error-analysis.json)、[连续状态取样](../../test/lstp-mainline-20260917/trace/dense-outputs.json)、[局部时钟结果](../../test/lstp-mainline-20260917/rebase/summary.json)。
### B. 直接比较事件前后保存点
按本机 [Amesim `ameputsimopt`](F:/AMESim2404/Amesim/scripting/python/amesim.py:1093) 的定义,仅将副本 `.sim` 第二行的 `simOptions[2]` 从 0 改为 1,启用 `printDiscont`。模型、容差、时间区间、普通输出间隔及其他求解选项不变,重新运行 Amesim。
记录从 5,002 行增至 5,046 行,核验**所有原有时刻、全部原保存变量逐值不变**。32.4 s 第 2 路新增记录为:
| Amesim 实际时刻,s | 阶段 | 公共质量块速度,m/s | 接触力,N |
| --- | --- | ---: | ---: |
| 32.400000000000006 | 驱动切换前 | 0 | 468,662.652083 |
| 32.40000000000001 | 驱动切换后立即 | 0 | 468,662.652083 |
| 32.40000000000221 | 原普通输出,已演化约 2.2 ps | −1.2883209101e-5 | 1,754,152.144194 |
平台事件后的力为 **468,662.665051 N**。与 Amesim 的事件后立即值比较,差 **0.012968 N**。这直接证明“相同信号阶段”还不足以把事件边界与已经演化的普通输出当成同一个瞬态。
### C. 保持原比较算法,重新比较整组八路
平台结果不变;参照换成含事件点的 Amesim 输出,仍用相同名义 10 ms 网格、相同时间候选窗口、完整 10 个驱动信号配对,并让所有曲线共用一对行。没有根据力差选择样本,没有删除事件网格点。
Amesim 原始事件输出存在一处文件行序倒置:先写 0.8000000000000009 s 普通点,后写 0.8 s 事件点。本次诊断只按原始实际时间稳定排序,保留全部行、值和重复时间,并保存排序到原始行的映射;未改变平台导入器或原始结果文件。
| 循环八路指标 | 原普通 Amesim 输出 | Amesim 含事件点输出 |
| --- | ---: | ---: |
| 比较曲线/网格点 | 238 / 5,001 | 238 / 5,001 |
| 未配对时刻 | 0 | 0 |
| 全网格最大绝对力差,N | 1,285,489.485624 | **1.312403** |
| 最大力差时刻 | 32.4 s | 0.01 s |
| 力的相对差超过 5% 的曲线样本数 | 24 | **0** |
| 所有变量相对差超过 5% 的曲线样本数 | 32 | **6** |
| 最大压力差,Pa | 61.105785 | 61.105785 |
| 最大温度差,K | 0.389714 | 0.389714 |
剩余 6 个为质量流量 4、焓流 2;5% 仍只是原有诊断线。32.4 s 八路在事件侧配对后的力差均小于 0.015 N。**1.312403 N 是这组共同网格和事件侧对照的最大差,不能作为网格之间所有极短瞬态误差的上界。**
证据:[新对照汇总](../../test/lstp-mainline-20260917/event-output-comparison/summary.json)、[全部曲线](../../test/lstp-mainline-20260917/event-output-comparison/comparison.json)、[阶段配对](../../test/lstp-mainline-20260917/event-output-comparison/phase-pairing.json)、[原始行映射](../../test/lstp-mainline-20260917/event-output-comparison/ame-row-order.json)。
## 5. 约 2.084e14 N 的原始力峰来自哪里
该峰发生在约 32.4003551 s,即释放后约 0.355 ms,公共质量块从上限运动至下限。碰撞前,两端速度都约 −2083.9 m/s、相对速度较小;理想下限把公共质量块速度重置为 0,支路质量块仍保持约 −2083.9 m/s。因此大阻尼立即产生约 −2.084e14 N 的力。此模型允许负接触力,该值不会被截断。
| 第 2 路对应的下限碰撞后 | 平台 | Amesim |
| --- | ---: | ---: |
| 事件时刻,s | 32.40035510065042 | 32.400355100640695 |
| 公共质量块位移/速度 | 0 m / 0 m/s | 0 m / 0 m/s |
| 支路速度,m/s | −2083.913265366 | −2083.913208006 |
| 接触力,N | −2.083913259638e14 | −2.083913202278e14 |
对应碰撞时间差约 **9.73 ps**,力值相对差约 **2.75e-6%**。单看绝对差仍有约 5.74e6 N,但这是在约 2.084e14 N 的峰值上。两边都出现同一种速度重置和阻尼峰,不能把这个峰本身归因于缺少 LSTP 事件。
这验证了当前参照模型的实现一致性;是否希望工程模型具有如此大的外力、速度及理想碰撞瞬态,应由工况与接触建模目标决定,不能用调小数值结果来替代模型选择。
证据:[平台碰撞前后样本](../../test/lstp-mainline-20260917/raw-force-peak.json)、[Amesim 事件输出及碰撞样本](../../test/lstp-mainline-20260917/amesim-event-output/mechanical-events.json)。
## 6. 后续建议及复现
初步平台展示与正确性评价,优先使用**开启不连续点输出的 Amesim 结果**作为参照,并保留共同事件侧配对、原时间戳及原普通输出的审计。当前证据不支持为消除原来的 128 万 N 配对差再调整 LSTP 刚度、阻尼,或将昂贵的 MASS 释放实验合入默认路径。
若下一步要验证皮秒尺度的峰形,再研究按事件相对时间表示/求解短瞬态、保存精度以及硬限位重置后的连续状态;本次只完成了独立局部时钟诊断,没有改生产时间处理。
[诊断脚本](../../tests/manual/diagnose_mql8_force_events.py)支持分阶段复现,完整执行使用新目录:
```powershell
.\.venv-win\Scripts\python.exe -X utf8 -m unittest tests.test_native_contact_events -v
.\.venv-win\Scripts\python.exe -X utf8 tests/manual/diagnose_mql8_force_events.py --output test/lstp-mainline-new-run --stage all
```
该诊断依赖本工作站已对齐的工程、上一轮独立实验数据和 Amesim 2404。源码、测试、规范及本文为可跟踪交付;原始结果在 Git 忽略的 `test/lstp-mainline-20260917/`。Windows 编译器的偶发启动失败由现有重试恢复;诊断和新专项串行构建以减少干扰,生产构建调度未更改。
@@ -0,0 +1,171 @@
# MASS 与 LSTP 事件独立实验及八路对比
日期:2026-09-17。环境:Windows、本项目 Python 3.12、原生 C、CVODE BDF;组件专项同时覆盖 RK45。
## 1. 本次实验回答什么
以已经完成 UD00 边界修复、事件输出语义修改的当前平台为基线,独立比较 `off`、`mass`、`lstp`、`all` 四种配置。循环和非循环各运行 50 s;每种配置保留完整结果,再做 5 次轮换顺序的纯求解计时。
**实验实现放在 `tests/manual/`,没有把新事件启用到默认平台。已有信号修复、输出语义、组件公式、雅可比分色及物性复用保留。** 因此本文的“未修改”特指本轮新增机械事件关闭,不是回退到此前有信号边界问题的版本。
MASS 的新增事件在当前八路工况中没有带来精度改善,纯求解耗时中位数增加 **31%–34%**。LSTP 的非循环最大力差降低 **9.90%**,本次耗时没有明显增加,但循环工况 32.4 s 的主要力差没有改善,部分其他误差反而增大。两类一起开启,精度与只开 LSTP 相同,耗时增加 **22%–23%**。
## 2. 独立实现与隔离方式
当前 `native/` 冻结为 `test/mechanical-events-20260917/source-control/native/`。`off` 直接用这个副本;其他配置用另一份副本追加实验运行代码,编译缓存、可执行文件、结果存储均放在本次实验目录。
[实验程序构建](../../tests/manual/mechanical_event_variant.py)在原生成源码后追加事件描述与释放驱动力求值入口。每次构建均断言原生成源码仍是逐字相同的前缀,状态键、求值调度和雅可比稀疏结构一致。正常 RHS、Jacobian 和物性缓存路径没有替换。八路仍为 132 个状态、1,784 个输出、27 个雅可比颜色。
生产源码的前后 SHA-256 核验、原模型前缀核验与积分计数用于区分“新增事件改变积分轨迹”和“破坏已有优化”。这些检查不代表新事件本身没有计算成本。
最终核验:46 个生产源码文件前后哈希相同,控制副本与当前 `native/` 相同;所有配置仍采用 `colored-difference` 和输出语义 v2。两组 `off` 的全部比较数组与上一轮输出语义基线完全一致。见[隔离及结果核验](../../test/mechanical-events-20260917/baselines/evidence-verification.json)与[生产源码哈希](../../test/mechanical-events-20260917/baselines/production-verification.json)。
### MASS
- 弹性上下限:分别检查 `xmin - x`、`x - xmax` 过零,定位接触和脱离。
- 非负接触力模式:在接触区内额外检查未截断力 `K*p + D(p)*v_relative` 过零。允许负力的模式不添加这个截断事件。
- 理想/恢复限位保持后的释放:在已贴限位、速度接近零时,检查不施加限位约束的加速度是否转向允许运动的方向。
- 原有碰撞、恢复系数和摩擦事件继续按原逻辑处理。弹性事件不重置速度或位置;保持后的释放从约束位置、零速度继续。
为隔离已有优化,释放驱动力来自独立整模型求值,没有修改原 RHS 以共享中间量。`releaseRhsCalls` 单独记录这项成本,并计入 `nfev`。这是**本实验实现**的代价,不是所有 MASS 事件实现必然付出的代价。
### LSTP
按原公式的同一运算顺序计算 `gap = gap0 + x2 - x1`,检查 `-gap` 的过零。非负力模式再检查接触区内的原始力过零;允许负力时不检查力截断。事件不施加硬碰撞式速度重置。
### 公共定位与输出
[实验运行库](../../tests/manual/mechanical_events_runtime.c)在积分接受步的密集插值上二分定位;浮点区间不可再分时停止。同一浮点时刻发生的事件合并提交并重启一次。仅在事件真正接受后更新防重复触发记录;试算、雅可比求值和输出重放不更新物理模式,输出仍由原公式计算。
相对速度换向时,先按换向点分段找间隙根,以覆盖步首尾间隙同号但中间接触/脱离的情况;孤立切触不制造虚假来回事件。这仍依赖积分步已经解析运动,不能保证捕获一个接受步内任意多次未解析的高频振荡。根的二分精度也不等于积分状态精度。
沿用上一轮 `outputSemantics.version=2`:记录事件左邻时刻和接受后的事件时刻。没有调整比较点来挑选误差更小的力值。
## 3. 组件独立验证
验证包括解析解、物理守恒、现有 Amesim 参考回路和无事件回归,代码见[专项测试](../../tests/test_mechanical_event_experiment.py)。
| 测试 | 验证依据 |
| --- | --- |
| MASS 弹性上下限 | 阻尼振子的解析接触持续时间及出界速度;两个最大步长、BDF/RK45 |
| MASS 保持后释放 | 连续斜坡驱动,1 s 释放;上下限镜像,最终速度和位置解析值 |
| MASS 原有恢复碰撞 | 恢复系数 0.5;与事件关闭版逐样本、最终状态和启动次数对照 |
| LSTP 两质量弹性碰撞 | 解析接触/脱离时刻、速度交换、总动量和动能加弹性势能 |
| MASS/LSTP 非负力 | 接触区内力截断事件、非负输出、MASS 截断时刻解析值 |
| 指数阻尼与两种力模式 | MASS/LSTP、BDF/RK45、extended 生成路径,检查完成且无事件抖振 |
| 无接触、初始贴边静止、持续接触 | 无多余重启,原始输出与关闭版完全一致 |
| 同号间隙与切触 | 已知二次多项式密集输出:准确找到 0.4/0.6 s 两根,切触不造事件 |
| 已有 Amesim 回路 | `friction_ideal`、`friction_elastic`、`joint_mechanical`,按现有参考值核验 |
弹性接触时刻/速度允许误差 2e-6,连续释放时刻 1e-8 s,解析截断时刻 3e-6 s;守恒断言另设独立公差。Amesim 参考来自现有 `amesim-component-semantics-2404.json`,这里不声称重新测出了 Amesim 的内部根定位算法。八路则使用本轮重新运行的 Amesim 结果。
**9 个测试方法全部通过**,覆盖 38 种模型/配置/求解器组合,另含 2 个解析密集输出场景。按开发顺序分批执行,力截断条件补全后专门复跑相关测试;日志有重复方法,不能把各批数量直接相加。证据:[首批 6 项](../../test/mechanical-events-20260917/isolated-verified.log)、[截断复测及参考回归 3 项](../../test/mechanical-events-20260917/independent-reference-tests.log)、[恢复碰撞回归 1 项](../../test/mechanical-events-20260917/restitution-regression.log)。
## 4. 八路比较口径
循环参照为 `tests/data/test_mql.ame`,非循环参照为已审计的 `test/node-fixes-amesim-20260914/test_mql.ame`。输入用上轮已对齐的工程副本,原 AME 和用户工程不修改。每个工况重新执行一次 Amesim,作为四种配置共同的参考;平台 BDF、0–50 s、输出间隔 0.01 s、误差限数值 1e-8。
八路包含 10 个 MASS:9 个无限位、1 个理想限位,没有弹性限位 MASS;8 个 LSTP 均允许负力,接触刚度与阻尼均为 1e11,指数阻尼距离约 1e-7 m。因此八路不覆盖全部新增模式,必须结合前面的组件测试判断。
每个比较点先核验 8 个 STEP 和 2 个 UD00 的完整驱动组合,再让全部 238 条曲线共用同一对真实保存行,不跨信号跳变插值。另检查八个 `gap < 0` 标志。信号阶段匹配不等于两边浮点时间完全相同,也不代表验证了网格之间的全部接触事件。
全部 8 次准确性运行均正常结束,全部比较输出有限;各自 **5,001/5,001 个时刻成功配对,信号最大差为 0,接触/分离标志不匹配为 0**。没有删除未匹配点来改善指标。
## 5. 八路准确性结果
### 汇总
下表列 `off → lstp`。`mass` 在 238 条已比较曲线上与 `off` 完全一致,`all` 与 `lstp` 完全一致,因此不重复相同列。
| 指标 | 循环:off → lstp | 非循环:off → lstp |
| --- | --- | --- |
| 最大接触力绝对差,N | 1,285,489.483041 → 1,285,489.485624 | 1.456532 → **1.312403** |
| 上项发生时间,s | 32.4 → 32.4 | 0.01 → 0.01 |
| 平静段最大力差,N | 0.275890 → **0.212043** | 0.275890 → **0.154170** |
| 最大压力差,Pa | 65.686768 → **61.105785** | 65.686768 → **61.105785** |
| 最大温度差,K | 0.395395 → **0.389714** | 0.395395 → **0.389714** |
| 最大间隙差,m | 1.293005e-8 → **5.474060e-8,增大** | 1.293005e-8 → **1.581826e-8,增大** |
| 最大质量流量差,kg/s | 1.326797e-5 → **2.424649e-5,增大** | 5.663171e-6 → **2.424649e-5,增大** |
| 最大焓流差,W | 141.920203 → **18.688805** | 141.920203 → **18.688805** |
| 明确非零且相对差超过 5% 的曲线样本数 | 56 → **32** | 30 → **6** |
“平静段”沿用已有定位统计:t≥0.1 s,避开信号边界前后 0.02 s;它不能替代包含事件点的完整指标。5% 是诊断筛查线,不是工程验收公差。循环的 32 个超线样本仍含接触力 24、速度 2、质量流 4、焓流 2;非循环剩余质量流 4、焓流 2。
最大压力差降低约 6.97%,温度差降低约 1.44%;循环/非循环平静段最大力差分别降低约 23.14%/44.12%。但间隙和质量流量的最大绝对差增大,因此不能称为所有指标全面改善。新增最大流量差发生在 0.09 s、`amesim_pn3node2_1.reference_mass_flow`;该点参考流量约 0.02098 kg/s,绝对差约为参考值的 0.116%。原有小流量样本的相对差计数减少,与这一绝对误差增大可以同时发生。
### 八个支路分别看
以下均为各支路完整 0–50 s 网格的最大绝对力差,单位 N。支路编号对应 `amesim_lstp00a_1…8`。循环均发生在 32.4 s,非循环均发生在 0.01 s。
| 支路 | 循环 off | 循环 lstp | 非循环 off | 非循环 lstp |
| --- | ---: | ---: | ---: | ---: |
| 1 | 1,285,489.480 | 1,285,489.479 | 1.450464 | 1.306552 |
| 2 | 1,285,489.483 | 1,285,489.479 | 1.456532 | 1.312398 |
| 3 | 1,285,489.482 | 1,285,489.486 | 1.456530 | 1.312403 |
| 4 | 1,285,489.478 | 1,285,489.480 | 1.450460 | 1.306567 |
| 5 | 1,285,489.475 | 1,285,489.483 | 1.447450 | 1.299400 |
| 6 | 1,285,489.475 | 1,285,489.481 | 1.446239 | 1.298499 |
| 7 | 1,285,489.477 | 1,285,489.478 | 1.446238 | 1.298504 |
| 8 | 1,285,489.478 | 1,285,489.477 | 1.447445 | 1.299415 |
非循环八路均改善,循环八路的大力差均基本不变。完整精度数据见[汇总](../../test/mechanical-events-20260917/baselines/effect-summary.json)、[逐支路](../../test/mechanical-events-20260917/baselines/branch-force-summary.json)、[全部物理量分组](../../test/mechanical-events-20260917/baselines/group-summary.json)。
### 实际新增了哪些事件
两组工况都只有 8 个新增 LSTP 接触事件,全部发生在 **6.5966840909145208e-9 s**,合并为一次积分重启;之后没有检测到新的 LSTP 接触/脱离。当前采用允许负力模式,因此没有力截断事件。
两组都没有新增 MASS 弹性接触或连续释放事件。当前模型没有弹性限位 MASS;理想限位的相关驱动跳变已有时间事件负责重新启动,没有额外触发本次连续释放根。不能用“本模型未触发”推断其他驱动下不需要释放事件;连续斜坡测试已验证释放能正确发生。
循环 32.4 s 仍使用平台实际时刻 `32.400000000000006` 和 Amesim `32.40000000000221`。驱动阶段一致,但约 2.20e-12 s 的实际时间差与高刚度连续状态的影响仍未分离。新增的启动间隙事件不能替代对此的检查。
额外事件保存点的最大绝对接触力仍约为循环 **2.084e14 N**、非循环 **4.035e11 N**,与 `off` 基本同量级;这些点没有同分辨率 Amesim 参考,未计入上述准确性判断。
## 6. 求解耗时与成本来源
每组、每配置运行 **5 次纯求解**,轮换配置顺序。计时排除编译和结果重放/序列化;完整输出准确性运行另存。下表为墙钟中位数,括号为五次最小–最大值,百分比相对本次同工况 `off`,不与上轮不同时间运行的单次秒数混比。
| 配置 | 循环耗时,s | 相对 off | 非循环耗时,s | 相对 off |
| --- | ---: | ---: | ---: | ---: |
| off | 14.334(14.209–14.664) | — | 9.583(8.978–10.293) | — |
| mass | 19.220(18.035–20.428) | **+34.09%** | 12.565(12.190–14.599) | **+31.12%** |
| lstp | 13.605(13.369–15.221) | **−5.09%** | 8.871(8.661–9.044) | **−7.43%** |
| all | 17.697(17.003–19.600) | **+23.46%** | 11.653(11.246–11.852) | **+21.60%** |
LSTP 的运行范围与基线有重叠,五次试验不构成普遍提速保证;本次没有明显增加耗时,且积分步数下降。MASS 的范围与基线明显分离,额外开销同时有计算次数支持。各配置五次计时的积分计数完全重复,并与完整输出运行一致。
| 工况/配置 | 接受步 | 拒步 | RHS 求值 nfev | 求解器启动 | 额外释放驱动力求值 |
| --- | ---: | ---: | ---: | ---: | ---: |
| 循环 off | 19,606 | 1,552 | 85,570 | 20 | 0 |
| 循环 mass | 19,606 | 1,552 | 122,240 | 20 | **36,670** |
| 循环 lstp | 16,934 | 1,137 | 81,067 | 21 | 0 |
| 循环 all | 16,934 | 1,137 | 112,331 | 21 | 31,264 |
| 非循环 off | 11,238 | 885 | 56,299 | 5 | 0 |
| 非循环 mass | 11,238 | 885 | 78,561 | 5 | **22,262** |
| 非循环 lstp | 9,980 | 742 | 54,412 | 6 | 0 |
| 非循环 all | 9,980 | 742 | 74,172 | 6 | 19,760 |
LSTP 循环/非循环分别执行 135,472/79,840 次元件步检查、2,196/963 次密集插值调用,实际只增加一次事件重启;其检查直接使用状态,不调用整模型 RHS。MASS 单独开启时,轨迹、步数和原有重启次数没变,多出的 nfev 恰好等于释放驱动力整模型求值次数。这解释了为什么“没有新事件发生”仍可能有明显耗时。
CPU 时间中位数也支持同一方向:循环 off/mass/lstp/all 为 13.891/18.266/13.062/16.984 s,非循环为 9.328/11.969/8.609/11.281 s。全部独立计时见[循环](../../test/mechanical-events-20260917/baselines/full/timings.json)、[非循环](../../test/mechanical-events-20260917/baselines/noncyclic/timings.json)。
## 7. 结论适用范围与下一步
1. **当前八路不宜直接把本实验 MASS 释放检查作为默认路径。** 其新增事件在这两组工况中没有触发,结果不变而存在额外计算。对于连续驱动力导致的限位释放,专项解析测试证明事件机制有作用;后续若集成,应优先从已有 RHS 获取释放驱动力,或仅计算必要依赖,再重新计时。
2. **LSTP 可以作为后续生产集成候选,但本次仍保留在实验中。** 它补齐了接触边界定位,部分误差降低,间隙和质量流量最大误差也有回退;不能据此声称循环八路的主要力差已解决,也不能把这一模型中的耗时结果推广到频繁接触/脱离的模型。正式集成前应根据展示关注量确认这些取舍。
3. **循环力峰仍应检查同一实际时刻的连续状态。** 32.4 s 的信号阶段已经一致,新增间隙事件仍未消除差异。下一步应核对细时间窗中的位移、速度、间隙和力,区分实际保存时刻偏移、高刚度响应及已有硬限位处理的影响。
纳秒级启动事件的定位时刻是本次平台数值轨迹上的根,没有同分辨率 Amesim 根时刻验证。额外事件保存点的巨大力峰同样保留,没有平滑或删除,也没有把缺少同分辨率参考的峰值当成准确性结论。
## 8. 复现与证据
[运行说明](../../tests/manual/mechanical_events_experiment.md)、[八路运行脚本](../../tests/manual/evaluate_mechanical_events.py)、[指标汇总脚本](../../tests/manual/summarize_mechanical_events.py)。
```powershell
.\.venv-win\Scripts\python.exe -X utf8 -m unittest tests.test_mechanical_event_experiment -v
.\.venv-win\Scripts\python.exe -X utf8 tests/manual/evaluate_mechanical_events.py --output test/mechanical-events-new-run --repeats 5
.\.venv-win\Scripts\python.exe -X utf8 tests/manual/summarize_mechanical_events.py test/mechanical-events-new-run
```
八路脚本依赖本工作站 Amesim 2404 及已有对齐工程;主输出目录必须是新目录。构建阶段出现过 Windows 缓存发布拒绝与 GCC 子进程启动/停滞,保留已完成运行后续跑,最后一次恢复使用 `--resume --serial-build`。串行选项只改变实验构建调度,不改变编译参数、模型、事件实现或求解器设置;构建及其等待均不计入上述纯求解时间。
本轮完整数据位于 Git 忽略的 `test/mechanical-events-20260917/`;代码、测试和本文可跟踪保存。每个配置目录有 `comparison.json`、`curves.npz`、`phase-pairing.json`、完整原始结果、事件日志及五次计时目录。
@@ -0,0 +1,95 @@
# 事件输出语义修改与八路基线复测
日期:2026-09-17。Windows,本项目 Python 3.12、原生 C、CVODE BDF;独立测试同时覆盖 RK45。
## 1. 结论
**已完成输出语义修改、专项测试,以及循环/非循环两组 50 s 八路基线重跑。两组各 5,001 个比较时刻均找到同一信号驱动阶段的样本,信号比较误差归零;循环接触力尖峰差仍然存在。**
本次修改只增加事件两侧的保存样本并明确比较规则,没有补充接触事件。前后对照均使用已经修复 UD00 的内核,避免把此前信号修复与本次输出修改混在一起归因。修改前的原生源码快照保存在 `test/output-semantics-20260917/control-source/native/`。
## 2. 输出语义怎样改变
- 规则采样时间仍按实际浮点数解释,不把网格点吸附到事件,不提前切换信号。
- 对仿真区间内部的时间事件,保存 `nextafter(boundary, -INFINITY)` 和 `boundary`。前者是旧积分段实际到达的终点;后者保留连续状态,用事件后的信号重放输出。两者是相邻的真实浮点时刻,不是两个重名时间戳。
- 对已注册的机械状态事件,在当前积分区间可用时,保存事件左邻时刻的插值状态,然后保存事件后的接受状态。这保留了碰撞前后速度及各自模式。初始即时事件没有新左侧区间,不倒填样本。
- 同一实际时间依然以后一次接受状态为准;序列严格递增。结果增加 `outputSemantics.version=2`,已有状态/输出块格式和变量集合保持兼容。
- 新增样本同样走既有有界缓冲、存储配额和输出重放流程。纯求解模式不保存这些样本。
主要实现:[公共事件采样](../../native/runtime/common.c)、[BDF](../../native/runtime/cvode_solver.c)、[RK45](../../native/runtime/rk45.c)、[结果元数据](../../native/runtime/main.c)。注册与重放要求见[组件开发规范 12.2](../standard/component-model-authoring-spec-v1.md#122-事件输出与重放)。
## 3. 如何保证比较阶段一致
当前八路有 8 个 STEP 和 2 个 UD00,UD00 各启用阶段均为常值。比较程序先取 Amesim 在名义网格附近实际保存的样本,再在平台保存样本中寻找相同的完整信号组合。
- 时间候选窗口仍为输出间隔的 `1e-7`,本次即 `1e-9 s`;它只用于匹配保存时间的舍入差,不修改求解时间或事件时间。
- 10 个信号必须同时一致;所有 238 条曲线共用同一对样本行,不按接触力、压力或误差大小选择样本。
- 不跨跳变插值;缺失对应阶段时明确记录未配对,不把错误配对纳入同阶段指标。旧的纯时间匹配指标另存为 `raw-time-comparison.json`,用于审计。
- 本规则针对本次分段常值信号;脚本遇到 UD00 斜坡会明确拒绝,避免把“输出值相近”错误地推广为所有信号的阶段标识。
- 额外核验八个接触件的带符号间隙:两组全部已配对点的 `gap<0` 标志一致。但这不是对网格之间所有接触/脱离时刻的验证,也不声称复原了 Amesim 的全部内部离散模式。
修改前循环工况缺少 4 个时刻的同阶段样本,非循环缺少 1 个;修改后两组均为 **5,001/5,001 配对,未配对 0 个**,没有删除事件点。循环改配时刻为 0.8、10.8、32.4、44 s;非循环为 0.8 s。真实时间、选中的信号值及原始行记录在各组 `phase-pairing.json`。
## 4. 第一次测试与回归
首先执行输出/相位配对、循环 UD00、存储专项共 18 项,全部通过;另补非网格时刻阶跃测试 1 项,通过。随后结果传输、HTTP 返回、取消/异常处理、恢复碰撞及摩擦保持回归 11 项通过,共 **30 项**。
关键验证包括:32.4 s 浮点边界前后分属不同阶段时正确配对;Amesim 保留事件前值时选用平台左侧样本;缺失阶段或任一驱动不同则明确拒绝;BDF/RK45 保存非网格阶跃两侧;碰撞前后速度分别为约 1 和 −0.5 m/s;有/无采样的积分最终状态及计数一致;二进制存储与 JSON 返回一致。
证据:[18 项首次通过日志](../../test/output-semantics-20260917/verified-first-tests.log)、[非网格阶跃](../../test/output-semantics-20260917/off-grid-test.log)、[11 项附加回归](../../test/output-semantics-20260917/additional-regression.log)。测试过程中一次解析碰撞时间断言过严:原设 1e-9 s,实际偏差约 2.61e-8 s;按该数值积分测试的目的改为 1e-6 s。事件两侧相邻浮点时间及速度断言未放宽。编译器偶发启动失败由既有重试恢复。
## 5. 八路基线结果
使用原有 AME 作为参照,在副本中审计并对齐参数、连接和运行设置。两组均重新执行 Amesim 归档中的程序及平台程序,没有用旧曲线代替本次运行。循环采用当前 `tests/data/test_mql.ame`;非循环采用此前已审计的 `test/node-fixes-amesim-20260914/test_mql.ame`。时间 0–50 s,输出间隔 0.01 s,两边误差限数值 1e-8。原始工程文件未修改。
下表“修改前”是保留了 UD00 修复的本次对照运行,采用旧的纯时间配对;“修改后”使用新增事件样本和同阶段配对。每项均包含事件网格点。
| 指标 | 循环:修改前 → 修改后 | 非循环:修改前 → 修改后 |
| --- | --- | --- |
| 信号最大绝对差 | 约 1e17 → **0** | 约 1e17 → **0** |
| 接触力最大绝对差 | 1,285,489.483 → **1,285,489.483 N** | 1.456532 → **1.456532 N** |
| 压力最大绝对差 | 65.686768 → **65.686768 Pa** | 65.686768 → **65.686768 Pa** |
| 温度最大绝对差 | 0.395395 → **0.395395 K** | 0.395395 → **0.395395 K** |
| 明确非零且相对差超过 5% 的曲线样本数 | 63 → **56** | 32 → **30** |
| 修改后信号阶段不匹配的比较点 | **0** | **0** |
| 修改后八路接触/分离标志不匹配的比较点 | **0** | **0** |
5% 仍是诊断筛查线,不是工程验收公差。循环剩余 56 个超线曲线样本为接触力 24、速度 2、质量流 23、焓流 7;非循环剩余 30 个为质量流 23、焓流 7。事件之外的小流量/焓流差异未因本次修改消失。历史初评中的 61 个不能直接当作本次修改前计数,因为本次对照的 UD00 已修复。
### 32.4 s 为何仍需继续查
选中的平台时刻为 `32.400000000000006`,Amesim 为 `32.40000000000221`。两边 UD00 均为事件后的值,第 2 路间隙均为负、处于接触区,但力仍分别为 **468,662.661 N** 与 **1,754,152.144 N**。
本次证据只排除了“信号前值对信号后值”的误配,不能排除高刚度过程对约 `2.20e-12 s` 实际时间差的敏感性,也不能证明缺少接触事件就是该误差的唯一原因。弹性接触力取决于连续位移/速度;切换信号输出不会直接把这些连续状态改成 Amesim 的值。
共同 10 ms 网格仍不是原始瞬态的上界:循环额外事件点中约 **2.084e14 N** 的绝对力峰仍存在;非循环原始峰值约 **4.035e11 N**。这些原始峰未被平滑、删除或纳入不具备同分辨率参考的精度结论。循环在 t≥0.1 s 且避开信号边界前后 0.02 s 的定位统计中,最大力差约 **0.276 N**,不能替代完整事件段验收。
## 6. 是否改变求解与耗时
对两组修改前后原始结果逐项核验:全部旧样本仍保留原始时间,1,784 条原输出在这些时刻数值完全一致,最终状态和全部下列积分计数也一致。这支持本次为输出/对照语义修改,没有改变八路积分轨迹。
| 项目 | 循环:修改前 → 修改后 | 非循环:修改前 → 修改后 |
| --- | --- | --- |
| 实际保存点数 | 5,010 → 5,028 | 5,002 → 5,006 |
| 接受步数 | 19,606 → 19,606 | 11,238 → 11,238 |
| 拒步计数 | 1,552 → 1,552 | 885 → 885 |
| 方程求值 | 85,570 → 85,570 | 56,299 → 56,299 |
| 求解器启动次数 | 20 → 20 | 5 → 5 |
| 雅可比刷新 | 1,835 → 1,835 | 1,270 → 1,270 |
| 线性 setup 次数 | 6,028 → 6,028 | 3,603 → 3,603 |
| 求解耗时,单次 | 11.945 → 11.572 s | 7.357 → 7.469 s |
| 平台进程耗时,单次 | 18.339 → 17.851 s | 13.638 → 13.869 s |
这些单次耗时有运行波动,不能据此声称性能提升或给出可靠开销百分比;可以确认没有新增积分重启与 RHS 求值。新增保存和输出重放仍有实际工作量。
## 7. 后续判断与复现
输出阶段误配已消除,可将这两组作为接下来修改的基线。继续判断循环接触力峰前,应在同一实际时刻或足够细的共同时间窗口核对位移、速度、间隙和接触力,区分保存时间偏移、动态响应及接触事件定位的影响;现有结果不支持直接把全部力峰差归因于接触事件缺失。
汇总证据:[前后效果对照](../../test/output-semantics-20260917/effect-summary.json)、[修改后运行汇总](../../test/output-semantics-20260917/after/summary.json)、[循环阶段配对](../../test/output-semantics-20260917/after/full/phase-pairing.json)、[非循环阶段配对](../../test/output-semantics-20260917/after/noncyclic/phase-pairing.json)。
```powershell
.\.venv-win\Scripts\python.exe -X utf8 tests/manual/evaluate_mql8_correctness.py --output test/output-semantics-rerun --ame-home F:/AMESim2404/Amesim --align-cyclic-from-ame --noncyclic-ame test/node-fixes-amesim-20260914/test_mql.ame --noncyclic-project tests/data/test-mql-8-corrected.json --profiles full noncyclic
```
输出目录必须是新目录。`--native-source` 可指定本次冻结源码作修改前对照;`tests/manual/summarize_output_semantics.py` 核验 before/after 两组结果。原始结果位于 Git 忽略的 `test/output-semantics-20260917/`;代码、测试与本报告为可跟踪交付。
@@ -0,0 +1,132 @@
# 八路曲线剩余差异占比与全程评价
日期:2026-09-17。
## 1. 结论与适用范围
**在目前 0–50 s、10 ms 共同输出网格上,两侧主要运行过程的数值一致性较好,剩余流量和焓流差异对整条曲线的影响很小。这个结论支持当前平台的初步功能展示和基线一致性说明。**
循环工况的 238 条曲线中,只有 3 条曲线、6 个“曲线×时刻”样本超过原来的 5% 相对差筛查线;它们集中在 0.30、0.31、0.32 s 三个时刻,占 5,001 个共同输出时刻的 **0.0600%**。不过,不能据此说其余时间完全没有问题:参考值接近零时,另有零星绝对差异;把这类点也计入,涉及 **34 / 5,001 个时刻,即 0.6799%**。
比占比更有说服力的是幅值和累计指标:质量流量各曲线中最差的全程相对 L2 误差为 **0.008566%**,焓流为 **0.007368%**;逐条曲线的累计绝对差与参考累计绝对值之比,最差分别为 **0.013934%** 和 **0.013814%**。这两种指标不会仅因为增加长时间的双方零输出而变小。
本次只读取已有结果,不修改元件、求解器、配对逻辑或工况,不重新求解。统计不包含共同网格之间所有自适应步或所有机械事件点,因此不构成未采样的极短瞬态误差上界。流量和焓流的差异尚未完成根因定位;本次量化的是影响范围和量级。
## 2. 数据来源与评价口径
- 循环:上一轮 LSTP 已接入主路的结果,对比开启不连续点输出的 Amesim 结果;238 条曲线,5,001 个共同网格点,全部成功配对。
- 不循环:同一轮主路基线的已有结果,Amesim 参照仍为普通输出,采用原有信号阶段配对;主要用来交叉检查剩余流量和焓流差异是否重复出现。
- 每个网格点、所有变量沿用同一对原始记录,不根据误差大小重新选择事件侧,没有平滑、插值或删除异常点。
- 原相对差口径为 `100 × |平台−Amesim| / |Amesim|`。质量流量只有在 `|Amesim| > 1e-6 kg/s` 时计算该比值;焓流门槛为 `1 W`。这些是已有报告的诊断门槛,不是工程验收公差。
- 接近零的区间单列:`|Amesim| ≤ 门槛` 且 `|平台−Amesim| > 门槛`。避免除以极小数,也避免把这些点直接忽略。
- “全程占比”使用曲线样本数或不重复时刻数,分母分别注明。连续标记点折合时间只是 10 ms 网格上的估计,不是已经定位的超差起止时刻。
令差值为 `e`、参考值为 `r`、梯形积分权重为 `w`:
- 全程相对 L2 误差:`100 × sqrt(Σw·e² / Σw·r²)`,即相对均方根误差。
- 全程相对 L1 误差:`100 × Σw·|e| / Σw·|r|`,避免正负误差相互抵消。
- 累计输送量误差:对流量或焓流差值做梯形积分;另保存绝对差积分和任意时刻累计有符号差的最大值。
积分均为已有 10 ms 网格上的估算。每条节点通道独立计算,不把串联节点的流量相加当成系统总输送量,也不把这些积分当成完整的守恒验证。
## 3. 超过 5% 的差异有多大占比
每类流量都有 16 条对照曲线,每条 5,001 个点。
| 变量类别 | 超过 5% 的曲线数 | 超过 5% 的样本数 | 占该类全部 80,016 个样本 | 占该类高于近零门槛的样本 |
| --- | ---: | ---: | ---: | ---: |
| 质量流量 | 2 | 4 | 0.004999% | 4 / 576 = 0.6944% |
| 焓流 | 1 | 2 | 0.002500% | 2 / 426 = 0.4695% |
下面按单条受影响曲线计算,防止其他无差异曲线稀释占比:
| 曲线 | 超过 5% 的时刻,s | 占本曲线全部样本 | 占本曲线高于近零门槛的样本 | 最大相对差 |
| --- | --- | ---: | ---: | ---: |
| `amesim_pn3node2_2.reference_mass_flow` | 0.30 | 0.0200% | 1 / 32 = 3.125% | 11.111% |
| `amesim_pn3node2_3.reference_mass_flow` | 0.30、0.31、0.32 | 0.0600% | 3 / 32 = 9.375% | 32.690% |
| `amesim_p4node2_4.reference_enthalpy_flow` | 0.31、0.32 | 0.0400% | 2 / 35 = 5.714% | 11.360% |
所以,单说“占全程很小”不够:最差流量曲线高于近零门槛的样本中,仍有 9.375% 超过相对差筛查线。关键是它们全部位于衰减尾部,实际量值很小:
- 最大相对流量差位于 0.32 s:Amesim **3.66657 mg/s**,平台 **2.46797 mg/s**,差 **1.19860 mg/s**;该曲线参考峰值约 **249,299 mg/s**。
- 最大相对焓流差位于 0.31 s:Amesim **−3.08080 W**,平台 **−2.73081 W**,差 **0.34999 W**;该曲线参考绝对峰值约 **522,749 W**。
- 0.30–0.32 s 并非本基线登记的 STEP/UD00 信号切换时刻。当前不能把这些剩余差异直接归因为之前的事件侧配对问题。
若仅查看参考幅值超过各自峰值 1% 的主要幅值区间,并同时要求高于近零门槛,质量流量最大逐点相对差为 **0.11559%**,焓流为 **0.10395%**。1% 峰值是用于区分主要幅值和衰减尾部的辅助口径,不替代上面的全样本统计。
门槛敏感性也支持差异集中于小量区间:近零门槛缩小到原来的 0.1 倍,超过 5% 的样本增至流量 79、焓流 29;扩大到 10 倍,两类都为 0。本报告保留原门槛,未靠改门槛消除差异。
## 4. 接近零的区间没有被忽略
| 类别 | 接近零但绝对差超过门槛的样本 | 占该类全部样本 | 这些点上的最大绝对差 |
| --- | ---: | ---: | ---: |
| 质量流量 | 44 | 0.054989% | 7.24440 mg/s |
| 焓流 | 2 | 0.002500% | 8.01591 W |
这些异常涉及 31 个不重复时刻,网格折合约 0.31 s,占全程约 0.62%。多数是零散点,最长连续标记段为 45.80–45.84 s 的 5 个样本,网格折合约 0.05 s。焓流两个点分别在 1.41 s 和 46.18 s,并与流量异常时刻重合。
连同相对差超过 5% 的三个时刻,共涉及 34 个时刻,网格折合约 0.34 s、约 0.68%。**99.32% 的共同网格时刻未触发上述任一筛查条件**,但这不表示误差严格为零。
按运行阶段分组,流量/焓流的主要绝对差仍在初始阶段;后续循环中没有再次出现高于原近零门槛且相对差超过 5% 的点:
| 时间区间,s | 最大流量绝对差,mg/s | 最大焓流绝对差,W | 流量近零异常样本 | 焓流近零异常样本 |
| --- | ---: | ---: | ---: | ---: |
| [0, 1) | 24.2465 | 18.6888 | 2 | 0 |
| [1, 10.8) | 6.5727 | 8.0159 | 9 | 1 |
| [10.8, 21.6) | 0.2172 | 0.02551 | 0 | 0 |
| [21.6, 32.4) | 7.2444 | 0.26117 | 5 | 0 |
| [32.4, 43.2) | 4.5750 | 0.17762 | 8 | 0 |
| [43.2, 50] | 6.3927 | 5.08374 | 20 | 1 |
## 5. 全程幅值与累计影响
以下每一列均独立取该类曲线中的最差值,因此同一行不同列可能来自不同曲线。
| 变量类别 | 最大全程相对 L2 误差 | 最大全程相对 L1 误差 | 最大逐点绝对差 |
| --- | ---: | ---: | ---: |
| 接触力,8 条 | 0.000008651% | 0.000004595% | 1.31240 N |
| 质量流量,16 条 | 0.008566% | 0.013934% | 24.2465 mg/s |
| 焓流,16 条 | 0.007368% | 0.013814% | 18.6888 W |
| 压力,56 条 | 0.00001109% | 0.000004617% | 61.1058 Pa |
| 温度,56 条 | 0.002601% | 0.002407% | 0.389714 K |
| 接触间隙,8 条 | 0.133016% | 0.045482% | 54.7406 nm |
接触间隙的最大逐点相对差为 1.16799%,但对应约 55 nm 的绝对差;因此除近零流量/焓流外,也不应宣称所有变量逐点都小于 1%。其余类别的完整逐曲线结果保存在统计 JSON 中。
质量流量的绝对差积分最大为 **7.82056e−7 kg = 0.782056 mg**;同一条曲线的参考绝对流量积分为 **5.61262 g**,比值 **0.013934%**。所有流量曲线中,任意时刻累计有符号差的最大绝对值为 **0.364845 mg**。
焓流的绝对差积分最大为 **0.491310 J**;该通道参考绝对焓流积分约 **12,521.9 J**。逐条比较误差积分与各自参考积分,最差比例为 **0.013814%**,来自另一条较小幅值通道。所有焓流曲线中,任意时刻累计有符号差的最大绝对值为 **0.307860 J**。
这些结果表明,在已保存输出的尺度上,剩余差异没有表现为显著的累计质量输送或能量输送偏差。
模型中另有异常量级的机械位移/容积输出,例如某位移参考值达到约 1.12e17 m;其绝对差虽大,相对差很小。本报告评价两套实现的一致性,不把这类模型本身的异常物理量级解释为工程工况合理,也不笼统宣称所有量的绝对差都小。
## 6. 不循环工况交叉检查
不循环工况仍然是相同三个时刻、相同三条曲线、相同六个相对差超过 5% 的样本。质量流量/焓流最差全程相对 L2 误差分别为 **0.008262% / 0.007048%**,相对 L1 误差分别为 **0.010307% / 0.010197%**。
接近零的绝对异常少于循环工况:流量 11 个样本,焓流 1 个;加上相对异常,共涉及 13 个不重复时刻,占 5,001 个时刻的 **0.25995%**。这支持主要剩余相对差属于初始衰减尾部,不是循环越多便持续积累的整体偏离。
## 7. 展示用结论与复现
可以在当前基线展示中使用以下表述:
> 在与 Amesim 对齐工况和事件输出阶段后,0–50 s、10 ms 共同输出网格上的主要响应曲线具有良好的数值一致性。质量流量和焓流最差全程相对均方根误差分别约为 0.0086% 和 0.0074%;较大的逐点相对差主要集中于近零衰减尾部,对累计输送量的影响很小。该评价不覆盖共同网格之间的全部极短瞬态。
本次不需要为整体展示而优先修改流量或焓流公式。若后续目标是低流量尾部、微小泄漏或每一事件内部瞬态的精确复现,再针对这些区间增加密集采样与根因定位;现有占比不应被当作这类目标已经通过的证据。
![全程曲线、近零尾部与绝对误差包络](../../test/mql8-curve-coverage-20260917/coverage.png)
- [完整统计:两种工况、所有曲线、区间及异常点](../../test/mql8-curve-coverage-20260917/coverage.json)
- [只读分析脚本](../../tests/manual/analyze_mql8_curve_coverage.py)
- [上一步力差与事件输出定位报告](LSTP主路接入与八路力差定位-2026-09-17.md)
复现统计无需重新仿真:
```powershell
.\.venv-win\Scripts\python.exe -X utf8 tests/manual/analyze_mql8_curve_coverage.py
# 本机已有 matplotlib 的环境可同时生成图:
F:\Anaconda\python.exe -X utf8 tests/manual/analyze_mql8_curve_coverage.py --plots
```
脚本核验:全部输入有限、共同网格完整配对、时间严格递增、积分权重合计 50 s;逐条曲线重算的相对异常数和近零异常数与此前报告完全一致;读取前后的输入 SHA-256 不变。原平台代码与仿真结果未改动。
@@ -0,0 +1,165 @@
# 八路模型正确性初评与展示建议
日期:2026-09-17。代码提交:`1aac2200843009a86066271d3bfb1053bd8d6b2d`。本机 Windows / Python 3.12.3 / GCC 8.1 / SUNDIALS 7.4.0。
## 评价结论
**平台已具备初步展示核心仿真功能的条件,但尚不能表述为“全部结果与 Amesim 一致”或“已完成工程精度验证”。**
本轮重新执行当前 Amesim 模型程序和当前平台代码,共完成六组对照,每组比较 238 条关键曲线。20 项相关自动化回归全部通过。压力、温度、常规行程和质量守恒表现较好;当前仍需处理默认模型配置不一致、循环信号边界不一致,以及小流量/焓流的局部偏差。
原始模型输入和 143 个已跟踪生产文件的 SHA-256 在评估前后均未变化。本轮新增评估脚本、图片和报告,没有修改组件公式、求解器或默认工程。
## 1. 首先发现:当前两份默认模型并非相同工况
| 项目 | 当前平台 JSON | 当前 Amesim AME |
| --- | --- | --- |
| 文件 | `tests/data/test-mql-8-corrected.json` | `tests/data/test_mql.ame` |
| 两个 UD00 信号 | 非循环,`iscyclic=0` | 循环,Amesim 枚举值为 2 |
| 仿真终点 | 10 s | 50 s |
| 输出间隔 | 0.01 s | 0.05 s |
| 误差限数值 | 后端默认 rtol=1e-8 | tolerance=1e-7 |
结构审计得到 **157 个元件、178 条逐端口连接、1,092 项公开参数**;只有两个 UD00 的循环参数不匹配,连接差异为 0。当前 AME 的 SHA-256 为 `0ad9117d88c64c743fcfc41c7b1c5d10854527ab6478cc76ffa092913e52ada3`,与 9 月 14 日旧报告的 `251b2f…` 不同,不能直接复用旧报告作为当前输入的结论。
正式对照在新建 JSON 副本中,仅将这两个循环参数按 Amesim 语义设为 1;再次审计后 **1,092 项参数及全部连接一致**,并完成 1,015 项图纸参数与归档运行参数交叉核验。运行时间、输出间隔、误差限在两边的副本中统一。原文件保持原样。
证据:[原始审计](../../test/evaluation-mql8-20260917/audit-original/audit.json)、[两个参数的对齐记录](../../test/evaluation-mql8-20260917/alignment-changes.json)、[对齐后审计](../../test/evaluation-mql8-20260917/audit-aligned/audit.json)。
## 2. 本轮实际运行与比较口径
| 工况 | 信号 | 时长 | 输出间隔 | 两边误差限数值 | 结果 |
| --- | --- | ---: | ---: | ---: | --- |
| 对齐后的短演示 `default` | 循环 | 10 s | 0.01 s | 1e-8 | 两边完成 |
| 当前模型全程 `full` | 循环 | 50 s | 0.01 s | 1e-8 | 两边完成 |
| 启动细采样 `startup` | 循环 | 0.15 s | 0.0001 s | 1e-8 | 两边完成 |
| 启动容差收紧 `startup-refined` | 循环 | 0.15 s | 0.0001 s | 1e-10 | 两边完成 |
| 最初压缩过程 `volume-startup` | 循环 | 2 μs | 2 ns | 1e-10 | 两边完成 |
| 非循环对照 `noncyclic` | 非循环 | 50 s | 0.01 s | 1e-8 | 两边完成 |
非循环对照使用已保存、重新审计的旧版非循环 AME 与当前默认 JSON,只在副本中统一运行设置;同样重新执行程序,没有读取旧运行曲线代替新结果。其余五组使用当前 AME。
Amesim 端提取源归档内的可执行程序与运行输入后重新运行;没有重新编译 Amesim。除 `.sim` 前五个运行设置外,提取文件逐字节校验不变。平台走 JSON→System XML→组件网络→当前原生 C 编译/运行。平台使用 CVODE BDF,最大步长为 1e30;Amesim 保留其余原运行选项。相同误差限数字不代表两套求解器的误差权重和算法完全相同。
每组映射压力、温度、位移、速度、8 路接触力与间隙、节点焓流/质量流、气室质量/容积/容积功、活塞容积及信号,共 **238 条曲线**。平台完整输出为 **1,784 条**;其余输出检查了有限性,未逐条声明与 Amesim 相符。节点焓流由保存的各支路流量和流入焓按符号重构;Amesim 气室体积功按其保存的压力、连接容积变化率及容积下限条件重构。
单位统一为 SI:Amesim 表压加 101300 Pa,g 转 kg,cm³ 转 m³,L/min 转 m³/s,间隙 mm 转 m。映射来自元件拓扑和端口审计,没有交换元件、拟合时间轴或任意改符号。
**时间比较方法:**在共同规则网格上,若原始保存时刻距名义采样时刻不超过输出间隔的 1e-7,直接读取最近保存值;其他点线性插值。保留原记录的事件侧别,不平均、不平滑尖峰。直接对 `1e17→49000` 的巨大跳变做插值,会因输出时间浮点漂移在 0.81 s 伪造 57888 等尾值。本轮已用原始样本消除这一比较器伪差;原始结果、原始时刻及朴素插值诊断仍保留。
所有“最大差”均指所述采样网格,不是连续时间极值。事件点不从主统计中删除;另外的事件外指标仅用于定位问题。
## 3. 主要结果
| 指标 | 50 s 循环全程,10 ms 采样 | 启动 0.15 s,0.1 ms 采样 |
| --- | ---: | ---: |
| 最大绝对压力差 | **65.687 Pa** | **4,641.503 Pa** |
| 最大温差 | **0.3954 K** | **0.4575 K** |
| 压力最大逐点相对差 | 0.000319% | 0.02022% |
| 温度最大逐点相对差 | 0.004197% | 0.007854% |
| 节点焓流最大绝对差 | 141.920 W | 27,064.123 W |
| 节点质量流最大绝对差 | 1.3268e-5 kg/s | 0.390305 kg/s,位于 0.04 s 阶跃点 |
| 8 路接触力最大绝对差 | **1,285,489.483 N**,32.4 s | 105.579 N |
| 普通质量块最大位移差,排除特殊 #9 | 1.2930e-8 m | 7.4362e-13 m |
表中各指标取该类全部映射曲线中的最大值,最大绝对差与最大相对差可能属于不同曲线和时刻。
![主要曲线和误差](../../test/evaluation-mql8-20260917/comparison.png)
### 完成状态与基本物理检查
- 六组均完成指定终点;所有读取的结果数值有限,输出时间有序。50 s 循环运行到达 50 s,没有再出现早期的 10.8 s 提前退出。
- 56 个气体质量状态合计初值约 **5.566893 kg**。六组中最大总质量漂移为 **4.3521e-14 kg**,相对漂移约 **7.82e-15**。
- 所检查的保存结果中气体质量、绝对压力和温度均为正。质量守恒通过不等于能量及所有本构关系均已验证。
- 20 项回归全部通过,覆盖默认非循环八路 10 s 完整运行、节点能量与容积传递、容积功和下限、氦气物性与反解、UD00 信号链路、压力单位、CSV 导出。首次运行中的部分 GCC 启动失败经现有重试机制恢复;最终没有测试失败。
### 容积下限的快速过程
普通 10 ms 采样看不到最初快速压缩,不能只拿恒定容积说明实现正确。2 ns 采样下,双方第 15 个气室都在 32 ns 的首个下限采样点达到 **0.15 L**,此前初值为 15 L。八个气室最大容积差约 **1.716e-13 m³**;体积功率最大绝对差约 **5.306e5 W**,最大逐点相对差约 **4.786e-6%**,参考功率本身约为 1e13 W 量级。
![容积与体积功率](../../test/evaluation-mql8-20260917/volume-startup.png)
该八路文件中的活塞直接连接气室;经节点传递容积与非零预设容积变化率的覆盖来自本轮通过的专项测试,不能仅由这张图推断。
## 4. 仍不能验收为全部一致的部分
### 4.1 循环事件与采样边界:已确认存在实现缺陷
当前原生信号函数分别通过 `fmod` 判断周期位置、通过周期乘法定位事件。本轮直接编译调用当前源文件,得到:
| 事件函数返回时刻 | 该时刻信号求值 | 应进入的阶段值 | 下一个 double 时刻的值 |
| --- | ---: | ---: | ---: |
| 44 s | 1e17 | 49000 | 49000 |
| 54 s,补充探针 | 49000 | 1e17 | 1e17 |
这证明同一数值时刻上的事件定位和信号求值不一致;不需要积分器即可复现。定位文件:[signal.c](../../native/components/modules/signal.c)。证据:[探针输出](../../test/evaluation-mql8-20260917/signal-probe/signal-boundaries.csv)。
32.4 s 的大接触力差还包含另一层问题:平台保存时间为 `32.4`,此时 UD00 为 49000;Amesim 保存时间为 `32.40000000000221`,UD00 为 1e17。第 2 路接触力分别为 **468662.661 N** 和 **1754152.144 N**。显示相同的小数时间并不保证处于跳变的同一侧,不能把该差额直接认定为接触力本构公式错误。
仅作定位:保留 t≥0.1 s 并避开各信号边界前后 0.02 s 后,8 路接触力最大差为 **0.276 N**,普通质量块速度最大差约 **1.35e-7 m/s**。这不能替代包括事件点的整段验收。
此外,平台原始额外事件样本在约 `32.40035510065 s` 保存了约 **−2.084e14 N** 的接触力;共同 10 ms 网格未包含这个样本,Amesim 本轮保存点也未覆盖同一时刻。因此本轮无法对该窄峰完成同侧、同分辨率定量验收,不能把共同网格最大差当作全部原始事件样本的上界。
### 4.2 小流量与焓流:绝对差小,但局部相对差仍明显
沿用此前报告的诊断分界:焓流参考绝对值超过 1 W、质量流超过 1e-6 kg/s 时计算逐点相对误差;参考更接近零时单列原值和绝对差。**±5% 是本轮筛查线,不是用户批准的工程公差。** 其他物理量分界及分界乘/除 10 的敏感性保存在指标文件中。
| 50 s 工况中的样本 | Amesim | 当前平台 | 绝对差 | 相对差 |
| --- | ---: | ---: | ---: | ---: |
| P4NODE2_1 参考焓流,0.32 s | −2.375850 W | −1.641047 W | 0.734803 W | −30.928% |
| P4NODE2_2 参考质量流,0.32 s | 3.260972e-6 kg/s | 8.351735e-6 kg/s | 5.090763e-6 kg/s | +156.112% |
50 s 循环共同网格中,明确非零的超 5% 样本为 **61 个**:接触力 24、速度 2、信号 5、焓流 7、质量流 23。其中 **30 个位于信号事件点之外**,全部属于上述焓流/质量流;不能将所有差异解释成事件侧别。参考近零的异常另计,没有因为不算相对误差而自动通过。
启动段 0.04 s 的质量流约 0.390305 kg/s 差异位于阶跃点;收紧双方误差限后,该点差异消失,启动质量流最大差降为 0.0009686 kg/s。另一方面,启动焓流过零附近仍有约 0.32 W 差异。收紧容差后最大压差仍约 **4.551 kPa**,最大温差为 **0.5214 K**,说明剩余偏差没有简单随误差限收紧消失。
本轮没有证明管阻公式、近零流量闭合或积分误差中的哪一项是剩余偏差的唯一原因。应在相同状态输入下逐项比较,再决定修改位置。
![事件与小量差异](../../test/evaluation-mql8-20260917/diagnostics.png)
### 4.3 输入本身属于极端工况,数值对齐不等于物理有效
模型包含 1e17 量级激励;无限位、无摩擦的特殊质量块 #9 在循环 50 s 时位移约 **1.12e17 m**。其最大绝对位移差约 **3.056e10 m**,最大逐点相对差约 **9.78e-5%**。该量及相连活塞的巨大几何容积必须单独列出,不能与普通行程的绝对误差混合评价。
启动细采样的气体温度达到约 **11759 K**;当前物性诊断也报告状态方程有效上限 1500 K 被越过,并报告内部理想热量学查询约 39.44 K 低于其 200 K 下限。39.44 K 是内部物性查询温度,不是初始气室温度。这些提示来自本次实际运行。
因此该输入适合展示复杂网络、刚性系统、事件和诊断能力;若用于展示某个真实设备的预测精度,需要另行明确合理的载荷、约束和物性适用工况,并以实验或可靠工程基准验证。
## 5. 建议修改优先级与验收方式
| 优先级 | 修改对象 | 依据 | 修改后应如何验收 |
| --- | --- | --- | --- |
| P1,展示前 | 默认 AME/JSON 的工况管理 | 两个循环开关、终点、采样间隔不同 | 明确主展示工况;保存成对的 AME/JSON 与哈希;参数、端口和运行设置审计一致 |
| P1,正确性 | UD00 的阶段判定、事件定位和输出侧别 | 当前 44 s/54 s 探针直接失败;循环接触力事件点差异大 | 为所有边界检查前一个 double、边界、本身之后一个 double;约定事件前/后输出;比较事件时刻、同侧力与速度及窄窗口原始样本 |
| P2,精度 | 启动残差与小流量/焓流 | 事件之外仍有偏差,收紧容差后未完全消失 | 先做同状态物性、管流、节点输入输出探针;再做分辨率/容差收敛;同时报告绝对误差和非零相对误差 |
| P2,展示模型 | 极端载荷、自由质量块与物性适用域 | 巨大位移、超温、纳秒级压缩 | 保留当前模型作为压力测试;另建有合理量级和约束的展示工况,重新对齐两平台,不能仅缩坐标轴隐藏异常 |
现有结果不足以支持“先更换积分器”或“继续优化求解器即可修复曲线差异”。已证明的边界缺陷和输入一致性应优先处理。
## 6. 可用于初步展示的内容
建议展示顺序为:加载八路工程 → 查看元件、连接与信号设置 → 运行 → 叠加两平台压力/温度/普通行程 → 展示误差曲线 → 查看物性诊断与事件差异 → 导出结果。
可直接导入的评估副本:[10 s 对齐工程](../../test/evaluation-mql8-20260917/default/platform.json)、[50 s 循环工程](../../test/evaluation-mql8-20260917/full/platform.json)。两者已与当前 AME 的物理输入对齐。
可用表述:“已完成 157 元件、132 状态八路网络的原生求解及结果输出,对 238 条关键曲线进行了 Amesim 对照;主要连续状态吻合较好,事件边界和小流量精度仍在完善。”
本轮没有执行真实浏览器中的拖拽、导入按钮、持久化恢复、绘图及取消全流程,不将后端测试通过等同于前端展示流程已经验收。单次 50 s 循环平台求解约 12.41 s,Amesim 完整进程约 7.59 s;计时边界不同且未重复测量,**不据此计算性能优劣比**。
## 7. 交付与复现
全部本机证据在 `test/evaluation-mql8-20260917/`,该目录按仓库规则被 Git 忽略;报告及四个脚本为新增可跟踪文件。
- [总指标与运行状态](../../test/evaluation-mql8-20260917/summary.json)、[输入/代码哈希](../../test/evaluation-mql8-20260917/manifest.json)、[评估结束哈希核验](../../test/evaluation-mql8-20260917/source-verification.json)。
- [1,428 条分工况曲线指标 CSV](../../test/evaluation-mql8-20260917/curve-errors.csv)、[18 条展示变量的双平台曲线 CSV](../../test/evaluation-mql8-20260917/showcase-curves.csv)。每个工况另有完整 `curves.npz`、`comparison.json`、映射、原始结果、日志和事件邻近样本。
- [20 项回归日志](../../test/evaluation-mql8-20260917/regression.log)、[信号边界探针](../../test/evaluation-mql8-20260917/signal-probe/signal-boundaries.csv)、[采样匹配检查](../../test/evaluation-mql8-20260917/sampling-verification.json)。
- [主评估脚本](../../tests/manual/evaluate_mql8_correctness.py)、[曲线映射](../../tests/manual/mql8_comparison.py)、[边界探针脚本](../../tests/manual/probe_mql8_signal_boundaries.py)、[绘图脚本](../../tests/manual/plot_mql8_evaluation.py)。
从仓库根目录使用新的输出目录重跑当前循环模型五组对照:
```powershell
.\.venv-win\Scripts\python.exe tests/manual/evaluate_mql8_correctness.py --output test/evaluation-mql8-rerun --ame-home F:/AMESim2404/Amesim --align-cyclic-from-ame
.\.venv-win\Scripts\python.exe tests/manual/probe_mql8_signal_boundaries.py --output test/evaluation-mql8-rerun/signal-probe
```
可选追加 `--noncyclic-ame test/node-fixes-amesim-20260914/test_mql.ame --noncyclic-project tests/data/test-mql-8-corrected.json`,重跑第六组非循环对照;该历史副本只存在于当前工作站证据目录。绘图脚本需要 Matplotlib,本轮使用已安装该库的 `F:/Anaconda/python.exe`。主评估脚本仅需项目既有运行依赖与 NumPy。
@@ -0,0 +1,101 @@
# 循环 UD00 事件边界修复与独立验证
日期:2026-09-17。环境:Windows,项目 `.venv-win`,MinGW GCC 8.1.0,原生 C 运行库,CVODE 7.4.0 BDF / RK45。
## 1. 本次完成的第一步
修复 UD00 的信号求值与事件时间计算不一致。实现位于 [signal.c](../../native/components/modules/signal.c),新增回归位于 [test_native_signal_boundaries.py](../../tests/test_native_signal_boundaries.py)。
原来信号值用 `fmod(t - tstart, period)` 判断当前阶段,事件查询另用绝对时间计算。两种浮点运算可能落在边界两侧。例如阶段时长为 0.8 s 和 10 s 时,事件查询已经返回 44 s,而取模仍小于 0.8,信号在该时刻仍输出上一段的 `1e17`。
现在 `native_signal()` 与 `native_signal_break()` 共用 `signal_interval()`,共同确定当前阶段、阶段起点及下一边界。阶段内部仍按线性插值计算;周期结束统一使用下一周期的起点,避免同一事件出现两个相邻浮点时间。
本次没有修改八路工程 JSON、Amesim 文件、积分算法或结果采样协议。已有 UD00 的调用接口保持不变,两条原生生成路径均自动使用修复后的内核。
## 2. 修改后的事件定位及处理
### 时间边界定位
设信号开始时间为 `s`,各段累计时长为 `offset[i]`,周期为 `P`:普通阶段边界按 `(s + offset[i]) + k * P` 计算;周期终点统一采用下一周期起点 `s + (k + 1) * P`。这些表达式的浮点运算顺序也是合同的一部分,求值与事件查询共同使用同一函数。
当前周期由估算值开始,再依据绝对周期起点修正。阶段采用左闭右开区间:严格早于边界时仍在前段,恰好等于边界时进入后段;零时长阶段被跳过。查询返回严格晚于当前时间的最近事件,无后续事件或事件超过仿真终点时返回仿真终点。
### 运行库处理
1. 生成的 `model_next_break(t, end)` 汇总各信号的下一边界,选择最早一个。
2. BDF / RK45 在这个时间边界处分段积分。对仿真内部的边界,旧段积分终点是边界左侧相邻浮点数 `nextafter(boundary, -INFINITY)`。
3. 到达边界后保留连续状态,将时间推进到该边界,并重新启动积分。此时信号求值已切换到新段,导数计算使用新信号。
4. 结果输出仍按保存的实际时间和状态调用 `model_eval()` 重放。因为信号阶段是时间的纯函数,求解试算、回退和重放不会推进或污染一个全局阶段计数器。
第 1~4 项的运行库流程原来已经存在;本次让第 3、4 项使用的信号值与第 1 项报告的边界保持一致。连续状态如位置、速度不会仅因信号切换被重置;代数量如输出信号和力可以立即改变。机械碰撞、摩擦等由状态触发的事件仍使用各自的状态事件处理。
### 与 Amesim 对齐的范围
本次确立平台自身一致的右连续规则。Amesim 的 UD00 还维护当前阶段及 `TNEXT`,通过离散时间事件调用更新阶段;本次没有完整复制这套离散调用和结果保存协议。
尤其是普通浮点数 `32.4` 与本工况计算出的事件时间 `32.400000000000006` 不相等。前者仍属于边界前,测试专门保留这一点,没有使用全局 epsilon 提前切换。后续对照 Amesim 的原始采样时,应继续区分实际时间和事件前/后侧;此次不能直接推出此前八路模型在 32.4 s 的力尖峰差异已经消失。
## 3. 独立信号测试结果
测试网络只有一个循环 UD00:第一段 `1e17`,持续 0.8 s;第二段 `49000`,持续 10 s;从 0 s 开始循环。两种求解器均运行至 60 s,以 0.01 s 保存结果,共 6,001 个采样点。该模型没有物理动态状态,运行库使用一个内部常量状态执行真实求解及输出重放流程。
| 验证项 | 结果 |
| --- | --- |
| BDF 独立仿真 | 成功完成 60 s;6,001 点逐点符合冻结边界期望值 |
| RK45 独立仿真 | 成功完成 60 s;6,001 点逐点符合冻结边界期望值 |
| 两种求解器比较 | 全部采样值相同 |
| 事件分段 | 两种求解器均启动 12 次:初始启动一次,11 个内部时间事件后分别重启 |
| 44 s 信号 | 修改前 `1e17`;修改后 `49000`,符合后一段 |
| 54 s 信号 | 修改前 `49000`;修改后 `1e17`,符合下一周期 |
| 八段信号 1,000 周期 | 连续检查 8,000 个边界,无漏报、重复;边界左邻/本身/右邻均正确 |
| 非零起点与小周期 | 3 组工况,各 900 个边界通过 |
| 含零时长阶段的循环 | 连续 2,000 个边界通过;周期尾部零时长段不泄漏瞬时值 |
| 其他 | 斜坡、非循环终值、零总时长、单段锯齿、乱序重放通过 |
新增 7 个测试方法及相关 3 个既有测试全部通过(最终版本 10 / 10,39.033 s,包含积分启动次数断言)。修改前先运行关键边界测试,已复现 44 s、54 s 及多阶段循环边界失败,详见保留日志。独立测试用于确认信号内核和求解/重放接入,不替代八路气动与机械系统的精度验收。
测试过程中曾遇到测试子进程返回空输出及 GCC 进程启动重试。边界查询已合并为单次批量调用,数值判定保持严格;最终定向测试通过。相关中间日志保留在验证目录,不把进程空输出当成边界数值错误,也不将一次成功重跑解释为环境问题已修复。
### 完整回归的结果与限制
另执行 `python -m unittest discover -s tests -v`,共运行 458 项,耗时 1,534.126 s,报告 7 个错误、25 项跳过,不能记为整库回归通过。新增 7 项信号测试、既有 UD00 XML、阶跃重启、信号驱动力以及当前八路工程保存配置的完整运行测试均通过。
| 错误来源 | 实际失败点与处理 |
| --- | --- |
| 管道多变过程测试 | `model --init` 输出不能解析为 JSON,尚未进入数值断言;单独复核又遇到 GCC 预处理超时 |
| 冻结参考网络 38 | 发布编译缓存时目录重命名报 Windows `WinError 5` |
| 雅可比固定状态夹具 | 夹具保存的输入哈希与当前八路 JSON 不一致,类初始化被主动阻止 |
| 物性警告测试 | GCC 预处理超时;残留预处理进程退出后测试序列继续 |
| 工程输入合同的三项测试 | 默认 GBK 解码 UTF-8 JSON 失败;仅加 `-X utf8` 单独重跑,3 / 3 通过 |
夹具期望输入 SHA-256 为 `670977bef67e62d9c66e8af497bada208bd72a7301be45128d185d47282cf288`;实际工作树及 `HEAD` 中八路 JSON 的 SHA-256 均为 `60ed16c71b72be4ae71ca7980f4f8cdcdf6b4c4c4c8950863c2390960b71ced4`,确认这项输入不匹配不是本次修改引入。没有为使回归通过而更新参考夹具、修改其他内核或放宽断言。本次仅在 Windows 验证,未声明 Linux 验收通过。
验证文件:
- [修改前失败日志](../../test/ud00-boundaries-20260917/before-tests.log)
- [最终定向测试通过日志](../../test/ud00-boundaries-20260917/final-verified-tests.log)
- [边界及左右相邻浮点值](../../test/ud00-boundaries-20260917/after-probe/signal-boundaries.csv)
- [双求解器独立运行摘要](../../test/ud00-boundaries-20260917/isolated-summary.json)
- [6,001 点结果及期望值](../../test/ud00-boundaries-20260917/isolated-signal-samples.csv)
- [完整回归日志](../../test/ud00-boundaries-20260917/regression-tests.log)
- [UTF-8 编码复核日志](../../test/ud00-boundaries-20260917/utf8-recheck.log)
复现定向测试:
```powershell
.\.venv-win\Scripts\python.exe -m unittest tests.test_native_signal_boundaries tests.test_amesim_ud00_xml tests.test_amesim_semantics.NativeComponentSemanticTests.test_signal_endpoints_and_exponential_contact_law_in_c -v
```
## 4. 新注册类似信号组件时需要什么
| 情况 | 事件方面的工作 |
| --- | --- |
| 新增现有 UD00 实例或调整其参数 | 无需额外注册,直接使用已有求值和事件内核 |
| 新型号复用 UD00 分段语义 | 接入已有 `native_signal` 和 `native_signal_break`,并把后者汇入 `model_next_break` |
| 新阶跃、脉冲、分段周期信号 | 除输出公式,还要提供下一时间事件;与输出共享边界计算和相等判定 |
| 无分段的光滑信号,如正弦 | 通常无需专门时间事件 |
| 由压力、位置等状态触发的切换 | 需要相应状态事件定位与切换机制,时间事件表不足以实现 |
仅在 `library.py` 注册类型,或填写 `PORTS` / `PARAMETERS` / `RESULT_VARIABLES`,不会自动实现这些事件。默认在 `native_codegen/extended.py` 接入;若新型号也进入 `compiler.py` 紧凑路径,两条路径都要覆盖。新增 C 函数时同步公共声明与模块导出/依赖。
新组件通常只负责描述事件时刻及切换语义,由运行库统一执行积分分段与重启。相关要求已写入[元件开发规范第 12.1 节](../standard/component-model-authoring-spec-v1.md#121-分段信号的时间事件)。
@@ -0,0 +1,136 @@
# 机械元件事件覆盖与 Amesim 对照
本次核查基于当前平台源码、八路工程的实际生成代码、本机 Amesim 2404 子模型源码及随安装提供的帮助。没有修改机械内核,也没有重新运行 Amesim;数值验收证据引用上一轮修复后的回归日志。
## 1. UD00 修复的影响范围
生产代码改动仅在 `native/components/modules/signal.c`。MASS、接触力公式、机械事件定位及求解器重启代码没有修改。已有机械事件仍然生效,不需要因为这次信号修复而重新注册。
若机械系统由 UD00 驱动,修正信号边界的取值会影响驱动力、后续轨迹和碰撞时刻;积分步分布也可能变化。因此“机械事件机制没有改动”不代表耦合系统的每一个数值结果都保持不变。纯机械模型若没有使用 UD00,则没有这条直接数值调用依赖。
## 2. 逐类比较
| 元件或模式 | 当前平台 | Amesim 2404 本机证据 | 判断 |
| --- | --- | --- | --- |
| MECMAS21 理想限位,`stoptype=1` | 注册 `NativeStop`;在接受步内用密集插值和二分定位上下界;命中后位置置于界面,速度置零,再重启积分 | `MECMAS21.c` 调用 `endst2_`;帮助说明其碰撞处理为位置等于限位、速度为零,并维护状态标记 | 撞击事件已具备,无需重复添加;应核验脱离限位、初始就在限位及同时事件 |
| MECMAS21 恢复碰撞,`stoptype=3` | 注册相同的限位定位;入射速度超过阈值时按恢复系数反向,否则置零 | 调用 `endstrc_`;帮助说明恢复系数及防止无限反弹的低速阈值 | 核心撞击事件已具备;保留并补边界组合验收 |
| MECMAS21 弹性限位,`stoptype=2` | 计算上下限穿透及弹簧阻尼力,未向 `NativeStop` 或其他通用事件表注册接触/脱离 | 允许负力时调用 `endst1b_`,否则调用 `mecEndStop_` 且 `handle_disc=1`;帮助说明维护接触状态 | 建议补充上下界接触/脱离事件,非负力模式还应处理原始接触力的截断边界 |
| LSTP00A 弹性接触 | 用间隙计算非负穿透,再调用 `native_contact`;没有接触状态事件注册 | 与弹性限位复用上述接触工具;`mecEndStop_` 明确启用不连续处理 | 当前八路模型应优先补充这一项 |
| 静摩擦、库仑摩擦、Karnopp/Stribeck 模式 | 已有 `NativeFriction`、保存的离散模式、停滑与起滑定位;密集插值定位速度阈值和净驱动力超过静摩擦阈值的时刻 | `MECMAS21.c` 调用摩擦工具;`linert1c` 帮助说明低速区、起滑阈值及保存的模式标记 | 已有基础,不是缺失整套事件;需核验换向、阈值相等、与限位/信号同时触发 |
| 无限位 MASS,`stoptype=4` | 不注册限位;若启用干摩擦仍有摩擦事件 | 同样绕过限位工具,保留所选摩擦模型 | 无需人为添加上下限位事件 |
| 普通双向线性弹簧、黏性阻尼 | 当前公开机械目录主要提供 LSTP00A 接触件;不能把它等同于普通双向弹簧阻尼 | Amesim `SD0000.c` 直接计算连续的弹簧、阻尼力及导数 | 将来新增这类光滑模型通常不需要事件;出现间隙、单向作用、饱和或摩擦切换时再添加 |
平台依据:
- [机械力公式](../../native/components/modules/mechanics.c):`native_contact`、`native_stop_motion`。
- [机械事件定位与状态更新](../../native/runtime/common.c):`locate`、`locate_breakaway`、`native_accept`。
- [生成器的接触方程、摩擦与限位注册](../../app/simulation/native_codegen/extended.py):接触力约在 559 行,MASS 弹性力约在 571 行,限位注册约在 625 行。
- [紧凑生成路径](../../app/simulation/native_codegen/compiler.py):支持的限位模式也生成 `NativeStop`;其他模式由扩展生成路径处理。
Amesim 依据:
- [MECMAS21.c](F:/AMESim2404/Amesim/libmec/submodels/MECMAS21.c:547):理想、弹性、恢复碰撞和摩擦模式的实际调用分支。
- [LSTP00A.c](F:/AMESim2404/Amesim/libmec/submodels/LSTP00A.c:294):接触工具调用及 `handle_disc=1`。
- [SD0000.c](F:/AMESim2404/Amesim/libmec/submodels/SD0000.c:267):普通弹簧阻尼连续方程。
- 本机 [amehelp.qch](F:/AMESim2404/Amesim/doc/amehelp.qch) 内的 `libmec/doc/html/utils/endst1b.html`、`mecEndStop.html`、`endst2.html`、`endstrc.html`、`linert1c.html`。这些帮助页通过只读查询 Qt 帮助数据库取得。
本机安装未提供上述接触工具的完整内部源代码。因此可以确认 Amesim 的接触状态管理和不连续处理接口,但不据此声称已完全复原其全部根函数、内部容差或同一时刻的执行顺序。
## 3. 当前八路工程的优先级
从 `tests/data/test-mql-8-corrected.json` 实际生成得到:
- 共 10 个 MECMAS21:9 个无限位,1 个理想限位;理想限位元件为 `amesim_mecmas21_10`,范围 `[0, 0.37] m`。
- 共 8 个 LSTP00A,均为数值刚度、允许负接触力;`kcont=1e11 N/m`、`rcont=1e11 N·s/m`、`Pdis≈1e-7 m`。
- 生成头文件为 `NSTATES=132`、`NSTOPS=1`、`NFRICTIONS=0`。这 1 组 `NativeStop` 包含上下两个位置界面,不代表只有一个事件时刻。
- `model_next_break` 只汇总信号时间事件,没有注册这 8 个接触件的间隙事件。接触力仍被计算,只是没有单独定位接触/脱离时刻。
建议优先顺序:
1. **先补 LSTP00A 的接触/脱离定位。** 它直接覆盖当前八路模型的 8 个接触件。
2. **复用同一机制覆盖 MASS 弹性限位。** 当前八路工程没有启用该模式,但这是平台组件能力中明显未显式覆盖的事件。
3. **完善已有理想/恢复限位和摩擦的组合边界。** 当前理想限位通过 `native_stop_motion` 的条件方程处理保持与离开,没有单独注册“约束反力消失/自由净驱动力反向”的根函数;应核验连续驱动力下的离开时刻,再补充需要的释放事件。
缺少接触事件不等于当前接触力公式错误。自适应积分可以通过缩小步长处理部分分段变化,已有温和工况也能通过对照。显式事件的目的,是更稳定地确定作用阶段、接触时刻及峰值附近的积分历史。此次核查不能证明之前八路模型的全部力峰差异均由这一缺口导致。
## 4. 应怎样添加
这类事件由状态决定,不能像 UD00 那样只向 `model_next_break(t,end)` 添加预定时间。应扩展状态事件接口,允许组件提供事件面、穿越方向、有效模式及事件后的操作。
- **接触边界:** LSTP00A 使用带符号间隙 `gap = gap0 + x2 - x1` 的 `gap=0`;MASS 弹性限位使用 `x-xmin=0`、`x-xmax=0`。不要用已截成非负的 penetration 作为唯一找根量,否则脱离区间整段为零,失去跨越方向。
- **非负接触力边界:** 仅在 `discContactOption=2` 且处于接触区时,处理未截断力 `Fraw=0` 的分支切换。允许负力的模式没有这个截断开关,不应因力过零就重启。
- **事件动作不同:** 弹性接触只重新确认分段并重启积分,位置和速度保持连续;理想碰撞、恢复碰撞才按其模型重置状态。不能把弹性接触事件直接当成硬碰撞。
- **不需要的事件:** `Pdis` 是指数过渡尺度,不是“穿透达到它就突然切换”的阈值;也不应在 `3*Pdis` 处造事件。数值刚度/几何刚度是初始化参数模式,若运行中不切换,也不产生事件。
- **防止重复触发:** 初始恰好接触、贴边不动、离开后重新接触、同一时刻多个接触和信号跳变都需明确阶段与方向,并保证试探求值不提交离散模式。
最低验收应覆盖:单 MASS 上下碰撞及释放、单弹性限位、双质量块 LSTP00A 接触/脱离、允许/禁止负力两种模式、非零/零阻尼过渡尺度,并对 BDF/RK45、不同最大步长比较事件时刻和接触峰值。与 Amesim 对照时还需区分事件前后值。
## 5. 已有验证能说明什么
上一轮修复后的[完整回归日志](../../test/ud00-boundaries-20260917/regression-tests.log)中,恢复碰撞双求解器测试、LSTP00A 接触测试、MASS 摩擦保持以及 `test_amesim_stick_slip_endstops_and_joint_circuits` 均通过。
最后一项使用独立 Amesim 2404 运行生成的参考数据,覆盖理想限位、弹性限位、摩擦与联合机械等场景;每个参考场景检查 40 个选定时刻,测试设置最大步长 0.002 s。它支持“相关常规数值行为仍可用”,不足以证明当前八路模型的高刚度接触全过程、所有边界组合都与 Amesim 严格一致。
## 6. 补充接触/脱离事件的求解时间成本
### 结论及适用范围
事件检查、定位和处理会增加计算工作,但总求解时间不一定增加。若它减少了跨接触边界的拒步、非线性迭代和反复缩步,这些节省可能抵消部分甚至全部开销。当前尚未实现接触事件,不能给出已测得的变慢百分比。
以下分析针对前文建议的“状态事件定位后重启积分”实现。单纯记录零点与切换接触阶段不是同一件事,也不应假设所有事件定位都必然要求相同的重启方式。
### 成本来自哪里
| 环节 | 当前代码依据及新增成本 | 判断 |
| --- | --- | --- |
| 每个接受步检查接触状态 | `native_accept` 已经检查硬限位/摩擦;新增 LSTP00A 间隙可直接由两个位置状态及常数计算 | 对 8 个接触件,轻量状态判断预计成本较低;这是算法量级判断,不是计时结果 |
| 定位实际跨越时刻 | 现有 `locate` 固定执行 60 次二分,每次调用密集插值;BDF 使用 `CVodeGetDky`,RK45 计算状态插值多项式 | 纯间隙根函数不需要重算整个气动网络;定位次数和精度决定成本 |
| 截断并重启积分 | `native_accept` 可把已接受步截回事件;BDF 调用 `CVodeReInit`,RK45 重新计算导数和初始步长 | 会放弃事件之后那部分试算工作,并产生重新起步的工作;BDF 的历史重建尤其值得关注 |
| 雅可比及线性求解 | BDF 原生路径使用差分雅可比和稠密线性求解器;新起步需要相应初始化与后续数值建立工作 | 成本作用于整个耦合系统,而非只作用于触发事件的那一个弹簧 |
| 保存事件点及输出重放 | 若沿用现有状态事件存储流程,每个新增事件时刻会保存状态,输出阶段再次调用 `model_eval` | 通常少量事件时不突出;大量事件会增加结果体积与后处理时间,需要与纯求解耗时区分 |
CVODE 7.4.0 的官方实现中,`CVodeReInit` 将当前阶次重设为 1,并重置步数/历史起点;下一次积分进入初始设置与初始步长计算。它复用已有内存,不是重新编译模型或重新分配整个求解器。见 [CVODE 7.4.0 官方源代码](https://github.com/LLNL/sundials/blob/v7.4.0/src/cvode/cvode.c#L518)。平台对应入口见 [cvode_solver.c](../../native/runtime/cvode_solver.c:357)、[rk45.c](../../native/runtime/rk45.c:57) 和 [common.c](../../native/runtime/common.c:137)。
因此,主要风险是频繁切换造成的整系统重新起步,不是“多写了几个 if 判断”。重启不清除从仿真开始以来的物理运动结果,而是从事件处的状态继续积分。
### 既有八路运行数据能支持什么判断
早先正确性评估中的 **50 s 循环工况**记录如下,来源为 [native-summary.json](../../test/evaluation-mql8-20260917/full/native-summary.json)。这是 UD00 修复前的历史样本,仅用于估计工作量,不能作为当前代码已经完成的性能 A/B 对比。
| 指标 | 历史样本 |
| --- | ---: |
| 求解壁钟时间 `solveSeconds` | 12.414 s |
| 接受步数 | 19,412 |
| 误差测试失败次数 `rejectedSteps` | 1,511 |
| 方程求值计数 `nfev` | 84,268 |
| 其中雅可比差分求值 | 50,512 |
| 雅可比刷新次数 | 1,804 |
| 线性求解器 setup 次数,输出字段 `nlu` | 5,903 |
| 求解器启动次数,含初次启动 | 20 |
| 已注册状态事件的触发批次数 | 9 |
该生成模型包含 132 个状态、1,784 个输出和 27 个雅可比分色组。历史数据中每次雅可比刷新对应 28 次方程求值(27 组扰动加一个基准求值),合计 `1804 × 28 = 50512`。这说明多余的整模型求值、雅可比刷新和重新起步比单纯间隙算术更值得控制;不能把方程调用次数占比直接当作耗时占比。
若接受步数暂按历史样本不变估算,8 个接触件每步检查一次是 `8 × 19412 = 155296` 次简单间隙检查;两个端点均重算则约为其两倍。间隙直接读取位置即可,不需要为这些检查各自求解气动物性、管道流量和全部输出。
另一方面,历史的 `stateTransitions=9` 不包含当时没有注册的 LSTP00A 接触/脱离,所以不能据此预测新增事件数量。8 个接触件可能长期保持接触,也可能反复接触和脱离;真正影响重启成本的是整个仿真期间的切换次数,以及是否出现数值反复触发。
不能将 12.414 s 除以启动次数来推算单次事件成本,也不能把平台求解时间与报告中的 Amesim 完整进程时间直接比较。
### 建议采用的成本控制措施
1. **专门生成轻量事件函数。** 直接读取位置/速度状态,批量计算间隙和需要的局部接触力;避免为了事件检查调用完整 `model_eval`。在不需要新增物理动态量时,不人为增加连续状态维度。
2. **先筛查,后定位。** 只对可能跨界的区间执行找根;同时覆盖区间内转向等情况,不能为了省时漏掉端点同号但中间跨界的情形。定位达到所需精度或浮点区间不再可分后停止,不必机械重复满 60 次。
3. **仅在阶段实际切换时处理。** 保持接触期间不反复重启;明确初始贴边、穿越方向和离开后的再次触发规则。不能以放大物理间隙、移动事件时间或粗暴忽略真实小振荡来换速度。
4. **同一时刻的多个事件批量提交、一次重启。** 包括多个接触件,以及与信号边界重合的情况;不得为了减少启动次数把有真实先后关系的事件强行合并。
5. **只注册启用模式所需的边界。** 当前八路的接触件均允许负力,不额外注册非负力截断事件;也不在 `Pdis` 或 `3*Pdis` 处造事件。
按上述实现,接触/脱离次数较少时,额外成本预期可控;频繁真实碰撞或边界数值反复触发时,成本可能明显增加。事件处理不会消除模型本身的高刚度,也不能保证 RK45 因此适合所有高刚度工况。
### 如何验证增量成本
完成实现后,在同一份 JSON、求解器、容差、最大步长和输出设置下,对修改前后分别重复运行,使用已构建的可执行文件,比较中位数并记录波动。分别记录纯求解时间、进程总时间、事件检查/定位次数、实际切换数、启动次数、接受步/拒步、`nfev`、`njev` 和线性 setup 次数。
平台 `solveSeconds` 不包含后续全部输出重放和 JSON 写出,所以事件点增多的输出成本还应检查总进程时间。现有 `nfev` 也不统计纯密集插值调用,新增定位计数/耗时才能解释这部分成本。
除当前八路模型外,至少检查长期不接触、持续接触、单次接触/脱离及反复碰撞四种情况;在事件时刻与接触力精度得到验证的前提下评估速度。本轮仅完成成本评估,没有修改机械事件实现或进行新增事件的性能实测。
@@ -414,6 +414,33 @@ def create(
9. 保持 SI、端口符号和适用的质量/能量守恒;按需编译及缓存由内容失效机制管理,测试完整模型命中、参数变化、模块变化及遗漏导出错误。
10. C 代码、构建依赖、进程与文件操作遵守[跨平台交付约定](跨平台交付约定.md)。分别记录 Linux 与 Windows 实际结果,不把 Linux 模拟测试写成 Windows 验收。
### 12.1 分段信号的时间事件
新增阶跃、脉冲或循环分段信号时,元数据注册不会自动建立时间事件。除信号值方程外,还须在原生生成器中提供下一事件时间,汇入 `model_next_break(t, end)`。默认接入 `extended.py`;若该型号也走 `compiler.py` 紧凑路径,则两条路径都须实现。连续光滑信号没有这项分段要求;由压力、位置等状态触发的切换需要相应状态事件支持,不能仅凭时间表处理。
- 求值与事件查询必须共享同一套绝对时间边界。可复用 UD00 的 `native_signal` / `native_signal_break`;避免一边以取模判断阶段,另一边独立计算事件。仅新增现有 UD00 实例或修改参数无需再次注册事件。
- 对 `t < end`,返回严格晚于 `t` 的最近边界,超过仿真终点或没有后续事件时返回 `end`。包含开始时刻、阶段端点、周期回绕,以及模型需要定位的斜率变化;同一时刻的零时长阶段按合同合并处理。
- UD00 使用右连续阶段规则:`t < boundary` 属于前一段,`t == boundary` 属于后一段。周期终点统一使用下一周期起点。不能用任意全局 epsilon 把真实边界前的时刻提前切换。
- 求值必须支持试探步、回退和结果重放;不得在普通 `model_eval` 调用中推进全局阶段索引。UD00 当前由时间纯函数查找阶段。需要离散状态的组件,应另行实现可回退、可重放的状态及事件提交机制。
- 运行库按最早边界分段积分,在内部边界左侧结束旧段,再于边界处保留连续状态并重新启动积分。新组件通常复用该流程,不为每个型号新增求解器分支。名义采样时间仍按其实际浮点值求值;“界面显示同一小数”不等于内部时间完全相等。
- 新内核函数同步公共声明及 `native_codegen/modules.py` 导出/依赖。测试边界前一个浮点数、边界本身及后一个浮点数,并覆盖延迟开始、周期回绕、多周期、退化参数和 BDF/RK45 最小系统的结果重放。参照 `tests/test_native_signal_boundaries.py`。
### 12.2 事件输出与重放
原生输出语义 version 2 保留实际时间的规则采样,并自动保存内部时间事件的左侧相邻浮点时刻及事件时刻。已有状态重置事件在当前积分区间可用时保存左邻插值状态,再保存重置后的状态;同一实际时间以后一次接受状态为准。时间序列保持严格递增,样本数量可能超过规则网格点数。初始即时事件不倒填时间,仿真终点不额外制造时间事件。
组件接入已有时间/状态事件接口后无需自行写入输出文件。结果重放必须仅依赖保存时间、完整状态和模型参数;模式影响输出时,必须有可保存、可回退、可重放的表示,不能读取运行结束时的全局模式。接触状态接口同样需要遵守这项约束。相关运行库合同见 [native/README.md](../../native/README.md),测试见 `tests/test_native_output_semantics.py`。
外部对照不能把显示为同一小数时刻当作同一事件侧。当前八路评估以全部分段常值信号核验驱动阶段,所有变量共用同一对保存样本;保留双方真实时间及无法配对记录。相同阶段也不等于实际时间完全相同,高刚度快速过程仍需进一步核验时间差的影响。
### 12.3 LSTP 接触状态事件
LSTP00A 已在两条原生生成路径默认注册:`native_codegen/contacts.py` 发出两端机械速度/位移状态索引、间隙、刚度、阻尼、阻尼距离、力符号模式及原公式运算顺序;`native/runtime/contact_events.h` 在接受步的密集插值上定位间隙过零,并在非负力模式定位接触区内的原始力过零。新增现有 LSTP 实例或修改其参数无需额外注册。
此接口只覆盖已审查的 LSTP 状态形式,不会因新组件名称或元数据相似而自动适用。新增类似组件时,须明确事件函数及依赖、根方向、初始贴边/切触、连续状态是否重置、同时事件和防重复提交规则;生成的事件表达式应与力公式保持一致,包括浮点运算顺序。检测不能悄悄引入整模型 RHS 求值或改变已有雅可比/物性复用。MASS 弹性限位及连续释放仍为独立实验,未在本次加入默认路径。
接触事件的运行期记录只用于定位及防止重复触发,不影响力求值。若新模式需要真正的离散物理状态,应按 12.2 保存和重放,不能借用这个定位记录作为不可重放的力开关。验证应包括解析或独立参考、步长变化、接触与脱离、力截断、无事件轨迹保持和 BDF/RK45;参考 `tests/test_native_contact_events.py`。
## 13. 模型实现示例
可参照 [气瓶声明](../../app/simulation/components/experimental/storage/cylinder.py)、[气腔声明](../../app/simulation/components/amesim/storage/chambers.py)、[C 数值模块](../../native/components/modules/) 与 [系统 C 生成器](../../app/simulation/native_codegen/extended.py)。完整开发顺序见 [注册示例与验证](component-registration-example-v1.md)。
+10 -1
View File
@@ -34,6 +34,14 @@ bash bat/setup-native-linux.sh
通用诊断包含 `stateCount`、`sampleCount`。`pressureFlow`、`stream` 属于 Python 后端的可选诊断;C 后端不报告未计算的方程残差,网页仅在该值存在时显示它。
### 事件输出语义(version 2)
原生结果的 `outputSemantics` 描述采样合同。规则网格保持实际浮点时间,不吸附到附近事件。内部时间事件额外保存 `nextafter(event, -INFINITY)` 和事件时刻:前者是积分段的实际左侧终点,后者沿用连续状态、使用事件后的信号。状态重置事件在可用的积分区间内额外保存左邻时刻的插值状态,再保存事件后的接受状态。同一实际时间仍以后一次接受状态为准;时间序列严格递增,采样总数可能超过规则网格点数。
输出根据保存时间及完整状态重放;已有摩擦离散模式随状态保存。事件左邻样本是有限精度下的左侧样本,不声称保存了同一数学时刻的精确左极限。初始时刻或当前步起点的即时状态重置没有新的左侧区间,不倒填样本。仿真终点不额外制造一个时间事件。纯求解模式不增加采样或模型输出求值;此改动不注册新的机械事件。
与外部结果对照时,必须核验事件侧别。八路评估脚本使用全部 STEP/UD00 输出核验驱动阶段,所有变量共用同一对原始样本行;仅允许在输出间隔的 `1e-7` 范围内匹配时间舍入误差。无法匹配的点明确报告,不跨跳变插值、不根据力或压力误差选择样本。这个规则不代表已验证未注册的接触模式。
原生运行库已在本机 GCC 8.1 / SUNDIALS 7.4.0 验证。构建需要对应 C 头文件、导入库和 DLL,单纯安装 Python 包不能代替这些文件。构建器根据环境变量或当前 Python 基础环境查找 SUNDIALS,根据环境变量或 PATH 查找 GCC。
## 仿真结果分块存储
@@ -103,7 +111,8 @@ Jacobian 构建内现已按完整输入的浮点位复用储气物性、PH 温
- 扩展编译器上限 1024 状态、16384 输出;无连续状态的信号系统使用隐藏常量状态驱动输出。气动网络必须有压力状态锚点;独立气腔之间不能无阻力直接相连。兼容固定管路容腔是已实现的合并例外。闭合未收敛或方程欠定时明确失败,不静默回退。
- 支持原生 RK45 与 CVODE BDF。CVODE 默认按可证明的结构启用着色差分,使用稠密线性求解;不支持分组的模型自动保留逐列差分。
- 网页和 Python CLI 默认 `rtol=1e-8`;生成的状态绝对误差限为质量 `1e-14 kg`、内能 `1e-8 J`、速度/位移 `1e-12`(各自 SI 单位)。独立 C 程序默认 `rtol=1e-6`,对照时应显式传入。CLI 可覆盖 rtol;本版不支持自定义 atol 或 first_step。不同积分器相同局部容差不保证全局曲线误差完全相同。
- 时间信号显式分段,塑性/反弹端挡用稠密插值定位并重启。试探 RHS 不修改已接受状态。柔性接触沿用现有分段力公式,不改变刚度或阻尼来提速。
- 时间信号显式分段,塑性/反弹端挡用稠密插值定位并重启。LSTP00A 默认定位间隙过零;非负力模式还定位接触区内的原始力过零。两个生成路径均从机械状态索引发出 `NativeContact` 描述,检测不调用整模型 RHS,接触事件不重置位移/速度。试探 RHS 不修改已接受状态,接触力仍用原分段公式。MASS 弹性限位及连续释放的独立实验没有合入默认路径。
- LSTP 的防重复触发记录仅在接受事件时提交,不参与 RHS、雅可比或输出重放;同一浮点时刻的接触事件合并重启,并沿用输出语义 v2 保存两侧。结果的 `contactEvents` 提供检查、密集插值、二分和接触/力截断计数。相对速度换向时分段检测同号间隙的中间过零;这依赖已解析的积分步,不能保证捕获一步内任意多次未解析振荡。专项见 `tests/test_native_contact_events.py`。
- 每任务独立进程,支持进度、取消及超时。进程崩溃不会作为成功返回,受控失败保留最后接受状态。
- 编译缓存位于 `app/data/native-builds/`:`models/<SHA>/` 保存完整模型,`objects/<SHA>/` 保存可跨模型复用的模块目标文件。按当前模型使用的元件函数及其依赖选择模块,最多并行编译 4 个缺失单元。预处理后的实际 C 内容、工具链和编译选项组成对象键;完整模型键另含生成源码、组件合同及链接依赖。模型数值参数仍特化入 C,但仅重编受到影响的单元;时间、步长、rtol、采样选项仍是运行参数。
- 完整模型默认预算 256 MiB、对象预算 128 MiB,可分别设置非负整数环境变量 `SIMULATION_NATIVE_MODEL_CACHE_MB`、`SIMULATION_NATIVE_OBJECT_CACHE_MB`。按目录最近使用时间执行 LRU;活跃构建/运行及最后一个单独超额的条目保留并报告超额,因此是安全软上限。预算计算受管理文件的逻辑字节,不含旧版根级缓存、锁及文件系统元数据。
+71 -27
View File
@@ -1,37 +1,81 @@
#include "kernels.h"
#include <math.h>
double native_signal(double t, double start, int stages, int cyclic, const double *data) {
if (t < start) return data[0];
double elapsed = t-start, duration = 0, offset = 0;
for (int i=0;i<stages;i++) duration += data[16+i];
if (cyclic && duration > 0) elapsed = fmod(elapsed,duration);
else if (elapsed >= duration) return data[8+stages-1];
for (int i=0;i<stages;i++) {
double d = data[16+i];
if (elapsed < offset+d || i == stages-1)
return d <= 0 ? data[8+i] : data[i] + (elapsed-offset)/d*(data[8+i]-data[i]);
offset += d;
typedef struct {
int stage;
double begin, next;
} SignalInterval;
/* Both queries must construct exactly the same absolute boundary. In
* particular, fmod(t-start,period) can still be below a stage offset at the
* double returned by the event query (e.g. 44 s for periods of .8+10 s). */
static double signal_boundary(double start, double offset, double cycle, double period) {
return cycle==0 ? start+offset : (start+offset)+cycle*period;
}
static int signal_cycle(double t, double start, double period, double *current, double *next) {
double lo=fmax(0,floor((t-start)/period));
if (!isfinite(lo)) return 0;
double hi=fmax(lo+1,nextafter(lo,INFINITY));
if (signal_boundary(start,0,lo,period)>t) {
hi=lo; lo=fmax(0,hi-1);
if (signal_boundary(start,0,lo,period)>t) lo=0;
} else {
/* Usually hi=lo+1 already brackets t. At coarse time resolution,
* several cycles can round to the same double: bracket/bisect them
* rather than advancing one cycle at a time or shifting t. */
while (signal_boundary(start,0,hi,period)<=t) {
lo=hi; hi=fmax(2*hi,nextafter(hi,INFINITY));
if (!isfinite(hi)) return 0;
}
}
return data[8+stages-1];
for (;;) {
double mid=floor(lo+(hi-lo)/2);
if (mid<=lo || mid>=hi) break;
if (signal_boundary(start,0,mid,period)<=t) lo=mid;
else hi=mid;
}
*current=lo; *next=hi;
return 1;
}
static SignalInterval signal_interval(double t, double start, int stages, int cyclic,
const double *data) {
if (t<start) return (SignalInterval){-1,start,start};
double offsets[9]={0};
for (int i=0;i<stages;i++) offsets[i+1]=offsets[i]+data[16+i];
double period=offsets[stages], cycle=0, next_cycle=0;
cyclic=cyclic && period>0 && isfinite(period);
if (cyclic && !signal_cycle(t,start,period,&cycle,&next_cycle))
return (SignalInterval){-2,NAN,NAN};
for (int i=0;i<stages;i++) {
/* A cycle's last endpoint is the next cycle's first boundary,
* never a separately rounded (start+period)+cycle*period. */
double right=cyclic && offsets[i+1]==period ? signal_boundary(start,0,next_cycle,period) :
signal_boundary(start,offsets[i+1],cycle,period);
if (t<right)
return (SignalInterval){i,signal_boundary(start,offsets[i],cycle,period),right};
}
return (SignalInterval){stages,0,INFINITY};
}
double native_signal(double t, double start, int stages, int cyclic, const double *data) {
SignalInterval interval=signal_interval(t,start,stages,cyclic,data);
if (interval.stage==-2) return NAN;
if (interval.stage==-1) return data[0];
if (interval.stage==stages) return data[8+stages-1];
int i=interval.stage;
double d=data[16+i];
if (d<=0) return data[8+i];
double fraction=fmin(1,fmax(0,(t-interval.begin)/d));
if (fraction==0) return data[i];
if (fraction==1) return data[8+i];
return data[i]+fraction*(data[8+i]-data[i]);
}
double native_signal_break(double t, double end, double start, int stages,
int cyclic, const double *data) {
double duration=0, offset=0, result=end;
for (int i=0;i<stages;i++) duration += data[16+i];
/* A cyclic endpoint is already the next cycle's start. Listing both can
create two nearly equal floating-point break times for one event. */
for (int i=0;i<stages+(cyclic?0:1);i++) {
double event=start+offset;
if (cyclic && duration > 0 && event <= t) {
double cycle=fmax(0,floor((t-event)/duration)+1);
event += cycle*duration;
if (event <= t) event += duration;
}
if (event > t) result=fmin(result,event);
if (i < stages) offset += data[16+i];
}
return result;
SignalInterval interval=signal_interval(t,start,stages,cyclic,data);
return interval.stage==-2 ? NAN : fmin(end,interval.next);
}
+11
View File
@@ -7,6 +7,9 @@
#ifndef NFRICTIONS
#define NFRICTIONS 0
#endif
#ifndef NCONTACTS
#define NCONTACTS 0
#endif
#define NATIVE_JACOBIAN_STATS_COUNT 3
typedef struct {
double start, stop, sample_step, max_step, rtol, timeout;
@@ -44,6 +47,13 @@ typedef struct {
char message_buffer[384];
int status; /* 0 completed, 1 cancelled, 2 failed */
const char *message;
/* Accepted-event latch only; never used by RHS or output force replay. */
#if NCONTACTS
double contact_last[2*NCONTACTS];
int contact_direction[2*NCONTACTS], contact_pending[2*NCONTACTS];
#endif
unsigned long contact_checks, contact_dense, contact_roots;
unsigned long contact_transitions, contact_clipping;
NativePropertyWarning property_warnings[6];
} NativeRun;
typedef int (*NativeDense)(void *context, double t, double *state);
@@ -55,6 +65,7 @@ int native_fail(NativeRun *run, const char *reason, const char *operation, const
int native_rhs(NativeRun *run, double t, const double *y, double *dy);
int native_jacobian_rhs(NativeRun *run, double t, const double *y, double *dy);
int native_append(NativeRun *run, double t, const double *y);
int native_time_boundary_samples(NativeRun *run, double boundary, const double *state);
int native_accept(NativeRun *run, double t, double next, const double *old,
const double *trial, NativeDense dense, void *context,
double *accepted_time, double *accepted_state);
+32 -5
View File
@@ -86,6 +86,16 @@ int native_append(NativeRun *r, double t, const double *y) {
return native_samples_append(r,t,y);
}
/* The integrators already stop at the left-adjacent double of an internal
* time event. Preserve that actual endpoint and the continuous state at the
* event, so output replay can evaluate BOTH forcing phases without snapping
* a regular-grid timestamp or changing integration history. */
int native_time_boundary_samples(NativeRun *r,double boundary,const double *state) {
if (!r->options.record_samples || boundary>=r->options.stop) return 1;
return native_samples_append(r,nextafter(boundary,-INFINITY),state) &&
native_samples_append(r,boundary,state);
}
/* Amesim helium data domains: EOS, ideal Cp/h, viscosity. Warnings describe
* the property use, not a component. Trial/Newton/Jacobian evaluations never
* publish warnings; replay of output samples is also side-effect free. */
@@ -166,12 +176,14 @@ static double locate_breakaway(NativeRun *r,int index,double left,double right,
}
#endif
#include "contact_events.h"
int native_accept(NativeRun *r, double t, double next, const double *old,
const double *trial, NativeDense dense, void *context,
double *accepted_time, double *accepted_state) {
double when[2*(NSTOPS+NFRICTIONS+1)], bounds[2*(NSTOPS+NFRICTIONS+1)];
double restitution[2*(NSTOPS+NFRICTIONS+1)], thresholds[2*(NSTOPS+NFRICTIONS+1)];
int indices[2*(NSTOPS+NFRICTIONS+1)], friction[2*(NSTOPS+NFRICTIONS+1)], count=0;
double when[2*(NSTOPS+NFRICTIONS+NCONTACTS+1)], bounds[2*(NSTOPS+NFRICTIONS+NCONTACTS+1)]={0};
double restitution[2*(NSTOPS+NFRICTIONS+NCONTACTS+1)]={0}, thresholds[2*(NSTOPS+NFRICTIONS+NCONTACTS+1)]={0};
int indices[2*(NSTOPS+NFRICTIONS+NCONTACTS+1)], friction[2*(NSTOPS+NFRICTIONS+NCONTACTS+1)], count=0;
for (int j=0;j<NSTOPS;j++) {
NativeStop s=model_stops[j]; int v=s.velocity_index, x=v+1;
double vt0=1e-12*fmax(fabs(old[v]),1), vt1=1e-12*fmax(fabs(trial[v]),1);
@@ -219,6 +231,9 @@ int native_accept(NativeRun *r, double t, double next, const double *old,
bounds[count]=restitution[count]=thresholds[count]=0;count++;
}
}
#endif
#if NCONTACTS
if(!contact_candidates(r,t,next,old,trial,dense,context,when,indices,friction,&count)) return -1;
#endif
double stop=next;
for (int i=0;i<count;i++) stop=fmin(stop,when[i]);
@@ -232,8 +247,17 @@ int native_accept(NativeRun *r, double t, double next, const double *old,
}
if (count) {
if (!dense(context,stop,accepted_state)) return -1;
/* Keep the last representable pre-event sample before applying a
* reset. Discrete modes are part of the saved state, never replayed
* from a mutable global mode. An event at the current step start has
* no new left interval; do not invent or back-date a sample. */
double before=nextafter(stop,-INFINITY);
if (r->options.record_samples && before>=t && before<stop) {
double sy[NSTATES];
if (!dense(context,before,sy) || !native_samples_append(r,before,sy)) return -1;
}
for (int i=0;i<count;i++) if (fabs(when[i]-stop)<=1e-12*fmax(fabs(stop),1)) {
if(friction[i]>=0) continue;
if(friction[i]!=-1) continue;
double incoming=accepted_state[indices[i]];
accepted_state[indices[i]]=fabs(incoming)<=thresholds[i]?0:-restitution[i]*incoming;
accepted_state[indices[i]+1]=bounds[i];
@@ -242,7 +266,7 @@ int native_accept(NativeRun *r, double t, double next, const double *old,
/* Stop/reverse only at an accepted event. The discrete mode travels
* with saved states so replayed results and Jacobian trials are pure. */
double drives[NFRICTIONS];
for(int i=0;i<count;i++) if(friction[i]<0 && fabs(when[i]-stop)<=1e-12*fmax(fabs(stop),1)) {
for(int i=0;i<count;i++) if(friction[i]==-1 && fabs(when[i]-stop)<=1e-12*fmax(fabs(stop),1)) {
for(int j=0;j<NFRICTIONS;j++) if(model_frictions[j].velocity_index==indices[i]) {
double velocity=accepted_state[indices[i]];
accepted_state[model_frictions[j].mode_index]=velocity>0?1:(velocity<0?-1:0);
@@ -254,6 +278,9 @@ int native_accept(NativeRun *r, double t, double next, const double *old,
accepted_state[f.velocity_index]=0;
accepted_state[f.mode_index]=fabs(drives[j])>f.breakaway_force ? (drives[j]>0?1:-1) : 0;
}
#endif
#if NCONTACTS
contact_commit(r,stop,accepted_state,when,indices,friction,count);
#endif
if (!native_append(r,stop,accepted_state)) return -1;
while (r->options.start+r->sample_index*r->options.sample_step<=stop) r->sample_index++;
+105
View File
@@ -0,0 +1,105 @@
/* Private implementation included by common.c. All probes use state-only
* descriptors: no model RHS, property evaluation, mode mutation or reset. */
#if NCONTACTS
#include <float.h>
static double contact_penetration(int j,const double *y) {
NativeContact c=model_contacts[j];
double x1=y[c.velocity1+1],x2=y[c.velocity2+1];
/* Match each lowering's existing force formula, including association. */
return c.subtract_first ? -(c.gap0+(x2-x1)) : -(c.gap0+x2-x1);
}
static double contact_velocity(int j,const double *y) {
NativeContact c=model_contacts[j];
return y[c.velocity1]-y[c.velocity2];
}
static double contact_value(int j,int kind,const double *y) {
if(kind==2) return contact_velocity(j,y);
double p=contact_penetration(j,y);
if(!kind) return p;
NativeContact c=model_contacts[j];
double fraction=c.depth>0 ? -expm1(-fmax(p,0)/c.depth) : 1;
return c.stiffness*p+fraction*c.damping*contact_velocity(j,y);
}
static double contact_locate(NativeRun *r,int j,int kind,double left,double right,
double sign,NativeDense dense,void *context) {
double y[NSTATES];
for(int k=0;k<60;k++) {
double mid=left+.5*(right-left);
if(mid<=left || mid>=right) break;
r->contact_dense++;r->contact_roots++;
if(!dense(context,mid,y)) return NAN;
double g=contact_value(j,kind,y);
if(!isfinite(g)) return NAN;
if(sign<0 ? g>=0 : g<=0) right=mid; else left=mid;
}
return right;
}
static double contact_bracket(NativeRun *r,int j,int force,double left,double right,
double a,double b,NativeDense dense,void *context,int *direction) {
int slot=2*j+force;
if(r->contact_direction[slot] && r->contact_last[slot]==left)
a=r->contact_direction[slot]*DBL_MIN;
if(a==0 || (a>0 ? b>0 : b<0)) return INFINITY;
*direction=a<0 ? 1 : -1;
return contact_locate(r,j,force,left,right,a,dense,context);
}
static double contact_candidate(NativeRun *r,int j,int force,double t,double next,
const double *old,const double *trial,
NativeDense dense,void *context,int *direction) {
double a=contact_value(j,force,old),b=contact_value(j,force,trial);
if(!isfinite(a) || !isfinite(b)) return NAN;
/* Resolve a same-sign endpoint excursion by splitting at velocity reversal.
* This assumes resolved steps, not arbitrarily many oscillations per step. */
if(!force) {
double va=contact_velocity(j,old),vb=contact_velocity(j,trial);
if((va<0 && vb>0) || (va>0 && vb<0)) {
double turn=contact_locate(r,j,2,t,next,va,dense,context),y[NSTATES];
if(!isfinite(turn)) return NAN;
r->contact_dense++;
if(!dense(context,turn,y)) return NAN;
double g=contact_value(j,force,y);
if(g==0 && ((a<0 && b<0) || (a>0 && b>0))) return INFINITY;
double found=contact_bracket(r,j,force,t,turn,a,g,dense,context,direction);
if(isfinite(found) || isnan(found)) return found;
return contact_bracket(r,j,force,turn,next,g,b,dense,context,direction);
}
}
return contact_bracket(r,j,force,t,next,a,b,dense,context,direction);
}
static int contact_candidates(NativeRun *r,double t,double next,const double *old,const double *trial,
NativeDense dense,void *context,double *when,int *indices,int *kinds,int *count) {
for(int j=0;j<NCONTACTS;j++) {
r->contact_checks++;
for(int force=0;force<2;force++) {
if(force && (model_contacts[j].signed_force==1 || contact_penetration(j,old)<=0)) continue;
int direction=0;
double at=contact_candidate(r,j,force,t,next,old,trial,dense,context,&direction);
if(isnan(at)) return 0;
if(isfinite(at)) {
if(force) {
double y[NSTATES];r->contact_dense++;
if(!dense(context,at,y)) return 0;
if(contact_penetration(j,y)<=0) continue;
}
int n=(*count)++;
when[n]=at;indices[n]=j;kinds[n]=force?-3:-2;
r->contact_pending[2*j+force]=direction;
}
}
}
return 1;
}
static void contact_commit(NativeRun *r,double t,const double *y,
const double *when,const int *indices,const int *kinds,int count) {
for(int i=0;i<count;i++) {
if(kinds[i]>-2 || when[i]!=t) continue;
int j=indices[i],force=kinds[i]==-3,slot=2*j+force;
r->contact_last[slot]=t;r->contact_direction[slot]=r->contact_pending[slot];
if(force) r->contact_clipping++; else r->contact_transitions++;
fprintf(stderr,"{\"phase\":\"mechanical-event\",\"kind\":\"%s\",\"index\":%d,\"time\":%.17g,\"direction\":%d,\"penetration\":%.17g,\"relativeVelocity\":%.17g}\n",
force?"force-clip":"lstp-contact",j,t,r->contact_direction[slot],
contact_penetration(j,y),contact_velocity(j,y));
}
}
#endif
+4
View File
@@ -366,6 +366,10 @@ int native_bdf(NativeRun *r) {
r->starts++;
}
}
if (!native_time_boundary_samples(r,boundary,N_VGetArrayPointer(y))) {
native_fail(r,"sample-storage-failure","native_time_boundary_samples","Cannot store time-event samples.");
goto cleanup;
}
t=boundary; r->final_time=t;
memcpy(r->final_state,N_VGetArrayPointer(y),NSTATES*sizeof(double));
double sample=r->options.start+r->sample_index*r->options.sample_step;
+8
View File
@@ -51,7 +51,15 @@ static int write_result(NativeRun *r, const char *path, const char *index_path)
fprintf(f,"{\"success\":%s,\"status\":",!r->status?"true":"false");
json_string(f,r->status==0?"completed":r->status==1?"cancelled":"failed");
fprintf(f,",\"message\":"); json_string(f,r->message);
fprintf(f,",\"contactEvents\":{\"checks\":%lu,\"denseCalls\":%lu,\"rootIterations\":%lu,\"transitions\":%lu,\"forceClipTransitions\":%lu}",
r->contact_checks,r->contact_dense,r->contact_roots,r->contact_transitions,r->contact_clipping);
fprintf(f,",\"propertyWarnings\":"); native_property_warnings_json(r,f);
fputs(",\"outputSemantics\":{\"version\":2,"
"\"regularSamples\":\"actual-time-no-snap\","
"\"internalTimeEvents\":\"left-adjacent-and-at-event\","
"\"stateEvents\":\"left-adjacent-when-available-and-post-reset\","
"\"sameTime\":\"last-accepted-state\","
"\"replay\":\"saved-time-and-state-including-discrete-modes\"}",f);
fprintf(f,",\"jacobianReuse\":{\"gasEvaluations\":%lu,\"gasReuses\":%lu",
r->jacobian_gas_evaluations,r->jacobian_gas_reuses);
const char *reuse_names[]={"ph","density","pipe"};
+1
View File
@@ -108,6 +108,7 @@ int native_rk45(NativeRun *r) {
if (impact) restart=1;
else memcpy(f,K[6],sizeof(f));
}
if (!native_time_boundary_samples(r,boundary,y)) return 0;
t=boundary; r->final_time=t; memcpy(r->final_state,y,sizeof(y));
double sample=r->options.start+r->sample_index*r->options.sample_step;
if (r->options.record_samples && sample<=t && sample<=r->options.stop) {
+256
View File
@@ -0,0 +1,256 @@
"""Verify each actual read and ordered write, then summarize the two cases."""
from collections import Counter
from pathlib import Path
import ctypes as c
import hashlib
import json
import struct
ROOT = Path(__file__).resolve().parents[2]
OUT = ROOT/'test/context-access-20260917'
class Medium(c.Structure):
_fields_ = [('real_helium', c.c_int)] + [(k, c.c_double) for k in ['R','cp','Tref','slope','mu','muT','S']]
class State(c.Structure):
_fields_ = [('medium', Medium)] + [(k, c.c_double) for k in ['p','T','h','rho','mu','isentropic_factor','isentropic_exponent']] + [('valid', c.c_uint),('temperatures',c.c_void_p),('jacobian',c.c_void_p)]
class Pipe(c.Structure):
_fields_ = [('medium',Medium)] + [(k,c.c_double) for k in ['p1','p2','T','diameter','length','roughness','flow']] + [('kind',c.c_int),('valid',c.c_int)]
class Context(c.Structure):
_fields_ = [('states',c.c_void_p),('count',c.c_size_t),('capacity',c.c_size_t),('temperatures',c.c_void_p),('jacobian',c.c_void_p)]
def doubles(value):
data = bytes.fromhex(value)
return list(struct.unpack('<'+'d'*(len(data)//8),data))
def value(data, typ):
if typ == c.c_double:
return struct.unpack('<d', data)[0]
return int.from_bytes(data, 'little')
def named(domain, offset, size):
typ = {'states':State, 'pipes':Pipe, 'context':Context}[domain]
slot, off = divmod(offset,c.sizeof(typ))
if size == c.sizeof(typ):
return slot, '*', None
for name, ft in typ._fields_:
begin = getattr(typ,name).offset
if begin == off and c.sizeof(ft) == size:
return slot,name,ft
if ft == Medium and begin <= off < begin+c.sizeof(ft):
for mn,mt in Medium._fields_:
if getattr(Medium,mn).offset+begin == off:
return slot,'medium.'+mn,mt
raise AssertionError((domain,offset,size))
def first_match(memory, query):
ctx = Context.from_buffer_copy(memory['context'])
key = doubles(query['key']); p, second = key[:2]
matches = []
for i in range(ctx.count):
st = State.from_buffer_copy(memory['states'],i*c.sizeof(State))
flag, actual = (1, st.T) if query['kind']=='PT' else (2, st.h)
if st.valid & flag and st.p == p and actual == second and st.medium.real_helium == query['mediumKind'] and all(getattr(st.medium,k)==v for (k,_),v in zip(Medium._fields_[1:],key[2:])):
matches.append(i)
return matches
def analyze_operation(events):
begin=events[0]; entry=events[1]
assert begin['stateSize']==c.sizeof(State) and begin['pipeSize']==c.sizeof(Pipe)
memory={k:bytearray.fromhex(entry[k]) for k in ['context','states','pipes']}
known={k:bytearray(b'\1'*len(v)) for k,v in memory.items()}
ctx=Context.from_buffer_copy(memory['context'])
memory['states'].extend(bytes((ctx.capacity-ctx.count)*c.sizeof(State)))
known['states'].extend(bytes((ctx.capacity-ctx.count)*c.sizeof(State)))
summary={k:begin[k] for k in ['jac','group','position','t','inputs']}
summary.update(entryCount=ctx.count,capacity=ctx.capacity,queries=[],allocations=[],writes=[],consumed=[],validTests=[],sameValueWrites=0,accessReads=0,accessWrites=0,scalar=[])
assert not ctx.temperatures, 'Non-NULL observer is outside this verified contract.'
summary['bindings']={'jacobian':ctx.jacobian or 0,'temperatures':ctx.temperatures or 0}
st74=State.from_buffer_copy(memory['states'],74*c.sizeof(State)) if ctx.count>74 else None
if st74: summary['state74']={'p':st74.p,'T':st74.T,'valid':st74.valid}
pending=None
for event in events[2:]:
kind=event['event']
if kind=='query':
assert pending is None
pending=dict(event, allMatches=first_match(memory,event), tested=[])
elif kind=='match':
assert pending and pending['kind']==event['kind']
matches=pending['allMatches']; expected=matches[0] if matches else -1
assert event['slot']==expected and event['hit']==bool(matches)
expected_scans=list(range(expected+1 if expected>=0 else pending['count']))
assert pending['tested']==expected_scans,(begin,pending)
summary['queries'].append(dict(kind=event['kind'],key=pending['key'],keyValues=doubles(pending['key']),mediumKind=pending['mediumKind'],hit=event['hit'],slot=event['slot'],allMatches=matches,scanCount=len(expected_scans)))
pending=None
elif kind in ['read','write']:
domain=event['domain']; offset=event['offset']; data=bytes.fromhex(event['value'])
previous=memory[domain][offset:offset+len(data)]
slot,field,typ=named(domain,offset,len(data))
if kind=='read':
assert previous==data,(begin,event,'read mismatch')
summary['accessReads']+=1
# Lookup reads are recorded in raw events; this list isolates
# fields consumed after selection, including observers/keys.
if domain!='context' and pending is None and event['function']!='same_medium':
summary['consumed'].append(dict(domain=domain,slot=slot,field=field,value=event['value'],function=event['function']))
else:
summary['accessWrites']+=1
equal=all(known[domain][offset:offset+len(data)]) and previous==data
summary['sameValueWrites']+=equal
memory[domain][offset:offset+len(data)]=data
known[domain][offset:offset+len(data)]=b'\1'*len(data)
summary['writes'].append(dict(domain=domain,slot=slot,field=field,value=event['value'],sameValue=equal,function=event['function']))
elif kind=='valid_test':
st=State.from_buffer_copy(memory['states'],event['slot']*c.sizeof(State))
assert st.valid & event['mask']==event['value']
if pending is not None:
pending['tested'].append(event['slot'])
else:
summary['validTests'].append(event)
elif kind=='allocate':
assert event['branch']=='append', 'Scratch requires additional object registration; fail closed.'
assert event['slot']==event['countAfter']-1
assert event['countAfter']==Context.from_buffer_copy(memory['context']).count
assert event['countAfter']<=event['capacity']
summary['allocations'].append(event)
elif kind=='snapshot':
assert event['phase']=='exit'
for domain in memory:
expected=bytes.fromhex(event[domain])
assert memory[domain][:len(expected)]==expected,(begin,domain,'write replay mismatch')
summary['exitCount']=event['count']
# Everything not written remains the live probe entry, checked
# across every byte of every existing slot and every pipe slot.
summary['replayExact']=True
elif kind=='end':
summary['outputs']=event['outputs']
summary['outputValues']=doubles(event['outputs'])
elif kind.startswith('scalar_'):
summary['scalar'].append(event)
else:
raise AssertionError(event)
assert pending is None and summary['replayExact']
# Snapshot replay alone cannot detect an omitted equal-valued write.
# Check the reviewed source's mandatory store sequence independently.
pipe_writes=[w for w in summary['writes'] if w['domain']=='pipes']
assert [w['field'] for w in pipe_writes]==['valid','medium','p1','p2','T','diameter','length','roughness','kind','flow','valid']
assert int.from_bytes(bytes.fromhex(pipe_writes[0]['value']),'little')==0
assert int.from_bytes(bytes.fromhex(pipe_writes[-1]['value']),'little')==1
assert pipe_writes[-2]['value']==summary['outputs']
assert all(w['slot']==(28 if begin['position']==52 else 0) for w in pipe_writes)
for allocation in summary['allocations']:
writes=[w for w in summary['writes'] if w['domain']=='states' and w['slot']==allocation['slot']]
assert [w['field'] for w in writes[:7]]==['*','medium','p','T','valid','temperatures','jacobian']
assert not [w for w in summary['writes'] if w['domain']=='states' and w['slot']<summary['entryCount']]
return summary
def compare_logical(b,p):
"""Pair hits by query order and appends by creation order, never slot ID."""
mapping={}
assert b['bindings']==p['bindings'], 'Observer/memo ownership changed within this Jacobian.'
for bq,pq in zip(b['queries'],p['queries']):
assert (bq['kind'],bq['key'],bq['mediumKind'],bq['hit'])==(pq['kind'],pq['key'],pq['mediumKind'],pq['hit'])
if bq['hit']:
assert mapping.setdefault(bq['slot'],pq['slot'])==pq['slot']
assert len(b['allocations'])==len(p['allocations'])
for ba,pa in zip(b['allocations'],p['allocations']):
assert mapping.setdefault(ba['slot'],pa['slot'])==pa['slot']
def normalized(items, translate, writes=False):
result=[]
for event in items:
domain,slot,field,v=event['domain'],event['slot'],event['field'],event['value']
if domain=='states' and translate:slot=mapping[slot]
if domain=='context' and field=='count':v='increment'
if field in ['jacobian','temperatures']:
assert int.from_bytes(bytes.fromhex(v),'little')==b['bindings'][field]
v='current_context.'+field
result.append((domain,slot,field,v))
return result if writes else set(result)
assert normalized(b['consumed'],True)==normalized(p['consumed'],False),(b['jac'],b['position'],'consumed')
assert normalized(b['writes'],True,True)==normalized(p['writes'],False,True),(b['jac'],b['position'],'ordered writes')
assert [(mapping[v['slot']],v['mask'],v['value']) for v in b['validTests']]==[(v['slot'],v['mask'],v['value']) for v in p['validTests']]
return mapping
def write_checklists(examples, summaries):
lines=['# Context 访问级清单:实测数据', '', '由 `analyze_context_access.py` 从访问事件生成。slot、Jacobian 和 position 均从 0 开始;group=-1 为 baseline。', '',
'以下以 Jacobian 200 为主案例,补充 position 52 的 PH 命中/未命中路径。完整逐次记录位于 `test/context-access-20260917/operations.json`;原始访问事件在 `audit/access.jsonl`。', '']
chosen=[s for s in summaries if (s['jac']==200 and s['group'] in [6,18]) or (s['jac'] in [1,2] and s['group']==18)]
for s in chosen:
lines += [f"## Jacobian {s['jac']},group {s['group']},position {s['position']}", '',
f"t={s['t']:.17g};count {s['entryCount']} → {s['exitCount']};capacity={s['capacity']};输出 `{s['outputValues'][0]:.17g}`;model evaluator 返回 `{s['evalReturn']}`。", '',
'### 查询与首次匹配', '', '| 顺序 | 类型 | 完整 key:p, T 或 h, R, cp, Tref, slope, mu, muT, S | medium kind | 首个匹配 slot | 扫描条数 |', '|---|---|---|---|---|---|']
for i,q in enumerate(s['queries']):
lines.append(f"| {i+1} | {q['kind']} | `"+', '.join(format(v,'.17g') for v in q['keyValues'])+f"` | {q['mediumKind']} | {q['slot'] if q['hit'] else 'miss'} | {q['scanCount']} |")
lines += ['', '### 实际选中后的字段读取', '', '| 域 / slot | 字段 |', '|---|---|']
fields={}
for r in s['consumed']:fields.setdefault((r['domain'],r['slot']),set()).add(r['field'])
for (domain,slot),names in sorted(fields.items()):lines.append(f"| {domain}[{slot}] | "+', '.join(f'`{n}`' for n in sorted(names))+' |')
lines += ['', '查询扫描另外按短路次序读取 `valid & PT/H`、`p`、`T/h`、medium;未匹配项的字段不等于被用于物性计算。', '', '### 选中后的 valid 测试', '', '| 顺序 | slot | mask | 结果 |', '|---|---|---|---|']
for i,v in enumerate(s['validTests']):lines.append(f"| {i+1} | {v['slot']} | {v['mask']} | {v['value']} |")
lines += ['', '### 必须保留的 probe 数据', '', f"保留入口 `states[0:{s['entryCount']}]` 的全部字段;此路径没有写已有物性条目。仅追加以下新条目,修改本 operation 的 pipe 槽;其余 pipe 槽保持 probe 值。", '', '### 有序写入动作', '', '| 顺序 | 目标 | 写入值 | 已知同值写入 |', '|---|---|---|---|']
for i,w in enumerate(s['writes']):
field=w['field'];data=bytes.fromhex(w['value'])
if field=='*':text='完整 struct 清零(显式写入)'
elif field=='medium':text='复制上述完整 medium'
elif field in ['temperatures','jacobian']:text='NULL' if not int.from_bytes(data,'little') else '当前 context 的 Jacobian memo 指针'
elif field in ['count','valid','kind']:text=str(int.from_bytes(data,'little'))
else:text=format(struct.unpack('<d',data)[0],'.17g')
lines.append(f"| {i+1} | `{w['domain']}[{w['slot']}].{field}` | {text} | {'是' if w['sameValue'] else '—'} |")
lines += ['', '新条目分配分支均为 positive/finite key 且 `count < capacity`;每次在当前 count 追加,再 count++。未初始化槽的 struct 首次清零不计入“已知同值写入”。', '',
'### Fallback 原因', '', '查询 key/首匹配逻辑条目不同、已消费字段或已测试 valid 位不同、容量不足转 scratch、observer 非空、pipe 命中分支改变,或无法建立无冲突的 slot 映射时,执行原 operation。此清单不授权放宽现有 guard。', '']
(ROOT/'tests/manual/context_access_checklists.md').write_text('\n'.join(lines),encoding='utf-8')
def main():
summaries=[]; events=[]; return_pending=[]; returns=Counter(); examples=[]
with (OUT/'audit/access.jsonl').open(encoding='utf-8') as stream:
for line in stream:
event=json.loads(line)
if event['event']=='eval_return':
assert return_pending
for item in return_pending: item['evalReturn']=event['result']
return_pending=[]; returns[event['result']]+=1
elif event['event']=='begin':
assert not events
events=[event]
else:
assert events
events.append(event)
if event['event']=='end':
item=analyze_operation(events);summaries.append(item);return_pending.append(item)
if item['jac']==200: examples.append(dict(summary=item,events=events))
events=[]
assert not events and not return_pending
by_key={(x['jac'],x['group'],x['position']):x for x in summaries}
cases={}
for group,pos in [(18,52),(6,16)]:
pairs=[]
for jac in range(896):
b=by_key[jac,-1,pos];p=by_key[jac,group,pos]
assert b['inputs']==p['inputs'] and b['outputs']==p['outputs'] and b['evalReturn']==p['evalReturn']==1
equal_query=[(q['kind'],q['key'],q['mediumKind'],q['hit']) for q in b['queries']]==[(q['kind'],q['key'],q['mediumKind'],q['hit']) for q in p['queries']]
mapping=compare_logical(b,p)
pairs.append(dict(jac=jac,queryKeysAndHitsEqual=equal_query,logicalReadsAndOrderedWritesEqual=True,slotMapping=mapping,entryCounts=[b['entryCount'],p['entryCount']],exitCounts=[b['exitCount'],p['exitCount']],slots=[[q['slot'] for q in b['queries']],[q['slot'] for q in p['queries']]],allocations=[[a['slot'] for a in b['allocations']],[a['slot'] for a in p['allocations']]],state74=[b.get('state74'),p.get('state74')]))
cases[f'group{group}_position{pos}']=dict(pairs=len(pairs),equalQueryKeysAndHits=sum(p['queryKeysAndHitsEqual'] for p in pairs),countShiftPairs=sum(p['entryCounts'][0]!=p['entryCounts'][1] for p in pairs),rows=pairs)
result=dict(operations=len(summaries),replayExact=len(summaries),reads=sum(s['accessReads'] for s in summaries),writes=sum(s['accessWrites'] for s in summaries),sameValueWrites=sum(s['sameValueWrites'] for s in summaries),evalReturns=dict(returns),cases=cases)
for name,obj in [('summary.json',result),('operations.json',summaries),('jacobian-200.json',examples)]:
(OUT/name).write_text(json.dumps(obj,ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
write_checklists(examples,summaries)
print(json.dumps({k:v for k,v in result.items() if k!='cases'},indent=2))
for name,case in cases.items(): print(name,{k:v for k,v in case.items() if k!='rows'})
if __name__=='__main__':main()
+191
View File
@@ -0,0 +1,191 @@
"""Join exact fallback census, provenance, and separately sampled timings."""
from pathlib import Path
from collections import Counter,defaultdict
import hashlib,json,statistics,struct,subprocess
from diagnose_context_fallback import ROOT,OUT,SOURCE,ex
def read(label,name='context.json'):return json.loads((OUT/label/name).read_text(encoding='utf-8'))
def lines(label,name):
with (OUT/label/name).open(encoding='utf-8') as f:
for line in f:yield json.loads(line)
def table(head,rows):return '\n| '+' | '.join(head)+' |\n| '+' | '.join(['---']*len(head))+' |\n'+'\n'.join('| '+' | '.join(map(str,r))+' |' for r in rows)+'\n'
def decode(h,floating):return struct.unpack('<d',int(h,16).to_bytes(8,'little'))[0] if floating else int(h,16)
def main():
plan=read('','plan.json');census=read('census');trace=read('provenance');pairs={(x['group'],x['region']):dict(x) for x in census['regions']}
original=json.loads((SOURCE.parent/'all-audit/probe.json').read_text(encoding='utf-8'))
assert sum(x['failures'] for x in pairs.values())==sum(x['contextMisses'] for x in original['groups'])==139776
assert sum(x['ops'] for x in pairs.values())==4620672
assert all(x['inputDiffOps']==x['outputDiffOps']==x['exitOutputDiff']==0 for x in pairs.values())
assert all(x['exitContextDiff']==x['failures'] for x in pairs.values())
assert all(x['failures'] in (0,896) for x in pairs.values())
regions={i:dict(id=i,start=a,end=b,contextual=plan['versions'][a]!=plan['versions'][b],groups=[],compares=0,failures=0,ops=0,seconds=0,operationSeconds=0,fields=Counter()) for i,(a,b) in enumerate(plan['regions'])}
opregion={}
for (g,r),x in pairs.items():
regions[r]['groups'].append(g)
for key in ('compares','failures','ops'):regions[r][key]+=x[key]
x['seconds']=0;x['operationSeconds']=0;x['fields']=Counter()
if x['failures']:
for pos in range(regions[r]['start'],regions[r]['end']):
assert (g,pos) not in opregion;opregion[g,pos]=r
ops={}
for g,pos,n,t in census['operations']:
r=opregion[g,pos];ops[g,pos]=dict(group=g,position=pos,operationId=plan['operations'][pos]['id'],region=r,count=n,seconds=0)
runs={};times={}
for kind,mode in [('regions',2),('ops',3),('functions',4)]:
runs[kind]=[read(f'{kind}-{i}') for i in range(3)];times[kind]=[read(f'{kind}-{i}','measurement.json') for i in range(3)]
assert all(x['mode']==mode and x['sampled']==112 for x in runs[kind])
for x in runs['regions']:
scale=896/x['sampled']/3/x['frequency']
for v in x['regions']:
value=v['regionTicks']*scale;pairs[v['group'],v['region']]['seconds']+=value;regions[v['region']]['seconds']+=value
for x in runs['ops']:
scale=896/x['sampled']/3/x['frequency']
for g,pos,n,t in x['operations']:
value=t*scale;r=opregion[g,pos];ops[g,pos]['seconds']+=value;pairs[g,r]['operationSeconds']+=value;regions[r]['operationSeconds']+=value
kernel_counts=Counter();kernel_reg=Counter()
for r,k,n,t,u in trace['kernels']:kernel_counts[k]+=n;kernel_reg[r,k]+=n
kernels={k:dict(name=name,module=module,count=kernel_counts[k],inclusiveSeconds=0,exclusiveSeconds=0) for k,(module,name) in enumerate(plan['kernels'])}
region_kernels=defaultdict(lambda:dict(count=0,inclusiveSeconds=0,exclusiveSeconds=0))
for (r,k),n in kernel_reg.items():region_kernels[r,k]['count']=n
for x in runs['functions']:
scale=896/x['sampled']/3/x['frequency']
for r,k,n,t,u in x['kernels']:
for key,val in [('inclusiveSeconds',t),('exclusiveSeconds',u)]:
kernels[k][key]+=val*scale;region_kernels[r,k][key]+=val*scale
origins=Counter();first_context_origins=Counter();field_totals=Counter();samples={};trace_count=0;causal=Counter();missing=[]
for x in lines('provenance','failures.jsonl'):
trace_count+=1;g=x['group'];r=x['region'];kind=x['kind'];fld=x['field']
field=['count','capacity','temperatures','jacobian'][fld] if kind==1 else trace['fields'][str(kind)][fld]['name'] if fld>=0 else 'padding'
full_field=field if kind==1 else f'{"states" if kind==2 else "pipes"}[{x["index"]}].{field}'
key=(g,r,kind,x['index'],fld,x['persistentOrigin'],x['firstContextOrigin']);origins[key]+=1
first_context_origins[g,x['firstContextOrigin']]+=1
field_totals[field]+=1;regions[r]['fields'][field]+=1;pairs[g,r]['fields'][field]+=1
pos=x['persistentOrigin'];first=x['firstContextOrigin']
if pos<0 or first<0:missing.append(x)
deps=set(plan['operations'][pos]['stateIndices'])&set(plan['groups'][g]['stateIndices']) if pos>=0 else set()
causal['directFieldOrigin' if deps else 'cacheSideEffectOrigin']+=1
rootdeps=set(plan['operations'][first]['stateIndices'])&set(plan['groups'][g]['stateIndices']) if first>=0 else set()
causal['firstContextHasStateDependency' if rootdeps else 'firstContextNoStateDependency']+=1
if (g,r) not in samples or x['jac']==200:
floating=kind>1 and fld>=0 and trace['fields'][str(kind)][fld]['floating']
samples[g,r]={**x,'fieldName':full_field,'baselineValue':decode(x['baseline'],floating),'trialValue':decode(x['trial'],floating),
'originName':plan['operations'][pos]['key'] if pos>=0 else str(pos),'originOperationId':plan['operations'][pos]['id'] if pos>=0 else pos,
'originPerturbedStates':[plan['stateKeys'][i] for i in sorted(deps)],'rootPerturbedStates':[plan['stateKeys'][i] for i in sorted(rootdeps)]}
assert trace_count==139776 and not missing,(trace_count,missing[:2])
assert field_totals==Counter(p=105741,count=34035)
assert sum(origins.values())==139776
# The census and provenance modes must have identical region-level counts.
for x in trace['regions']:
for k in ('attempts','compares','failures','ops','inputDiffOps','outputDiffOps','exitOutputDiff','exitContextDiff'):assert x[k]==pairs[x['group'],x['region']][k]
operation_totals={}
for (g,pos),x in ops.items():
if pos not in operation_totals:operation_totals[pos]=dict(position=pos,operationId=x['operationId'],name=plan['operations'][pos]['key'],count=0,seconds=0,groups=[],regions=[])
a=operation_totals[pos];a['count']+=x['count'];a['seconds']+=x['seconds'];a['groups'].append(g)
if x['region'] not in a['regions']:a['regions'].append(x['region'])
group_totals=[]
for g in range(27):
rr=[x for (gg,r),x in pairs.items() if gg==g];group_totals.append(dict(group=g,states=plan['groups'][g]['states'],
compares=sum(x['compares'] for x in rr),failures=sum(x['failures'] for x in rr),seconds=sum(x['seconds'] for x in rr),
failedRegions=[x['region'] for x in rr if x['failures']],ops=sum(x['ops'] for x in rr)))
hot=sorted((r for r in regions.values() if r['failures']),key=lambda r:-r['seconds']);hotops=sorted(operation_totals.values(),key=lambda o:-o['seconds'])
total=sum(r['seconds'] for r in hot);op_total=sum(o['seconds'] for o in hotops)
native_ops=[o for o in hotops if plan['versions'][o['position']]!=plan['versions'][o['position']+1]]
native_share=sum(o['seconds'] for o in native_ops)/op_total
for r in regions.values():
r['failedGroups']=[g for g in r['groups'] if pairs[g,r['id']]['failures']]
r['failureRate']=r['failures']/r['compares'] if r['compares'] else None
r['meanUs']=r['seconds']/r['failures']*1e6 if r['failures'] else 0
r['nativeOperationPositions']=[pos for pos in range(r['start'],r['end']) if plan['versions'][pos]!=plan['versions'][pos+1]]
# Production and pre-existing experimental source bytes are unchanged.
for directory in ('worker','kernels','trace-worker'):
b=read(directory,'build.json')
for name,h in b['sourceHashes'].items():assert hashlib.sha256((SOURCE/name).read_text(encoding='utf-8').encode()).hexdigest()==h
production=json.loads((SOURCE/'build-metadata.json').read_text(encoding='utf-8'))
for name,h in production['sourceHashes'].items():
assert hashlib.sha256((ex.builder.NATIVE/name).read_text(encoding='utf-8').encode()).hexdigest()==h
assert hashlib.sha256((ROOT/'tests/data/test-mql-8-corrected.json').read_bytes()).hexdigest()==production['inputSha256']
assert not subprocess.check_output(['git','diff','HEAD','--name-only'],cwd=ROOT,text=True).strip()
checked=[]
for path in sorted(OUT.glob('*/measurement.json')):
m=json.loads(path.read_text());checked.append(m)
result=dict(regions=list(regions.values()),groupRegions=list(pairs.values()),groups=group_totals,operations=list(operation_totals.values()),
groupOperations=list(ops.values()),kernels=list(kernels.values()),regionKernels=[dict(region=r,kernel=k,**v) for (r,k),v in region_kernels.items()],
provenance=[dict(group=k[0],region=k[1],kind=k[2],index=k[3],field=k[4],origin=k[5],firstContextOrigin=k[6],count=v) for k,v in origins.items()],
fieldCounts=field_totals,causalCounts=causal,samples=list(samples.values()),measurements=checked,
totals=dict(compares=sum(r['compares'] for r in regions.values()),failures=139776,regionSeconds=total,operationSeconds=op_total,nativeOperationShare=native_share))
ex.write(OUT/'analysis.json',result)
report=['**context fallback 定位报告**\n',
'本轮只新增独立诊断工具及worker副本,原lp_reuse比较/恢复实现、dependency graph、ordinary residual、物性算法、accepted-step check及线性求解器未改动。八路模型0–10s,BDF、rtol=1e-8,其他设置沿用前轮。以下区间ID与schedule位置均从0开始,范围使用[start,end),position与operation原始ID不同,映射保存在plan.json。\n']
report.append('**主要发现**\n')
report.append('139,776次回退全部具有相同特征:区间各operation的显式输入、输出均与baseline逐位一致,区间出口输出也一致;但出口context/cache全部仍不一致。首次失败原因仅为states[i].p(105,741次)或count(34,035次)。这支持“全context比较/整体snapshot恢复使局部无关区间回退”的判断,不支持“回退区间方程本身受到这些状态扰动”的判断。\n')
report.append('差异也不是假数据:前置受扰动operation确实会产生不同压力/温度的缓存条目;还有“前一operation改了缓存键→后一个物理输入不变的operation由命中变为新增条目→count变化”的间接链路。不能直接忽略这些差异并恢复整个baseline出口context,因为这会覆盖已有的真实扰动记录;本轮并未测试或声称该覆盖一定会改变最终解,也未证明放宽保护在任意模型上安全。\n')
report.append('**计数、覆盖范围与耗时口径**\n')
report.append(f'全量记录896个Jacobian、24,192次probe;495个可复用区间中127个包含context操作,发生160,384次context比较,139,776次失败({139776/160384:.2%}),涉及115个不同区间、156个group→区间组合。另有521,472次纯代数复用尝试不执行context比较,未混入比较分母。所有失败组合在896个Jacobian中均失败896次;其他组合全部成功。\n')
report.append('计数与字段来自全量census/provenance。所有139,776次失败均回溯到具体operation,无未定位记录。完整属性条目新建调用链、实际变化输入及数值样例在第0、200、450、700、895个Jacobian详细记录,覆盖约0、0.373、2.458、4.721、9.958s。字段原值同时保存十六进制浮点位,不以相对误差判定相同。\n')
report.append('耗时分三种独立模式,各3轮,每8个Jacobian分层抽1个,每轮112个,均完成完整仿真。区间计时从guard失败后的原计算开始,到区间执行结束为止,不包含context比较或字段日志;包含该区间原调度及诊断hook成本。operation计时包围原code语句;函数计时同时记录inclusive与扣除已插桩子函数后的exclusive。三种时间不能互相叠加。时间为原始插桩值,未扣空标记或强制缩放,极短操作和函数均值会高估;沿用前轮结论,不把细分时间当作精确优化收益。\n')
controls=[read(f'control-{i}','measurement.json') for i in range(3)]
report.append(table(['中位数','未插桩','区间抽样插桩','变化'],[(k,f'{statistics.median(x[k] for x in controls):.6f}',f'{statistics.median(x[k] for x in times["regions"]):.6f}',f'{statistics.median(x[k] for x in times["regions"])/statistics.median(x[k] for x in controls)-1:+.2%}') for k in ('jacobianSeconds','solveSeconds','solveCpuSeconds','processSeconds')]))
report.append(table(['模式,每种3轮','Jacobian中位数s','Jacobian最小–最大s','相对未插桩中位数','积分中位数s'],[(name,f'{statistics.median(x["jacobianSeconds"] for x in tt):.6f}',f'{min(x["jacobianSeconds"] for x in tt):.6f}–{max(x["jacobianSeconds"] for x in tt):.6f}',f'{statistics.median(x["jacobianSeconds"] for x in tt)/statistics.median(x["jacobianSeconds"] for x in controls)-1:+.2%}',f'{statistics.median(x["solveSeconds"] for x in tt):.6f}') for name,tt in [('未插桩',controls),('区间',times['regions']),('operation',times['ops']),('函数',times['functions'])]]))
report.append('这些小幅下降属于运行波动/编译布局差异,不能解释为插桩加速。函数模式中位Jacobian约增加4.84%,对抽中的短函数影响更大;其exclusive累计约0.879s,高于独立operation计时约0.660s。因此函数时间只用于热点排序和数量级判断,不作为无插桩下可节省时间的精确值。全量provenance为了追踪字段有意增加复制和日志,其Jacobian为4.801s、积分8.894s,完全不用于性能估计。\n')
report.append(f'区间回退累计时间折算约**{total:.6f}s**;operation模式独立测得约**{op_total:.6f}s**。两者来自不同插桩和样本,差值不能直接当作调度开销。\n')
report.append('**耗时最高的回退区间**\n')
report.append(table(['区间','schedule范围','group','比较/失败','失败率','累计ms','每次µs','占全部回退'],[(r['id'],f'[{r["start"]},{r["end"]})',','.join(map(str,r['failedGroups'])),f'{r["compares"]}/{r["failures"]}',f'{r["failureRate"]:.1%}',f'{r["seconds"]*1e3:.3f}',f'{r["meanUs"]:.3f}',f'{r["seconds"]/total:.2%}') for r in hot[:20]]))
report.append('全部127个context区间(包含成功区间)的比较次数、失败率、时间与组映射见intervals.md;纯代数区间也保存在analysis.json,但比较次数为0。\n')
report.append('**group分布与扰动变量**\n')
report.append(table(['group','比较/失败','累计回退ms','失败区间','扰动状态'],[(g['group'],f'{g["compares"]}/{g["failures"]}',f'{g["seconds"]*1e3:.3f}',','.join(map(str,g['failedRegions'])),', '.join(g['states'])) for g in group_totals]))
report.append(f'group0–9占失败次数{sum(g["failures"] for g in group_totals[:10])/139776:.2%},占回退时间{sum(g["seconds"] for g in group_totals[:10])/total:.2%};group10–25只有两个失败区间/组,但其区间较长,合计占时间{sum(g["seconds"] for g in group_totals[10:26])/total:.2%}。group26没有fallback。group10–25的对应缓存分叉由PNCH012的m/U扰动引起;同组若还含机械速度状态,不能仅凭共组就把cache差异归给该速度。详见originPerturbedStates。\n')
report.append('**完整证据链:直接压力变化**\n')
report.append('Jacobian 200,t≈0.372854446s,group18仅扰动amesim_pnch012_12.m。它改变p[43]和h[184];schedule position51(operation原始ID59,flow:amesim_pnl0001_16.port_1)的实际输入日志确认这两个值变化。调用链为native_pipe_flow_cached_context → native_pipe_flow_context → state_valve → isentropic → property_pt → property_new。详细trace覆盖上述被包装函数;isentropic/property_new的位置由源码补全,property_new日志直接记录新建条目。\n')
report.append('该operation新建的下游等熵条目states[74].p,baseline=15019640.749374540、probe=15019641.148960622 Pa;T也由460.10768147887495变为460.1076863719882 K。随后R475=[52,175)、R477=[313,452)入口首先在这个p字段不一致而失败。两区间实际执行的所有显式输入和输出均与baseline逐位一致。前置管路及其cache记录存在真实扰动依赖,但这两个候选复用区间的显式输入没有继续分叉;当前全context保护把前置变化传播成了这些区间的回退。\n')
report.append('对应R475累计/均值及R477累计/均值见上表和intervals.md;两者均每组896次,合并group18/19后各1792次。R490的对应链为group24/25 → amesim_pnch012_15.m/U → position48(id56,amesim_pnl0001_13.port_1)→ states[68].p → R490/R492;group24在同一时刻的p为15019642.069348963→15019642.468935065 Pa。\n')
report.append('**完整证据链:缓存新增导致count变化**\n')
report.append('同一Jacobian 200,group6包含amesim_pnl0001_9.m。position8(flow:amesim_pnor001_5.port_1)受其影响,baseline缓存slot6的键(p,T)=(15019652.421499353,290.99609647580155),probe变为(15019652.526950026,291.859408827332)。\n')
report.append('接着position10(flow:amesim_pnor001_6.port_1)的显式输入没有变化。它仍需要旧键(15019652.421499353,290.99609647580155):baseline在slot6命中,而probe该键已不存在,于native_temperature_ph_context → property_pt → property_new中新建slot8;其后另一个等熵条目也顺延。position10结束时count为9/10,后续R288入口为12/13,首次失败谓词是count。这里“首次写出count差异的operation10”不直接依赖扰动变量,真正上游原因是operation8改变了缓存内容。trace-created.jsonl保留两个调用链与键值,trace-inputs.jsonl证实operation10没有显式输入差异。\n')
report.append('group6一次同时扰动多个状态:该probe最早的任意context差异发生在position2,而上述特定旧键消失发生在position8,count开始持续不等发生在position10。这三个“首次”不能混同;firstContextOrigin只记录最早的任意context分叉,不自动证明它就是每一个后续字段的原因。特定键的因果链由上述新建条目日志另行确认。\n')
report.append(f'全量归因中,字段持续分叉来源直接含本group状态依赖的记录为{causal["directFieldOrigin"]:,},来源operation不含该group显式状态依赖的记录为{causal["cacheSideEffectOrigin"]:,};后者属于context/cache隐式影响,具体旧键→命中/新增→count链路以上述详细trace为例。所有139,776条记录所在probe的最早context分叉均出自图上受扰动影响的operation。这里只对五个时刻记录全部新建键调用链,未声称对每一条间接记录都完成了逐键因果回放。\n')
report.append('**fallback到底重算了什么**\n')
report.append(table(['position / 原始ID','operation','全量次数','累计ms','每次µs'],[(f'{o["position"]}/{o["operationId"]}',o['name'],o['count'],f'{o["seconds"]*1e3:.3f}',f'{o["seconds"]/o["count"]*1e6:.3f}') for o in hotops[:25]]))
report.append(f'包含native调用的{len(native_ops)}个不同operation贡献operation计时的{native_share:.2%},执行次数占{sum(o["count"] for o in native_ops)/4620672:.2%};其余大量线性、alias、stream等操作虽然次数多,但单次较便宜。最耗时的前20个operation占{sum(o["seconds"] for o in hotops[:20])/op_total:.2%},前40个占{sum(o["seconds"] for o in hotops[:40])/op_total:.2%}。完整operation逐组、逐区间归属保存在analysis.json。\n')
report.append('R490每次重跑135个operation,R457每次重跑147个operation;不是每个operation都需要context。最大连续“图上无关”区间只要跨过context操作,就采用一次入口整体比较,失败后连同纯代数部分一起执行。这是区间粒度带来的额外工作;是否值得进一步拆分仍需考虑比较、调度、恢复开销,本轮不据此修改。\n')
report.append('区间长也不必然更贵:R3仅18个operation,每次约23.876µs,高于135个operation的R490(18.865µs);R3集中执行管路流量。相反,很多区间的operation只是别名传播或代数赋值。因此需要同时看回退次数、操作构成和单次成本,不能只按区间长度判断。主要热点的逐区间字段、来源operation、回退operation及函数时间在hotspots.md中串联展示。\n')
report.append('**函数层调用次数与时间**\n')
report.append('exclusive已减去本表中被插桩的子调用;仍包含未插桩子函数、计时和包装开销。inclusive存在嵌套,禁止求和当作总时间。函数与operation/区间来自独立运行,不能叠加。\n')
report.append(table(['函数','全量fallback调用次数','inclusive ms','exclusive ms','exclusive µs/次'],[(k['name'],k['count'],f'{k["inclusiveSeconds"]*1e3:.3f}',f'{k["exclusiveSeconds"]*1e3:.3f}',f'{k["exclusiveSeconds"]/k["count"]*1e6:.3f}' if k['count'] else '—') for k in sorted(kernels.values(),key=lambda v:-v['exclusiveSeconds'])]))
report.append('state_valve的单次成本明显高于property_pt等查找函数;后者主要靠次数累积。native_temperature_ph、native_density和native_pipe_resistance在fallback内的实际调用均为0:已有Jacobian memo覆盖了这些求解,不能把本次fallback热点归因于重复PH反算、密度求解或管阻求根。仍发生的主要计算包括阀流量/等熵计算、黏度计算、物性上下文查找和缓存键查询。\n')
report.append('**正确性判断与边界**\n')
report.append('1. 对本模型和完整本次轨迹,未发现fallback区间存在未被dependency graph捕获的数值输出依赖;4,620,672次回退operation的显式输入及输出全部逐位一致。不能据此证明所有模型和输入都可忽略context。\n')
report.append('2. 已证实完整context比较覆盖了来自区间外的压力/缓存分配变化;已证实大区间失败会带动纯代数operation重算。这属于比较/区间粒度较粗以及cache副作用的保守传播。没有证据把它归为物性算法错误,或把fallback本身归为状态dependency graph过度保守。\n')
report.append('3. 原lp_reuse成功后恢复整个出口property states和全部pipe cache;本轮139,776次回退出口context均不等于baseline。直接放宽比较并调用原restore会覆盖这些差异,不能由“区间输出相同”推出“完整上下文恢复安全”。保持现有context逐位语义时,当前保护的回退符合实现约定;它不等价于这些方程数学上必须全部重算。\n')
report.append('4. 首次失败谓词只有count和states[i].p;由于原代码短路比较,这不代表其他property字段或pipe cache没有差异。本报告没有把“未成为首次失败原因”当作“始终相同”。\n')
report.append('**数值与原实现保护**\n')
report.append(f'本轮保存{len(checked)}份成功运行记录,均验证完整states/outputs/events二进制、warning、最终状态、步数和求解器计数,以及原context保护命中/回退计数不变。census、函数插桩验证轮与全量provenance轮另比较896个132×132矩阵及其(t,y),共15,611,904元素逐位一致。accepted/rejected=10840/918,Newton iterations=19371,nfev/njev/nlu=44467/896/3106。原实验和生产实现未修改。\n')
report.append('工具:tests/manual/diagnose_context_fallback.py、context_fallback_diag.h/.c、analyze_context_fallback.py。plan.json含每个operation原代码及inputs/outputs/stateIndices;analysis.json含完整区间/group/operation/函数计数与时间;provenance/failures.jsonl含每次失败字段、十六进制原值和来源operation;provenance/trace-created.jsonl及trace-inputs.jsonl给出五个时刻的新建条目调用链和变化输入。\n')
report.append('**代码证据与复现**\n')
report.append('[lp_reuse入口比较和整体restore](F:/Master/SystemSimulationApp/tests/manual/local_probe_support.c:69);[property_new/count与property_pt精确键查询](F:/Master/SystemSimulationApp/native/components/modules/properties.c:40);[PH context与Jacobian memo](F:/Master/SystemSimulationApp/native/components/modules/properties.c:93);[等熵及state_valve](F:/Master/SystemSimulationApp/native/components/modules/properties.c:244);[pipe flow/cache链路](F:/Master/SystemSimulationApp/native/components/modules/pipe.c:114)。\n')
report.append(f'已核验当前生产源文件与前轮编译元数据中的SHA-256一致,输入工程SHA-256一致;当前git diff HEAD为空。前轮编译提交为{production["commit"]}。新增文件仅为本轮独立诊断工具和产物。\n')
report.append('从仓库根目录使用.venv-win/Scripts/python.exe -B运行tests/manual/diagnose_context_fallback.py。prepare分别不带参数、带--kernels、带--kernels --trace,生成三个独立worker;run --label census --mode 1 --matrices进行全量计数;run --label provenance --mode 5 --kernels --trace --matrices进行全量字段追踪。计时分别为mode 2(区间)、mode 3(operation)、mode 4 --kernels(函数),均--stride 8;三轮seed分别为17–19、43–45、67–69。control使用--control --mode 0;三类计时独立串行运行,避免与编译重叠。最后运行tests/manual/analyze_context_fallback.py重建报告。\n')
(OUT/'report.md').write_text('\n'.join(report),encoding='utf-8')
detail=['**全部context区间统计**\n','编号为0基,schedule范围为[start,end)。时间来自三轮1/8抽样的原始区间计时。\n',table(['ID','范围','比较','失败','失败率','累计ms','µs/失败','失败group','首次字段计数'],[(r['id'],f'[{r["start"]},{r["end"]})',r['compares'],r['failures'],f'{r["failureRate"]:.2%}' if r['failureRate'] is not None else '—',f'{r["seconds"]*1e3:.4f}',f'{r["meanUs"]:.3f}',','.join(map(str,r['failedGroups'])),dict(r['fields'])) for r in regions.values() if r['contextual']])]
(OUT/'intervals.md').write_text('\n'.join(detail),encoding='utf-8')
# Readable joins for hot regions; complete machine-readable joins are in analysis.json.
selected=list(dict.fromkeys([r['id'] for r in hot[:10]]+[477,492,288]))
details=['**热点区间的完整对应关系**\n','以下计数全量,时间来自独立三轮抽样折算;字段值为Jacobian 200的样例,槽号和首次字段随时刻可能变化。各区间包含的所有operation连续位于[start,end),完整原代码在plan.json。字段来源与函数时间来自不同诊断模式,不能相加。\n']
for rid in selected:
r=regions[rid]
details.append(f'**R{rid},[{r["start"]},{r["end"]}),group {r["failedGroups"]}**\n')
details.append(f'比较{r["compares"]:,}次,失败{r["failures"]:,}次({r["failureRate"]:.1%});累计{r["seconds"]*1e3:.3f}ms,每次{r["meanUs"]:.3f}µs。全量首个失败字段分布:{dict(r["fields"])}。每次重算{r["end"]-r["start"]}个operation,其中{len(r["nativeOperationPositions"])}个包含native调用。\n')
ss=[samples[g,rid] for g in r['failedGroups']]
details.append(table(['group','本例首字段','baseline → probe','字段持续分叉position/ID','来源operation','与group交集的依赖状态'],[(s['group'],s['fieldName'],f'{s["baselineValue"]!r} → {s["trialValue"]!r}',f'{s["persistentOrigin"]}/{s["originOperationId"]}',s['originName'],', '.join(s['originPerturbedStates']) or '无;需追context/cache副作用') for s in ss]))
oo={}
for (g,pos),o in ops.items():
if o['region']!=rid:continue
a=oo.setdefault(pos,dict(count=0,seconds=0));a['count']+=o['count'];a['seconds']+=o['seconds']
ordered=sorted(oo.items(),key=lambda x:-x[1]['seconds'])
details.append(table(['前10个耗时operation的position/ID','operation','次数','累计ms','平均µs'],[(f'{pos}/{plan["operations"][pos]["id"]}',plan['operations'][pos]['key'],o['count'],f'{o["seconds"]*1e3:.4f}',f'{o["seconds"]/o["count"]*1e6:.4f}') for pos,o in ordered[:10]]))
kk=sorted([(k,v) for (rr,k),v in region_kernels.items() if rr==rid],key=lambda x:-x[1]['exclusiveSeconds'])
details.append(table(['该区间内函数','次数','inclusive ms','exclusive ms','exclusive µs/次'],[(kernels[k]['name'],v['count'],f'{v["inclusiveSeconds"]*1e3:.4f}',f'{v["exclusiveSeconds"]*1e3:.4f}',f'{v["exclusiveSeconds"]/v["count"]*1e6:.4f}' if v['count'] else '—') for k,v in kk]))
(OUT/'hotspots.md').write_text('\n'.join(details),encoding='utf-8')
print(json.dumps(dict(total=total,operationTotal=op_total,fields=field_totals,causes=causal,topRegions=[{k:r[k] for k in ['id','start','end','seconds','meanUs','failures']} for r in hot[:10]],nativeShare=native_share,top20Share=sum(o['seconds'] for o in hotops[:20])/op_total),ensure_ascii=False,indent=2))
if __name__=='__main__':main()
+109
View File
@@ -0,0 +1,109 @@
"""Audit the two staged shadow runs and write the result document."""
from collections import Counter
from pathlib import Path
import hashlib,json
ROOT=Path(__file__).resolve().parents[2]
OUT=ROOT/'test/context-shadow-20260917'
def read(path):return json.loads(path.read_text(encoding='utf-8'))
def audit(label):
folder=OUT/label;s=read(folder/'shadow-summary.json')
rows=[json.loads(line) for line in (folder/'shadow-trials.jsonl').read_text(encoding='utf-8').splitlines()]
negative=[json.loads(line) for line in (folder/'shadow-negative.jsonl').read_text(encoding='utf-8').splitlines()]
assert len(rows)==s['total']==s['accepted']==896
assert {r['jac'] for r in rows}==set(range(896))
assert all(r['reason']=='accepted' and r['referenceCount']==r['candidateCount'] for r in rows)
assert all(r['candidateCount']==r['probeCount']+r['appends'] for r in rows)
assert sum(r['baselineCount']!=r['probeCount'] for r in rows)==s['countDifferent']
assert sum(any(a!=b for a,b in r['mapping']) for r in rows)==s['slotRelocationTrials']
assert sum(sum(a!=b for a,b in r['mapping']) for r in rows)==s['relocatedSlots']
assert sum(r['appends'] for r in rows)==s['appends']
assert [sum(r['path']==i for r in rows) for i in range(4)]==s['paths']
assert s['mismatches']==s['liveContamination']==s['forbiddenNativeCalls']==s['rejected']==0
for key in ['tailEqual','liveTailEqual','memoImmutable','untouchedProbeEqual','metadataImmutable']:assert s[key]==896
assert len(negative)==s['negativePassed'] and all(n['reason']==n['expected'] and n['unchanged'] for n in negative)
assert not (folder/'first-mismatch.json').exists()
s['negativeReasonCounts']=dict(Counter(n['reason'] for n in negative))
s['example200']=next(r for r in rows if r['jac']==200)
s['measurement']=read(folder/'measurement.json')
return s
def main():
a=audit('r288');b=audit('position52')
worker_sha=hashlib.sha256((OUT/'worker/model.exe').read_bytes()).hexdigest()
assert a['validatedWorkerSha256']==b['validatedWorkerSha256']==worker_sha
assert a['measurement']['hashes']==b['measurement']['hashes']
build=read(OUT/'worker/build.json');assert build['wholeContextGuardUnchanged']
result=dict(r288=a,position52=b,workerSha256=worker_sha,productionPathChanged=False)
(OUT/'validation.json').write_text(json.dumps(result,ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
lines=['# 最小 context shadow replay 验证结果', '',
'## 1. Candidate 能否逐位复现 Reference', '',
'**可以,在本轮两个 operation 的已覆盖路径和严格前置条件下,1,792 次 shadow replay 全部通过,出口不一致为 0。** R288 先完成全部 896 次;同一 worker 二进制通过该阶段后,才运行 position 52 的 896 次。', '',
'参考答案始终是**同一个当前 probe 入口的独立深拷贝,真实执行原 operation 后的出口**。没有把 baseline 出口当参考。baseline 只提供本 Jacobian 内的局部语义记录,不提供用于恢复的完整 context。', '',
'| 统计 | group 6 / R288 / position 16 | group 18 / R475 / position 52 |', '|---|---:|---:|']
for title,key in [('shadow 总次数','total'),('可重放','accepted'),('自然轨迹 reject','rejected'),('Reference/Candidate 不一致','mismatches'),('入口 count 不同','countDifferent'),('发生 slot relocation 的 probe','slotRelocationTrials'),('重定位逻辑 slot 数量','relocatedSlots'),('追加条目总数','appends'),('PT hit','ptHit'),('PT miss','ptMiss'),('PH hit','phHit'),('PH miss','phMiss'),('独立后续 evaluator 返回值/dy/w/context 一致','tailEqual'),('后续 evaluator 与主仿真返回值/dy/w 一致','liveTailEqual'),('memo 条目只读验证','memoImmutable'),('未写入的 probe 数据保持不变','untouchedProbeEqual'),('局部 metadata 未被 Reference/Candidate 改写','metadataImmutable'),('Candidate 物理 native 调用','forbiddenNativeCalls'),('主 context/浮点环境污染','liveContamination')]:
lines.append(f'| {title} | {a[key]} | {b[key]} |')
lines += ['', '### 如何保证比较有意义', '',
'1. 在目标 probe 的 operation 前,深拷贝 property states、全部 pipe 槽、scalar memo 的全部 entries 和计数,分别绑定到 Reference/Candidate 私有存储。两路没有共享可写 context。',
'2. Reference 调用该位置的原始表达式。Candidate 只解释 baseline 捕获的 query/read/valid/allocate/write/OR/scalar-get 语义;物理 native 入口有运行时禁入检查。Reference 执行期间不能补写 Candidate 的记录。',
'3. Candidate 从自己的 probe 副本建立 overlay。重新扫描当前有序 entries,采用原 PT/PH 的 exact `==` 和 medium 比较语义找 first-match;用逻辑 ID 映射结果,追加使用当前 count。',
'4. 所有条件成功后进入无失败分支的 commit,仅按记录顺序更新实际写入字段、valid OR、count、指定 pipe 槽、memo 命中计数及显式输出;不复制 baseline context,也不覆盖 probe 未写字段。reject 前没有向 Candidate 或输出提交任何存储。',
'5. 逐位比较输出、所有 active property 字段、valid bits、全部 pipe 字段、memo entries/计数、warning observer、errno 和 x87/SSE 环境。私有指针按“绑定到各自当前 owner”检查;不要求两个独立 allocation 的地址相等。数值字段没有容差或近似比较。',
'6. 两个独立出口继续执行同一份原 evaluator 后续代码,实测返回状态、完整 dy/w、context 和 memo 计数一致;再与主仿真的真实 evaluator 返回值/dy/w 核对。主仿真仍然执行原 operation,没有采用 Candidate 输出,没有进入真实 skip 路径。',
'7. 两轮主仿真的状态、输出、事件及全部 896 个 132×132 Jacobian 仍与未插桩基线逐字节一致。whole-context guard 源码哈希保持一致。', '',
'operation 本身返回一个 double,没有单独的 int 成功码;报告中的 evaluator 状态来自两条后续执行路径,不是用 `isfinite(output)` 代替。', '',
'### 两个具体入口例子(Jacobian 200)', '', '| 项目 | R288 | position 52 |', '|---|---|---|']
for title,key in [('baseline count','baselineCount'),('当前 probe count','probeCount'),('Reference 出口 count','referenceCount'),('Candidate 出口 count','candidateCount'),('逻辑 slot → 当前 slot','mapping')]:lines.append(f'| {title} | `{a["example200"][key]}` | `{b["example200"][key]}` |')
lines += ['', 'R288 保留 probe 原 slot 12,在 13、14 追加;position 52 保留 probe slot 74 的压力/温度差异,查询重新扫描后仍 miss,再在 75、76 追加。', '',
'## 2. 哪些路径已经可以 replay', '', '| 查询路径 | R288 次数 | position 52 次数 |', '|---|---:|---:|']
for i,name in enumerate(['近零流量,无物性查询','PT miss → PT miss','PH miss → PT miss → PT hit → PT miss','PH hit → PT hit → PT miss']):lines.append(f'| {name} | {a["paths"][i]} | {b["paths"][i]} |')
lines += ['',
'以上“可 replay”指本轮相应 operation 的完整 0–10 s 轨迹,且所有 runtime guard 同时成立。PH miss 的 h 登记与 valid OR、后续对刚追加逻辑条目的 PT hit 都保留。近零流量不追加物性条目,但仍执行 pipe 的有序写入,包括 valid=0 的同值写入。', '',
'R288 的 638 次 count 差异中,67 次属于无查询/无追加路径,因此只有 571 次发生实际 slot relocation。position 52 同理,24 次 count 差异中有一次无追加。不能把 count 差异次数当作重定位次数。', '',
'## 3. 哪些情况仍必须 reject / fallback', '',
'自然轨迹中各 reject 分类均为 0。为防止“全成功但拒绝机制无效”,另在私有副本中执行下列负例,要求 reject 且整个 Candidate 与输出 sentinel 逐字节不变;这些不计入自然轨迹的 896 次。', '',
'| reject 分类 | R288 负例通过次数 | position 52 负例通过次数 |', '|---|---:|---:|']
for reason in sorted(set(a['negativeReasonCounts'])|set(b['negativeReasonCounts'])):lines.append(f'| `{reason}` | {a["negativeReasonCounts"].get(reason,0)} | {b["negativeReasonCounts"].get(reason,0)} |')
lines += ['',
'严格拒绝边界:', '',
'- 记录不属于当前 Jacobian/operation、源代码不再匹配已验证 worker、记录溢出或未知语义事件。',
'- 输入改变、first-match 逻辑映射冲突、hit/miss 路径改变、已消费字段或 valid 掩码结果不一致。',
'- 容量不足或 scratch、非空 observer、未覆盖的已有 entry 原地更新、pipe 命中分支变化、非有限结果或未覆盖路径。',
'- memo owner/lifetime/recording 不符合只读约束、所需 scalar key 未命中或值不匹配。Candidate 不调用物理 fallback 来弥补 memo miss。',
'- 舍入模式、SSE 控制模式或所需异常状态不满足已验证条件;已记录的非零 errno 前置条件不成立。', '',
'负例包括第一次追加完成后第二次容量检查失败、末尾未知事件、末尾非有限输出,因此覆盖了 overlay 已发生大量修改后的回滚,不只是入口早退。position 52 的 PH-hit 样本还直接改变 probe 已有条目的 rho 和 MU 位,确认消费值/valid guard 生效。', '',
'Jacobian memo 的 entries 始终只读,但 Reference 的 scalar get 会增加 reuses 计数。Candidate 在 overlay 中验证同一 bit-key 查找结果,并在 commit 中重放对应计数增量。没有将 baseline 的 recording/put 副作用照搬到 probe。', '',
'验证期间发现并修复了 Windows 诊断隔离问题:该工具链的 `fesetenv` 不完整恢复 SSE 控制寄存器。负例现在保存/恢复完整 x87/SSE 环境,并验证 SSE 单独改变时会拒绝。修复前失败证据保存在 `test/context-shadow-20260917/env-restore-investigation/`;本报告仅使用修复后的同一构建全量重跑结果。', '',
'## 4. Runtime metadata 需要多少', '', '| 当前实现 | R288 | position 52 |', '|---|---:|---:|',
f'| 单条语义事件 | {a["eventBytes"]} B | {b["eventBytes"]} B |',
f'| 最大事件数 | {a["eventMax"]} | {b["eventMax"]} |',
f'| 每条 operation 记录实际使用范围 | {a["metadataMin"]}–{a["metadataMax"]} B | {b["metadataMin"]}–{b["metadataMax"]} B |',
f'| 每阶段为 512 个事件预留 | {a["metadataReserved"]} B | {b["metadataReserved"]} B |', '',
'记录包括:Jacobian/operation 身份、四个显式输入、出口值、query 完整 key/逻辑命中关系、实际消费的字节值与 valid 掩码、创建顺序、字段更新/OR、scalar key/value、memo 绑定及环境前置条件。**不保存 baseline 完整 property/pipe context 用于 replay。** 只保留当前 Jacobian 的一条目标 operation 记录。', '',
f'事务 scratch 另需一个 {a["contextCopyBytes"]} B 的 overlay;本诊断为入口、Reference、Candidate、回滚检查及双路后续比较共保留 7 个 context 副本、2 个 {a["frameCopyBytes"]} B frame。它们是 shadow 验证工作内存,不能算作未来 skip 每条记录都必须长期保存的 metadata。slot 映射临时表为 256 个 int(当前 ABI 1,024 B)。', '',
'这是当前保守事件表示的实测大小,未做去重或压缩,也没有据此评价性能。', '',
'## 5. 是否具备最小真实 skip 实验的条件', '',
'**已具备针对这两个单独 operation、上述四种已验证路径的下一阶段实验条件。** 下一步必须继续保持严格 guard、事务 commit、reject 后原执行以及独立 Reference 抽查/全量对比;先对 R288 单点实验,再单独考虑 position 52。', '',
'本轮没有实现真实 skip,也没有移除或放宽 whole-context guard。结论不适用于整个 R475、全部 reuse interval、新模型、其他 native kernel、容量耗尽/observer 非空/已有 entry 原地更新等未验证分支;Linux 尚未运行本实验。双路执行和详细检查的时长不作性能证据。', '',
'## 复现与证据', '',
'依赖上一轮保留的 access worker 和未插桩 baseline 文件。准备阶段核对 access worker 源码哈希;第二阶段核对第一阶段通过的 worker 二进制 SHA-256。', '',
'```powershell',
r'.venv-win\Scripts\python.exe tests/manual/diagnose_context_shadow.py prepare',
r'.venv-win\Scripts\python.exe tests/manual/diagnose_context_shadow.py run --position 16',
r'.venv-win\Scripts\python.exe tests/manual/diagnose_context_shadow.py run --position 52',
r'.venv-win\Scripts\python.exe tests/manual/analyze_context_shadow.py',
'```', '',
'生成文件位于 `test/context-shadow-20260917/`:`worker/build.json`、各阶段 `shadow-trials.jsonl`、`shadow-summary.json`、`shadow-negative.jsonl`、`measurement.json`,以及汇总的 `validation.json`。若出现不一致,会输出首个不同字段和完整入口/两路 context、frame、metadata 到 `first-mismatch.json`。最终两阶段均未生成该文件。', '',
f'最终 worker SHA-256:`{worker_sha}`。', '',
'全量主轨迹哈希(两阶段相同):', '']
for name,digest in a['measurement']['hashes'].items():lines.append(f'- {name}: `{digest}`')
target=ROOT/'tests/manual/context_shadow_report.md';target.write_text('\n'.join(lines)+'\n',encoding='utf-8')
print(json.dumps(dict(total=a['total']+b['total'],accepted=a['accepted']+b['accepted'],mismatches=0,negativePassed=a['negativePassed']+b['negativePassed'],report=str(target)),ensure_ascii=False,indent=2))
if __name__=='__main__':main()
@@ -0,0 +1,244 @@
"""Read archived profiling only; write a reproducible fallback cost inventory.
No worker build, simulation, native code edit, or extrapolated speedup claim.
Run from any directory with the repository's Python interpreter.
"""
from collections import defaultdict
import hashlib
import json
from pathlib import Path
import re
import statistics
ROOT = Path(__file__).resolve().parents[2]
SOURCE = ROOT / "test/context-fallback-20260917"
OUT = ROOT / "test/fallback-profitability-20260917"
DOCS = ROOT / "tests/manual"
J = 896
MANIFEST = {}
def read(path):
raw = path.read_bytes()
MANIFEST[path.relative_to(ROOT).as_posix()] = hashlib.sha256(raw).hexdigest()
return json.loads(raw)
def close(a, b):
assert abs(a - b) <= 1e-12 * max(1, abs(a), abs(b)), (a, b)
def table(headers, rows):
def cell(x):
return str(x).replace("|", "\\|").replace("\n", " ")
return "\n| " + " | ".join(headers) + " |\n| " + " | ".join(["---"] * len(headers)) + " |\n" + "\n".join("| " + " | ".join(map(cell, r)) + " |" for r in rows) + "\n"
def labels(xs, prefix=""):
return ",".join(prefix + str(x) for x in xs)
def span_stats(xs):
return dict(rounds=xs, mean=statistics.mean(xs), median=statistics.median(xs), min=min(xs), max=max(xs))
def main():
a = read(SOURCE / "analysis.json")
plan = read(SOURCE / "plan.json")
typed = read(ROOT / "test/r288-typed-replay-20260917/performance-summary.json")
layered = read(ROOT / "test/local-probe-profile-direct-20260917/comparison.json")
P = typed["metrics"]["C"]["stages"]["median"]
H = typed["paired"]["C"]["metadataIncrementUs"]["median"]
def budgets(c, m):
# Strict inequality is required for a positive saving. Negative budgets
# are retained: they mean no nonnegative implementation cost can fit.
return dict(originalUs=c, structuralReusePerJacobian=m,
captureMaxAtZeroProbeUs=m*c,
captureMaxAtProbeUs={str(p): m*(c-p) for p in (0.1, 0.25, 0.5, 2, 5, 10, P)},
probeMaxAtTypedCaptureUs=c-H/m,
typedZeroCapturePossible=c>P,
typedWithSharedCapturePossible=c>P+H/m,
typedScenarioNetMs=J*(m*(c-P)-H)/1000)
native = {}
for pos, lines in enumerate(plan["code"]):
native[pos] = sorted(set(re.findall(r"\b(native_\w+)\s*\(", "\n".join(lines))))
for op in a["operations"]:
pos = op["position"]
assert bool(native[pos]) == (plan["versions"][pos] != plan["versions"][pos+1])
pairs = {(x["group"], x["region"]): x for x in a["groupRegions"] if x["failures"]}
gos = {(x["group"], x["position"]): x for x in a["groupOperations"]}
assert len(pairs) == 156 and len(gos) == len(a["groupOperations"]) == 5157
assert sum(x["failures"] for x in pairs.values()) == 139776
assert sum(x["count"] for x in gos.values()) == 4620672
assert all(x["failures"] == J for x in pairs.values())
assert all(x["inputDiffOps"] == x["outputDiffOps"] == x["exitOutputDiff"] == 0 and x["exitContextDiff"] == J for x in pairs.values())
region_runs, op_runs = [], []
for i in range(3):
r = read(SOURCE / f"regions-{i}/context.json")
o = read(SOURCE / f"ops-{i}/context.json")
assert r["sampled"] == o["sampled"] == 112
region_runs.append({(v["group"], v["region"]): v["regionTicks"] * J/r["sampled"]/r["frequency"] for v in r["regions"]})
op_runs.append({(g, pos): ticks * J/o["sampled"]/o["frequency"] for g, pos, n, ticks in o["operations"]})
for key, x in pairs.items():
close(x["seconds"], statistics.mean(r[key] for r in region_runs))
for key, x in gos.items():
close(x["seconds"], statistics.mean(o[key] for o in op_runs))
def composition(ops):
ns = sum(x["seconds"] for x in ops if native[x["position"]])
alg = sum(x["seconds"] for x in ops if not native[x["position"]])
return dict(nativeContainingOperationSeconds=ns, pureAlgebraAliasOperationSeconds=alg)
operations = []
for x in sorted(a["operations"], key=lambda x: -x["seconds"]):
pos = x["position"]
members = [o for (g, p), o in gos.items() if p == pos]
close(x["seconds"], sum(o["seconds"] for o in members))
assert x["count"] == len(x["groups"])*J
calls = native[pos]
kernel_family = any(c in calls for c in ("native_pipe_flow_cached_context", "native_medium_orifice_context"))
operation = dict(**x, nativeCalls=calls, code=plan["code"][pos],
nativeOperationCount=int(bool(calls)), operationCount=1,
contextFreeAlgebraAlias=not calls,
decision="A;停止R288后续优化" if pos == 16 else "A;E只测纯数值尾部" if kernel_family else "A" if calls else "A;C合并代数段",
baselineSharing="显式输入/输出同值;跨组记录共享仅为结构上限,消费字段/分支未普遍验证",
budget=budgets(x["seconds"]*1e6/x["count"], len(x["groups"])),
roundSeconds=span_stats([sum(run[g, pos] for g in x["groups"]) for run in op_runs]),
groupMeanUsRange=[min(o["seconds"]*1e6/o["count"] for o in members), max(o["seconds"]*1e6/o["count"] for o in members)],
functionTimingScope="仅有 regionKernels 的区间函数归因,无本 position 专属函数计时",
**composition(members))
operations.append(operation)
regions, group_regions = [], []
for r in sorted((r for r in a["regions"] if r["failures"]), key=lambda r: -r["seconds"]):
rid = r["id"]
members = [x for x in gos.values() if x["region"] == rid]
comp = composition(members)
close(r["operationSeconds"], sum(comp.values()))
native_positions = [p for p in range(r["start"], r["end"]) if native[p]]
assert native_positions == r["nativeOperationPositions"]
count = r["end"]-r["start"]
kernels = sorted((dict(name=plan["kernels"][k["kernel"]][1], **k) for k in a["regionKernels"] if k["region"] == rid), key=lambda k: -k["exclusiveSeconds"])
m = len(r["failedGroups"])
op_us = r["operationSeconds"]*1e6/r["failures"]
decision = "A" if count == 1 else "C/E预算筛选;native默认A"
if rid in (3, 480, 485, 490):
decision += ";D仅融合区间的待证假设"
region = dict(**r, operationCount=count, nativeOperationCount=len(native_positions),
nativeCalls=sorted(set(c for p in native_positions for c in native[p])),
kernels=kernels, decision=decision, **comp,
baselineSharing="同一 baseline 区间结构可供多个组引用;语义兼容未证" if m>1 else "仅一个失败组,无跨失败组摊销",
budget=budgets(r["meanUs"], m), operationModeBudget=budgets(op_us, m),
bothModesTypedWithSharedCapturePossible=min(r["meanUs"], op_us)>P+H/m,
roundMeanUs=span_stats([sum(run[g, rid] for g in r["failedGroups"])*1e6/r["failures"] for run in region_runs]))
regions.append(region)
for g in r["failedGroups"]:
x = pairs[g, rid]
group_regions.append(dict(**x, operationCount=count, nativeOperationCount=len(native_positions),
nativeCalls=region["nativeCalls"],
functionTimingScope=f"R{rid}汇总,无group级函数拆分", decision=decision,
**composition([v for v in members if v["group"] == g]),
budget=budgets(x["seconds"]*1e6/J, m),
# The net over J*m applies to a homogeneous-cost
# scenario, not the measured total for this group.
sharedBudgetNote="m为整个interval共享上限;本group成本的预算是假设同成本的情景,不是全interval实测净收益",
roundMeanUs=span_stats([run[g, rid]*1e6/J for run in region_runs])))
assert len(regions) == 115 and len(operations) == 343
close(sum(r["seconds"] for r in regions), a["totals"]["regionSeconds"])
close(sum(o["seconds"] for o in operations), a["totals"]["operationSeconds"])
close(sum(r["pureAlgebraAliasOperationSeconds"] for r in regions), sum(o["pureAlgebraAliasOperationSeconds"] for o in operations))
cohorts = []
for p in (2, 5, P, 10):
for capture in (0, H):
eligible = [r for r in regions if r["meanUs"] > p+capture/r["budget"]["structuralReusePerJacobian"]]
cohorts.append(dict(probeCostUs=p, captureUs=capture, regionIds=[r["id"] for r in eligible],
count=len(eligible), originalSeconds=sum(r["seconds"] for r in eligible),
originalShare=sum(r["seconds"] for r in eligible)/a["totals"]["regionSeconds"],
hypotheticalNetSeconds=sum(r["seconds"]-r["failures"]*p/1e6-J*capture/1e6 for r in eligible),
assumption="每个完整interval只付一次固定P,baseline捕获H跨所有失败组共享,100%成功;不等于逐operation replay,也不是预测"))
total_comp = composition(list(gos.values()))
pure_spans = []
for r in regions:
pos = r["start"]
while pos < r["end"]:
if native[pos]:
pos += 1
continue
start = pos
while pos < r["end"] and not native[pos]:
pos += 1
members = [gos[g, p] for g in r["failedGroups"] for p in range(start, pos)]
seconds = sum(x["seconds"] for x in members)
pure_spans.append(dict(region=r["id"], groups=r["failedGroups"], start=start, end=pos,
operationCount=pos-start, count=r["failures"], seconds=seconds,
meanUs=seconds*1e6/r["failures"]))
close(sum(s["seconds"] for s in pure_spans), total_comp["pureAlgebraAliasOperationSeconds"])
pure_spans.sort(key=lambda s: -s["seconds"])
kernel_cohort = [o for o in operations if any(c in o["nativeCalls"] for c in ("native_pipe_flow_cached_context", "native_medium_orifice_context"))]
summary = dict(**a["totals"], **total_comp, intervals=len(regions), groupIntervals=len(pairs),
operations=len(operations), groupOperations=len(gos),
maxOperationMeanUs=max(o["budget"]["originalUs"] for o in operations),
maxGroupOperationMeanUs=max(o["seconds"]*1e6/o["count"] for o in gos.values()),
pureSpanCount=len(pure_spans),
pureSpansAbove2UsSeconds=sum(s["seconds"] for s in pure_spans if s["meanUs"]>2),
kernelContainingPositions=[o["position"] for o in kernel_cohort],
kernelContainingOriginalSeconds=sum(o["seconds"] for o in kernel_cohort),
layeredFallbackSeconds=layered["main"]["totals"]["correctedSeconds"]["schedule_context_fallback"]*J/layered["main"]["samples"],
wholeContextSuccesses=a["totals"]["compares"]-a["totals"]["failures"])
group_ops = []
for x in a["groupOperations"]:
op = next(o for o in operations if o["position"] == x["position"])
group_ops.append(dict(**x, nativeCalls=op["nativeCalls"], decision=op["decision"],
meanUs=x["seconds"]*1e6/x["count"],
structuralReusePerJacobian=op["budget"]["structuralReusePerJacobian"]))
out = dict(scope="archived 0–10 s; diagnostics only", summary=summary,
referenceCosts=dict(typedProbeUs=P, typedCaptureIncrementUs=H, metrics=typed["metrics"], paired=typed["paired"]),
assumptions=dict(sharing="structural upper bound, not established semantic reuse", successRate=1,
originalCost="sampled instrumented mean; not an uninstrumented lower bound",
budgets="strictly less for profit; no incremental failed-guard/fallback-dispatch cost assumed"),
regions=regions, groupRegions=group_regions, operations=operations, groupOperations=group_ops,
pureSpans=pure_spans, groups=a["groups"], kernels=a["kernels"], scenarios=cohorts,
successfulContextRegions=[r for r in a["regions"] if r["contextual"] and not r["failures"]],
inputSha256=MANIFEST,
checks=["139776 interval failures", "4620672 fallback operation executions", "no duplicate (group,position)",
"156 failing group/interval pairs; 115 intervals; 343 positions; 5157 group/position pairs",
"raw three-run timing reconstruction matches archived analysis", "native-call and context-version classification agree",
"region, group and operation partitions conserve totals", "all pure spans conserve algebra time"])
OUT.mkdir(parents=True, exist_ok=True)
(OUT / "analysis.json").write_text(json.dumps(out, ensure_ascii=False, indent=2)+"\n", encoding="utf-8")
preamble = "# 全部 fallback 区间及预算\n\n由 `analyze_fallback_profitability.py` 从历史数据生成。ID、group、position 为0基,范围为[start,end)。ms为完整896个Jacobian折算累计;µs为每次fallback。按累计原计算时间排序。\n\nP=probe总开销,H=每个Jacobian新增baseline捕获,m=结构上最多共享的失败组数。盈利要求 P+H/m<C;Hmax=m(C-P)。所有预算是严格上限,不代表实际可达到;负数表示不可能。m不是已验证成功次数,若只能组内独立捕获则m=1。\n\n原区间时间、op时间和function时间来自不同抽样运行,不相加;纯代数是完全不含native调用的operation计时,native内部代数耗时未知。完整JSON另含全部5157条group/position映射、三轮原始折算值及函数表。\n"
doc = [preamble, "## 115个失败区间:成本和组成\n", table(
["R / 范围", "group", "次数", "累计ms", "均值µs / 三轮min–max", "ops/native", "op累计ms / 纯代数ms", "主要native", "主要function(exclusive排序)", "m / 共享", "选择"],
[(f"R{r['id']} [{r['start']},{r['end']})", labels(r['failedGroups']), r['failures'], f"{r['seconds']*1e3:.4f}",
f"{r['meanUs']:.3f} / {r['roundMeanUs']['min']:.3f}–{r['roundMeanUs']['max']:.3f}", f"{r['operationCount']}/{r['nativeOperationCount']}",
f"{r['operationSeconds']*1e3:.4f} / {r['pureAlgebraAliasOperationSeconds']*1e3:.4f}", labels(r['nativeCalls']),
labels([k['name'] for k in r['kernels'] if k['count']][:3]), f"{len(r['failedGroups'])} / 条件式", r['decision']) for r in regions]),
"\n## 每个区间的break-even边界\n\nPtyped、Htyped取R288最新批次中位数,仅作成本量级情景。区间预算假设一套融合机制处理整个区间,不能按每个native重复付P后仍使用本预算。\n", table(
["R", "Cµs", "m", "Hmax(P=0)µs", "Hmax(P=2/5/10)µs", "Pmax(Htyped)µs", "Ptyped,H=0可行", "Ptyped,Htyped可行", "op模式也支持该情景"],
[(r['id'], f"{r['meanUs']:.3f}", len(r['failedGroups']), f"{r['budget']['captureMaxAtZeroProbeUs']:.3f}",
"/".join(f"{r['budget']['captureMaxAtProbeUs'][str(p)]:.3f}" for p in (2,5,10)), f"{r['budget']['probeMaxAtTypedCaptureUs']:.3f}",
r['budget']['typedZeroCapturePossible'], r['budget']['typedWithSharedCapturePossible'], r['bothModesTypedWithSharedCapturePossible']) for r in regions]),
"\n## 全部156条group/interval成本\n", table(
["group", "R", "次数", "原计算ms", "µs/次", "ops/native", "纯代数ms", "m上限", "Pmax(Htyped/m)µs"],
[(r['group'], r['region'], r['failures'], f"{r['seconds']*1e3:.4f}", f"{r['budget']['originalUs']:.3f}", f"{r['operationCount']}/{r['nativeOperationCount']}",
f"{r['pureAlgebraAliasOperationSeconds']*1e3:.4f}", r['budget']['structuralReusePerJacobian'], f"{r['budget']['probeMaxAtTypedCaptureUs']:.3f}") for r in sorted(group_regions,key=lambda x:-x['seconds'])]),
"\n## 全部纯代数连续段(区间内切分候选)\n", table(
["R", "group", "范围", "ops", "执行次数", "累计ms", "每段µs / 允许的最大新增成本"],
[(s['region'], labels(s['groups']), f"[{s['start']},{s['end']})", s['operationCount'], s['count'], f"{s['seconds']*1e3:.4f}", f"{s['meanUs']:.3f}") for s in pure_spans])]
(DOCS / "fallback_profitability_intervals.md").write_text("\n".join(doc), encoding="utf-8")
doc = ["# 全部343个 fallback operation\n\n按跨group累计原计算时间排序。一个position计作一个operation,native=1表示原语句含native调用(不等于动态native调用总次数)。纯代数operation的纯代数耗时等于其累计时间;native语句内部的代数/函数耗时没有独立position级测量。具体(group,position)到R的关联见JSON groupOperations,不将各列group和R做笛卡尔积。\n\n所有m都是每Jacobian结构可共享上限,非语义证明。Hmax/Pmax使用与区间表相同的严格盈亏公式。逐operation的7.109µs typed量级,即使H=0也全部不盈利。A=原计算;C=批量代数段切分;E=只评估纯kernel memo,绝非跳过native的context副作用。\n", table(
["position / 原ID", "operation", "R", "group", "次数", "累计ms", "每次µs", "native", "主要native", "m", "Hmax(P=0)µs", "Hmax(P=2/5/10)µs", "Pmax(Htyped)µs", "选择"],
[(f"{o['position']}/{o['operationId']}", o['name'], labels(o['regions'],'R'), labels(o['groups']), o['count'], f"{o['seconds']*1e3:.4f}",
f"{o['budget']['originalUs']:.3f}", o['nativeOperationCount'], labels(o['nativeCalls']) or '纯代数/alias', o['budget']['structuralReusePerJacobian'],
f"{o['budget']['captureMaxAtZeroProbeUs']:.3f}", "/".join(f"{o['budget']['captureMaxAtProbeUs'][str(p)]:.3f}" for p in (2,5,10)),
f"{o['budget']['probeMaxAtTypedCaptureUs']:.3f}", o['decision']) for o in operations])]
(DOCS / "fallback_profitability_operations.md").write_text("\n".join(doc), encoding="utf-8")
print(json.dumps(dict(summary=summary, scenarios=cohorts, topPureSpans=pure_spans[:8], checks=out['checks']), ensure_ascii=False, indent=2))
if __name__ == "__main__":
main()
+156
View File
@@ -0,0 +1,156 @@
"""Reproducible accounting/validation report; never rescales categories to fit a target."""
from pathlib import Path
import hashlib,json,statistics
from profile_local_probe import ROOT,OUT,SOURCE,write
LABELS={
'other':'其余 callback 时间','perturbation_amount':'扰动量计算','state_copy_perturb':'状态复制/施加扰动',
'matrix_zero':'矩阵清零','difference_matrix_write':'差分计算/矩阵写回','baseline_compute':'baseline 原求值(不含 snapshot)',
'initialization':'probe initialization','gas_state_preparation':'probe gas state preparation',
'schedule_retained':'保守依赖保留的原计算','schedule_context_fallback':'context 失败后的 fallback 原计算',
'context_compare':'context/cache 比较','snapshot_capture_save':'baseline snapshot 捕获/保存',
'context_output_restore':'probe context/output restore','node_energy':'node energy','port_outputs':'端口输出赋值',
'mechanical_equations':'mechanical equations','gas_mass_energy':'气体质量/能量方程','remaining_outputs':'remaining outputs',
'pipe_diagnostics':'pipe diagnostics','finite_check':'finite check','schedule_dispatch':'schedule dispatch/管理',
'whole_probe_fallback':'整次 probe fallback'}
def read(label,file='measurement.json'):return json.loads((OUT/label/file).read_text(encoding='utf-8'))
def table(head,rows):
return '\n| '+' | '.join(head)+' |\n| '+' | '.join(['---']*len(head))+' |\n'+'\n'.join('| '+' | '.join(map(str,r))+' |' for r in rows)+'\n'
def profile(label):
p=read(label,'profile.json');f=p['frequency'];cal=statistics.median(p['calibrationTicksPerMarker'])
assert sum(sum(r['ticks']) for r in p['rows'])==p['ledgerSumTicks']==p['sampledCallbackTicks']
assert sum(sum(r['intervals']) for r in p['rows'])==p['markerCount']
assert all(r['evaluations']==p['sampledCallbacks'] for r in p['rows'][1:])
rows=[]
for r in p['rows']:
raw={n:t/f for n,t in zip(p['categories'],r['ticks'])}
corrected={n:(t-cal*c)/f for n,t,c in zip(p['categories'],r['ticks'],r['intervals'])}
assert min(corrected.values())>=0,(label,r['group'],'calibration below resolution',corrected)
rows.append({**r,'seconds':raw,'correctedSeconds':corrected})
boundary=p['sampledCallbackQpcTicks']/p['qpcFrequency']-p['sampledCallbackTicks']/f
# The two pairs of enclosing clocks delimit a real wrapper interval. Keep
# it visible, rather than rescale the TSC buckets to match QPC.
assert abs(boundary)<.01,(label,'clock cross-check',boundary)
rows[0]['seconds']['other']+=boundary
rows[0]['correctedSeconds']['other']+=boundary
return {**p,'rows':rows,'calibrationNs':cal/f*1e9,'timerSeconds':cal*p['markerCount']/f}
def aggregate(labels):
ps=[profile(n) for n in labels];categories=ps[0]['categories'];samples=sum(p['sampledCallbacks'] for p in ps)
rows=[]
for i in range(len(ps[0]['rows'])):
totals={key:{c:sum(p['rows'][i][key][c] for p in ps) for c in categories} for key in ('seconds','correctedSeconds')}
rows.append(dict(group=i-2,evaluations=sum(p['rows'][i]['evaluations'] for p in ps),**totals,
operations=[sum(p['rows'][i].get('operations',[0,0])[k] for p in ps) for k in range(2)]))
totals={key:{c:sum(r[key][c] for r in rows) for c in categories} for key in ('seconds','correctedSeconds')}
return dict(labels=labels,samples=samples,rows=rows,totals=totals,
callbackSeconds=sum(p['sampledCallbackQpcTicks']/p['qpcFrequency'] for p in ps),
timerSeconds=sum(p['timerSeconds'] for p in ps),
calibrationNs=[p['calibrationNs'] for p in ps])
def main():
# Compare every saved run, not only the runs chosen for timing summaries.
reference=json.loads((SOURCE.parent/'all-run-0/measurement.json').read_text(encoding='utf-8'))
keys=['statesSha256','outputsSha256','eventsSha256','finalState','final','propertyWarnings','acceptedSteps','rejectedSteps','stateTransitions','solverStarts','nfev','njev','nlu']
counter_keys=['newtonIterations','newtonConvergenceFailures','contextComparedBytes','contextCopiedBytes','modelCalls','groups']
checks=[]
for path in sorted(OUT.glob('*/measurement.json')):
r=json.loads(path.read_text(encoding='utf-8'))
diff=[k for k in keys if r[k]!=reference[k]]+[k for k in counter_keys if r['diagnostic'][k]!=reference['diagnostic'][k]]
assert not diff,(path,diff);checks.append(dict(run=path.parent.name,differences=diff))
full=profile('validate-all');plan=json.loads((SOURCE.parent/'plan.json').read_text(encoding='utf-8'))
audit=json.loads((SOURCE.parent/'all-audit/probe.json').read_text(encoding='utf-8'))
for g,row in enumerate(full['rows'][2:]):
retained=len(plan['groups'][g]['affectedOperations'])*896
total=sum(audit['groups'][g]['executed'])
assert row['operations']==[retained,total-retained]
assert full['rows'][28]['operations']==[0,0]
for name in ('jacobians','states','outputs','events'):
def digest(p):
with p.open('rb') as f:return hashlib.file_digest(f,'sha256').hexdigest()
assert digest(OUT/f'validate-all/{name}.bin')==digest(SOURCE.parent/f'all-audit/{name}.bin')
# Check the original sources, and the original numerical evaluator in the
# copied translation unit, have not been rewritten by this measurement.
build=json.loads((OUT/'build.json').read_text(encoding='utf-8'))
for name,expected in build['sourceHashes'].items():
assert hashlib.sha256((SOURCE/name).read_text(encoding='utf-8').encode()).hexdigest()==expected
old_model=(SOURCE/'model.c').read_text(encoding='utf-8')
assert old_model in (OUT/'worker/model.c').read_text(encoding='utf-8')
native=json.loads((SOURCE/'build-metadata.json').read_text(encoding='utf-8'))['sourceHashes']
for name,expected in native.items():assert hashlib.sha256((ROOT/'native'/name).read_text(encoding='utf-8').encode()).hexdigest()==expected
main_labels=[f'profile-{i}' for i in range(5)];data=aggregate(main_labels);scale=896/data['samples']
metrics=('jacobianSeconds','solveCpuSeconds','solveSeconds','processSeconds')
def metric(r,k):return r['diagnostic'][k] if k=='jacobianSeconds' else r[k]
controls=[read(f'control-{i}') for i in range(5)];runs=[read(n) for n in main_labels]
medians={mode:{k:statistics.median(metric(r,k) for r in rs) for k in metrics} for mode,rs in [('control',controls),('profile',runs)]}
deltas={k:medians['profile'][k]/medians['control'][k]-1 for k in metrics}
paired={k:[metric(b,k)/metric(a,k)-1 for a,b in zip(controls,runs)] for k in metrics}
densities={str(s):aggregate([f'density{s}-{i}' for i in range(2)]) for s in (8,32)}
coarse=aggregate([f'coarse-{i}' for i in range(3)])
result=dict(numericalChecks=checks,allMatrices=dict(count=896,entries=896*132*132,differentEntries=0),
originalSourcesUnchanged=True,main=data,medians=medians,deltas=deltas,pairedDeltas=paired,densityChecks=densities,
fullCoverageOperationCounts=[r['operations'] for r in full['rows'][2:]],allInstrumented=full,coarse=coarse)
write(OUT/'comparison.json',result)
report=['**局部 probe performance worker:互斥耗时分解(2026-09-17)**\n']
report.append('仅增加独立诊断构建/分析工具及计时副本;原局部优化脚本、生产实现、普通 residual、物性算法、accepted-step check、线性求解器均未修改。模型为 `tests/data/test-mql-8-corrected.json`,0–10 s、BDF、rtol=1e-8,原 atol/步长设置不变。\n')
estimate=sum(data['totals']['correctedSeconds'].values())*scale
cg=coarse['rows'][28];cg_us=sum(cg['seconds'].values())/cg['evaluations']*1e6
report.append(f'**先说明精度边界:** 低密度插桩相对未插桩的callback中位数变化{deltas["jacobianSeconds"]:+.2%}、积分CPU变化{deltas["solveCpuSeconds"]:+.2%};但细分数据扣空标记后折算{estimate:.6f}s,仍比未插桩{medians["control"]["jacobianSeconds"]:.6f}s高{estimate/medians["control"]["jacobianSeconds"]-1:.2%}。所以本轮完成了互斥分类、账本闭合和数值核验,但细分值尚未达到可直接当作未插桩精确耗时的精度。下面使用它判断热点量级与排序,不以这些百分比承诺优化收益。粗粒度独立对照中group26约{cg_us:.3f}µs,是固定底座总量的更可靠参考。\n')
report.append('**测量方法与互斥口径**\n')
report.append('主测量为5对交替串行运行,预热不计入。每连续16次 callback 分层随机抽1次,种子固定可复现;每轮56次,5轮共280次,baseline和每组probe各280次。未抽中callback走保留的原数值函数;抽中才进入计时副本,不开启shadow求值、物性内核入口计数或矩阵落盘。全量插桩+矩阵落盘仅用于数值核验。\n')
report.append('外层总时间仍用QPC;内部用带lfence的TSC读取及约10 KB互斥计数桶,避免每次记录事件数组。启动时要求CPU支持invariant TSC;用覆盖整个积分的QPC/TSC成对读数校准TSC频率。每个相邻区间只归属一个分类、一个row(外层/baseline/group),桶的TSC tick总和必须精确等于抽中callback内部总tick。外层QPC包络与内部TSC包络的差单列计入outer other,是时钟边界间的包装开销;不缩放内部分类。归并后的原始分类总和与抽中callback的QPC时间在浮点精度内相等。这只证明互斥账本闭合,不证明没有插桩扰动。\n')
report.append('在积分完成后,以相同的带屏障读时钟和计数桶更新测9批空标记,取每标记平均成本的中位数。保留原始时间,并另外给出“原始时间−标记数×空标记成本”的估计;未以未插桩总时间强制归一化,也不把任何负数截成0(出现负分类即报告失败)。校正不能消除屏障引起的执行串行化、编译布局、缓存、额外分支和操作分类的间接扰动;极短分类只作数量级参考。原QPC事件记录版保存在test/local-probe-profile-20260917,其校正后总量高估约10.8%,因此本报告继续展示残余偏差,而不宣称空标记校准能消除它。\n')
report.append(f'主测量标记成本:{min(data["calibrationNs"]):.2f}–{max(data["calibrationNs"]):.2f} ns/次。5轮被抽中callback合计 **{data["callbackSeconds"]:.9f} s**,原始分类加和完全相等;估计计时标记成本 **{data["timerSeconds"]:.9f} s**。\n')
report.append('baseline原求值列排除snapshot捕获/保存,baseline完整求值小计包含两者,只作小计不重复相加。snapshot包含入口t/y保存、选定property/pipe检查点复制、schedule结果数组保存。context比较包含元数据和memcmp;restore包含出口context/cache和区间结果恢复。schedule原计算在case原语句两侧划界,switch/循环/区间分派及测量分类开销另计schedule dispatch。\n')
report.append('initialization包括生成模型开头的数组/cache初始化及第一处gas求值前的准备赋值;gas state preparation从第一处gas求值到schedule入口,包括所有gas memo查找、必要物性计算、gas输出赋值及property context seed。schedule外的端口列仅为端口输出赋值。pipe diagnostics包括调用参数中的PH反算、诊断内核和acc累加;将acc写入w及ff的fmin限幅算remaining outputs。后者不是单纯memcpy,不能据名称假定廉价。\n')
report.append('**未插桩/插桩总耗时对照**\n')
labels={'jacobianSeconds':'Jacobian callback累计墙钟','solveCpuSeconds':'积分CPU','solveSeconds':'积分墙钟','processSeconds':'完整native进程墙钟'}
report.append(table(['5轮中位数','未插桩 / s','1/16插桩 / s','变化'],[(labels[k],f'{medians["control"][k]:.6f}',f'{medians["profile"][k]:.6f}',f'{deltas[k]:+.2%}') for k in metrics]))
report.append(table(['配对','control Jacobian','profile Jacobian','control积分','profile积分','control进程','profile进程'],[(i,*[f'{metric(r,k):.6f}' for k in ('jacobianSeconds','solveSeconds','processSeconds') for r in (controls[i],runs[i])]) for i in range(5)]))
report.append('所有样本保留,没有因较慢而删除。整进程包含启动、输出重放/落盘、诊断文件写入、标记校准和退出,不含预先完成的编译和浏览器/API流程。机器频率与调度噪声仍存在,不能把很小的负变化当作计时插桩带来的加速。\n')
report.append('**互斥分类:按抽样折算到一轮896次Jacobian**\n')
report.append('下面为5轮采样累计分类时间×896/280,未使用目标总时间做比例缩放。原始列含时间标记成本;估计列仅扣除空标记成本。\n')
raw=data['totals']['seconds'];adj=data['totals']['correctedSeconds'];total=sum(adj.values())
report.append(table(['分类','原始折算 / s','扣标记估计 / s','估计占比'],[(LABELS[c],f'{raw[c]*scale:.6f}',f'{adj[c]*scale:.6f}',f'{adj[c]/total:.2%}') for c in raw]))
report.append(f'原始折算合计 **{sum(raw.values())*scale:.6f} s**;扣标记估计合计 **{total*scale:.6f} s**;未插桩callback中位数 **{medians["control"]["jacobianSeconds"]:.6f} s**。估计合计与未插桩相差 **{(total*scale/medians["control"]["jacobianSeconds"]-1):+.2%}**,这部分不强行塞进其他分类。\n')
report.append(table(['范围(小计,不再相加)','原始每次 / µs','扣标记每次 / µs','一轮扣标记 / s'],[(name,f'{sum(data["rows"][i]["seconds"].values())/data["rows"][i]["evaluations"]*1e6:.3f}',f'{sum(data["rows"][i]["correctedSeconds"].values())/data["rows"][i]["evaluations"]*1e6:.3f}',f'{sum(data["rows"][i]["correctedSeconds"].values())*scale:.6f}') for name,i in [('baseline完整求值',1)]]))
report.append('**probe总体及逐group平均**\n')
probe_raw=sum(sum(r['seconds'].values()) for r in data['rows'][2:]);probe_adj=sum(sum(r['correctedSeconds'].values()) for r in data['rows'][2:])
report.append(f'共测量{data["samples"]*27:,}次probe,平均原始 **{probe_raw/(data["samples"]*27)*1e6:.3f} µs/probe**,扣标记估计 **{probe_adj/(data["samples"]*27)*1e6:.3f} µs/probe**。组平均范围为lp_eval入口至jac_rhs_reuse返回前,包含数值入口管理及后置统计;外层扰动/矩阵写回不归入probe。\n')
report.append(table(['group','次数','原始总均值 µs','扣标记总均值 µs','保留原计算 µs','fallback原计算 µs','全覆盖实际保留/回退操作'],[(r['group'],r['evaluations'],f'{sum(r["seconds"].values())/r["evaluations"]*1e6:.3f}',f'{sum(r["correctedSeconds"].values())/r["evaluations"]*1e6:.3f}',f'{r["correctedSeconds"]["schedule_retained"]/r["evaluations"]*1e6:.3f}',f'{r["correctedSeconds"]["schedule_context_fallback"]/r["evaluations"]*1e6:.3f}',str(full['rows'][r['group']+2]['operations'])) for r in data['rows'][2:]]))
report.append('各group完整分类数据(不只保留/回退)见comparison.json中main.rows[group+2],seconds与correctedSeconds除以evaluations即为单次均值。\n')
report.append('**低扰动粗粒度交叉核验**\n')
report.append('另构建coarse-worker:只有callback外层以及baseline/完整probe入口和出口的标记,数值调用直接进入原model_eval_local_internal,内部没有细分标记。3轮覆盖全部896次callback(非抽样)。以下原始均值不扣空标记,额外的每probe边界成本很小;它用于检查细分计时对每组总量的偏差,不用于强制缩放细分分类。\n')
report.append(table(['group','粗粒度完整probe µs','细分扣标记 µs','细分相对偏差'],[(g,f'{sum(coarse["rows"][g+2]["seconds"].values())/coarse["rows"][g+2]["evaluations"]*1e6:.3f}',f'{sum(data["rows"][g+2]["correctedSeconds"].values())/data["rows"][g+2]["evaluations"]*1e6:.3f}',f'{(sum(data["rows"][g+2]["correctedSeconds"].values())/data["rows"][g+2]["evaluations"])/(sum(coarse["rows"][g+2]["seconds"].values())/coarse["rows"][g+2]["evaluations"])-1:+.2%}') for g in range(27)]))
report.append(table(['粗粒度轮次','callback s','积分 s','group26 µs'],[(f'coarse-{i}',f'{read(f"coarse-{i}")["diagnostic"]["jacobianSeconds"]:.6f}',f'{read(f"coarse-{i}")["solveSeconds"]:.6f}',f'{sum(profile(f"coarse-{i}")["rows"][28]["seconds"].values())/896*1e6:.3f}') for i in range(3)]))
report.append('**group 26:schedule全跳过后的固定成本**\n')
g=data['rows'][28];n=g['evaluations'];ga=sum(g['correctedSeconds'].values())
cg=coarse['rows'][28];coarse_g26=sum(cg['seconds'].values())/cg['evaluations']*1e6
report.append(f'全量896次核验中,保留原操作=0、fallback原操作=0,与原audit逐操作计数相符。主测量均值:原始 **{sum(g["seconds"].values())/n*1e6:.3f} µs**,扣标记估计 **{ga/n*1e6:.3f} µs**;更低扰动的粗粒度完整probe均值 **{coarse_g26:.3f} µs**。这是本模型、本轨迹、group 26下的固定底座,不是所有group通用的固定常数,也不包含外层差分写回。\n')
report.append(table(['分类','原始均值 / µs','扣标记均值 / µs','估计占比'],[(LABELS[c],f'{g["seconds"][c]/n*1e6:.3f}',f'{v/n*1e6:.3f}',f'{v/ga:.2%}') for c,v in g['correctedSeconds'].items() if g['seconds'][c]]))
report.append('**采样密度与数值校验**\n')
density_rows=[]
for name,d in [('1/8',densities['8']),('1/16',data),('1/32',densities['32'])]:
factor=896/d['samples'];g=d['rows'][28]
density_rows.append((name,d['samples'],f'{sum(d["totals"]["correctedSeconds"].values())*factor:.6f}',f'{d["totals"]["correctedSeconds"]["schedule_retained"]*factor:.6f}',f'{d["totals"]["correctedSeconds"]["schedule_context_fallback"]*factor:.6f}',f'{sum(g["correctedSeconds"].values())/g["evaluations"]*1e6:.3f}'))
report.append(table(['密度','抽中callback','一轮扣标记估计 s','保留计算 s','fallback计算 s','group26 µs'],density_rows))
report.append(table(['只编入插桩、采样关闭','Jacobian s','积分 s','完整进程 s'],[(f'disabled-{i}',*[f'{metric(read(f"disabled-{i}"),k):.6f}' for k in ('jacobianSeconds','solveSeconds','processSeconds')]) for i in range(2)]))
report.append(f'共{len(checks)}次完整运行的states/outputs/event二进制、全部warning字段、最终状态、accepted/rejected steps、Newton iterations/failures、nfev/njev/nlu、context复制/比较字节数、各group保护命中/回退计数均与既有performance worker一致。全量计时副本另核对896个132×132矩阵,共15,611,904元素及各矩阵输入t/y,逐字节一致。内部互斥计时桶的闭合误差为0 TSC tick;加上外层边界差后,分类与QPC总量在浮点精度内闭合。\n')
report.append('全量插桩的每组“保守保留”和“context回退”操作数分别等于依赖计划应执行数、原audit实际执行数减去应执行数;group26两者均为0。整probe fallback未发生。生产native源文件hash、旧performance worker所有源文件hash以及原生成模型函数完整文本均验证未变。\n')
report.append(table(['求解器项目','所有本轮运行保持一致'],[(k,reference[k]) for k in ['acceptedSteps','rejectedSteps','stateTransitions','solverStarts','nfev','njev','nlu']]+[(k,reference['diagnostic'][k]) for k in ['newtonIterations','newtonConvergenceFailures']]))
report.append('**可以支持的诊断结论**\n')
report.append('1. schedule实际保留计算1,390,592次,context保护失败后的回退计算4,620,672次;139,776次区间context校验失败。细分估计回退原计算约0.61s,保守保留原计算约0.42s,1/8和1/32密度下排序相同。保护的主要时间影响体现在失败后的原计算,并非memcmp本身;不能据此推断去掉保护是安全的。\n')
report.append('2. 比较约0.032s,baseline快照/结果保存约0.053s,恢复约0.030s。三者不是零成本,但合计量级显著小于回退原计算。所有这些时间均与计算阶段互斥,baseline小计没有再次加入总和。\n')
report.append('3. 未裁剪的初始化、gas准备和schedule外方程/输出/诊断合计估计约0.58s;其中remaining outputs约0.24s、pipe diagnostics约0.19s,而gas准备约0.056s。remaining outputs包含各管道ff的fmin限幅,pipe diagnostics包含参数PH反算,不能把前者等同简单写内存、后者等同单一内核调用。此处未对某个具体函数做内部剖析,不宣称fmin就是已证实的单函数热点。\n')
report.append('4. group26的schedule保留/回退均为0,依然要执行未裁剪阶段和恢复操作。粗粒度三轮均值21.109–21.263µs;细分扣标记得到24.749µs,高约16.8%,提示小probe的细分扰动占比更高。其细分热点主要是remaining outputs、pipe diagnostics与finite check,但精确占比应保留上述测量误差。\n')
report.append('5. 随机分层采样减少了整轮测量干扰,不能消除被抽中callback自身的串行化/缓存/编译布局影响。细分校正总量仍有6.98%残差;本报告没有把残差摊进other或按比例缩放各分类来制造闭合。跨轮机器负载差异也会影响粗/细计时对比。本轮没有据此修改任何优化实现。\n')
report.append('**证据与复现**\n')
report.append('脚本:tests/manual/profile_local_probe.py、local_probe_profile.h/.c、analyze_local_probe_profile.py。prepare只在本目录生成计时worker;run --stride 1 --matrices做完整数值核验;batch做预热、5对主测量和密度对照;分析脚本生成本报告。各运行目录保留profile.json、measurement.json、probe.json、result.json和二进制结果;build.json保存原文件hash和阶段边界。\n')
(OUT/'report.md').write_text('\n'.join(report),encoding='utf-8')
print(json.dumps(dict(medians=medians,deltas=deltas,rawEstimated=sum(raw.values())*scale,correctedEstimated=total*scale,group26Us=ga/n*1e6,densityRows=density_rows,numericalRuns=len(checks)),ensure_ascii=False,indent=2))
if __name__=='__main__':main()
+236
View File
@@ -0,0 +1,236 @@
"""Read-only coverage and amplitude analysis of saved, phase-paired MQL8 curves.
No simulation, re-pairing, resampling, filtering, or production changes. Integral
metrics are trapezoidal estimates on the saved common grid, not bounds on any
unsampled transient. Occupancy durations use sample-cell weights and are not
located threshold-crossing times. Run with --plots in a matplotlib environment.
"""
from __future__ import annotations
import argparse
import hashlib
import json
from pathlib import Path
import numpy as np
ROOT = Path(__file__).resolve().parents[2]
BASE = ROOT / 'test/lstp-mainline-20260917'
def digest(path):
return hashlib.sha256(path.read_bytes()).hexdigest()
def ratio(numerator, denominator):
return float(100 * numerator / denominator) if denominator else None
def intervals(mask, time, weights):
indices = np.flatnonzero(mask)
blocks = np.split(indices, np.flatnonzero(np.diff(indices) > 1) + 1)
return [dict(firstSample=float(time[b[0]]), lastSample=float(time[b[-1]]),
sampleCount=len(b), cellDurationEstimate=float(weights[b].sum()))
for b in blocks if len(b)]
def mask_summary(mask, time, weights):
return dict(sampleCount=int(mask.sum()), samplePercent=ratio(mask.sum(), len(mask)),
cellDurationEstimate=float(weights[mask].sum()),
timePercentEstimate=ratio(weights[mask].sum(), weights.sum()),
intervals=intervals(mask, time, weights))
def curve_stats(row, actual, reference, time, weights):
error = actual - reference
absolute = np.abs(error)
magnitude = np.abs(reference)
epsilon = row['epsilon']
active = magnitude > epsilon
near = ~active
relative = np.zeros(len(time))
relative[active] = absolute[active] / magnitude[active] * 100
peak = float(magnitude.max())
significant = magnitude > max(epsilon, .01 * peak)
near_bad = near & (absolute > epsilon)
signed_integral = np.concatenate(([0.], np.cumsum(
.5 * (error[1:] + error[:-1]) * np.diff(time))))
result = dict(key=row['key'], quantity=row['quantity'], unit=row['unit'],
epsilon=epsilon, sampleCount=len(time), referencePeak=peak,
activeCount=int(active.sum()), significantCount=int(significant.sum()),
maximumAbsolute=float(absolute.max()),
worstAbsoluteTime=float(time[np.argmax(absolute)]),
maximumAbsolutePercentOfPeak=ratio(absolute.max(), peak),
rmse=float(np.sqrt(np.dot(weights, error**2) / weights.sum())),
relativeL2Percent=ratio(np.sqrt(np.dot(weights, error**2)),
np.sqrt(np.dot(weights, reference**2))),
integratedAbsoluteError=float(np.dot(weights, absolute)),
integratedReferenceMagnitude=float(np.dot(weights, magnitude)),
relativeL1Percent=ratio(np.dot(weights, absolute), np.dot(weights, magnitude)),
signedIntegralError=float(signed_integral[-1]),
maxCumulativeSignedError=float(np.abs(signed_integral).max()),
activeRelativePercentiles={str(p): float(np.percentile(relative[active], p))
if active.any() else None for p in (50, 95, 99, 100)},
significantMaxRelativePercent=float(relative[significant].max())
if significant.any() else None,
nearZeroCount=int(near.sum()),
nearZeroMaxAbsolute=float(absolute[near].max()) if near.any() else None,
nearZeroAboveEpsilon=mask_summary(near_bad, time, weights),
sensitivity={str(factor): int(((magnitude > epsilon * factor)
& (absolute > .05 * magnitude)).sum()) for factor in (.1, 1., 10.)})
masks = {}
for threshold in (1, 5):
mask = active & (relative > threshold)
masks[str(threshold)] = mask
result['above' + str(threshold)] = mask_summary(mask, time, weights) | dict(
activePercent=ratio(mask.sum(), active.sum()))
result['relative5OrNearZeroAbsolute'] = mask_summary(masks['5'] | near_bad, time, weights)
result['examplesAbove5'] = [dict(time=float(time[i]), platform=float(actual[i]),
amesim=float(reference[i]), absoluteError=float(absolute[i]),
relativePercent=float(relative[i])) for i in np.flatnonzero(masks['5'])]
result['nearZeroExamples'] = [dict(time=float(time[i]), platform=float(actual[i]),
amesim=float(reference[i]), absoluteError=float(absolute[i]))
for i in np.flatnonzero(near_bad)]
# Cross-check the earlier diagnostic without changing its epsilon or pairing.
assert result['above5']['sampleCount'] == row['above5PercentCount']
assert int(near_bad.sum()) == row['nearZeroBeyondEpsilon']
return result, masks['5'], near_bad
def analyze(source):
paths = [source / name for name in ('comparison.json', 'curves.npz')]
before = {str(path): digest(path) for path in paths}
comparison = json.loads(paths[0].read_bytes())
arrays = np.load(paths[1], allow_pickle=False)
time = arrays['time']
assert arrays['phaseMatched'].all() and np.all(np.diff(time) > 0)
dt = np.diff(time)
weights = np.r_[dt[0] / 2, (dt[:-1] + dt[1:]) / 2, dt[-1] / 2]
assert np.isclose(weights.sum(), time[-1] - time[0])
rows, masks, near_masks = [], {}, {}
for row in comparison['curves']:
actual, reference = (arrays[s + '|' + row['key']] for s in ('platform', 'amesim'))
assert np.isfinite(actual).all() and np.isfinite(reference).all()
stats, mask, near = curve_stats(row, actual, reference, time, weights)
rows.append(stats)
masks[row['key']], near_masks[row['key']] = mask, near
groups = {}
for quantity in sorted({r['quantity'] for r in rows}):
selected = [r for r in rows if r['quantity'] == quantity]
all_count = len(selected) * len(time)
active_count = sum(r['activeCount'] for r in selected)
union = np.any([masks[r['key']] for r in selected], axis=0)
near_union = np.any([near_masks[r['key']] for r in selected], axis=0)
def worst(field):
candidates = [r for r in selected if r[field] is not None]
if not candidates:
return None
r = max(candidates, key=lambda r: r[field])
return dict(key=r['key'], value=r[field])
groups[quantity] = dict(curveCount=len(selected), sampleCount=all_count,
activeCount=active_count,
above5Count=sum(r['above5']['sampleCount'] for r in selected),
above5SamplePercent=ratio(sum(r['above5']['sampleCount'] for r in selected), all_count),
above5ActivePercent=ratio(sum(r['above5']['sampleCount'] for r in selected), active_count),
anyCurveAbove5=mask_summary(union, time, weights),
nearZeroAboveEpsilonCount=sum(r['nearZeroAboveEpsilon']['sampleCount'] for r in selected),
nearZeroAboveEpsilonSamplePercent=ratio(sum(r['nearZeroAboveEpsilon']['sampleCount'] for r in selected), all_count),
anyCurveNearZeroAboveEpsilon=mask_summary(near_union, time, weights),
anyCurveRelative5OrNearZeroAbsolute=mask_summary(union | near_union, time, weights),
worst={field: worst(field) for field in ('maximumAbsolute',
'maximumAbsolutePercentOfPeak', 'relativeL2Percent', 'relativeL1Percent',
'significantMaxRelativePercent', 'integratedAbsoluteError',
'maxCumulativeSignedError')})
union = np.any(list(masks.values()), axis=0)
near_union = np.any(list(near_masks.values()), axis=0)
windows = []
for start, end in [(0., 1.), (1., 10.8), (10.8, 21.6), (21.6, 32.4), (32.4, 43.2), (43.2, 50.)]:
include = (time >= start - 1e-12) & (time < end - 1e-12 if end < 50 else time <= end)
details = {}
for quantity in ('mass_flow', 'enthalpy_flow'):
selected = [r for r in rows if r['quantity'] == quantity]
details[quantity] = dict(
above5Count=sum(int((masks[r['key']] & include).sum()) for r in selected),
nearZeroAboveEpsilonCount=sum(int((near_masks[r['key']] & include).sum()) for r in selected),
maximumAbsolute=max(float(np.abs(arrays['platform|' + r['key']]
- arrays['amesim|' + r['key']])[include].max()) for r in selected))
windows.append(dict(start=start, end=end, sampleCount=int(include.sum()), groups=details))
assert sum(r['above5']['sampleCount'] for r in rows) == comparison['above5PercentCount']
assert all(digest(path) == before[str(path)] for path in paths)
return dict(source=str(source), inputHashes=before, sourceUnchanged=True,
grid=dict(start=float(time[0]), end=float(time[-1]), count=len(time),
interval=float(np.median(dt))), curveCount=len(rows),
affectedCurveCount=sum(r['above5']['sampleCount'] > 0 for r in rows),
above5Count=sum(r['above5']['sampleCount'] for r in rows),
anyCurveAbove5=mask_summary(union, time, weights),
anyCurveNearZeroAboveEpsilon=mask_summary(near_union, time, weights),
anyCurveRelative5OrNearZeroAbsolute=mask_summary(union | near_union, time, weights),
groups=groups, windows=windows, curves=rows)
def plots(source, out):
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from matplotlib import font_manager
font = Path('C:/Windows/Fonts/msyh.ttc')
if font.exists():
font_manager.fontManager.addfont(str(font))
plt.rcParams['font.family'] = font_manager.FontProperties(fname=str(font)).get_name()
plt.rcParams.update({'font.size': 10, 'axes.unicode_minus': False,
'axes.spines.top': False, 'axes.spines.right': False})
arrays = np.load(source / 'curves.npz', allow_pickle=False)
t = arrays['time']
fig, axes = plt.subplots(2, 3, figsize=(15, 8), constrained_layout=True)
selected = [('amesim_pn3node2_3.reference_mass_flow', '质量流量', 'kg/s', 1e6, 'mg/s'),
('amesim_p4node2_4.reference_enthalpy_flow', '焓流', 'W', 1., 'W')]
for row, (key, label, unit, scale, small_unit) in enumerate(selected):
y, ref = arrays['platform|' + key], arrays['amesim|' + key]
for col, bounds in enumerate(((0, 50), (.27, .35))):
ax = axes[row, col]
mask = (t >= bounds[0] - 1e-12) & (t <= bounds[1] + 1e-12)
factor = 1. if col == 0 else scale
ax.plot(t[mask], ref[mask] * factor, color='#dd863b', lw=2, label='Amesim')
ax.plot(t[mask], y[mask] * factor, color='#126ca6', lw=1, ls='--', label='平台')
ax.set(xlabel='时间 / s', ylabel=unit if col == 0 else small_unit,
title=label + (':50 s 全程' if col == 0 else ':接近零的衰减尾部放大'))
if col == 1:
ax.axvspan(.295, .325, color='#d84b43', alpha=.12, label='差异集中区')
ax.legend(fontsize=8)
ax = axes[row, 2]
group_keys = [k for k in arrays.files if k.startswith('platform|')
and k.endswith('.reference_' + ('mass_flow' if row == 0 else 'enthalpy_flow'))]
envelope = np.max([np.abs(arrays[k] - arrays[k.replace('platform|', 'amesim|', 1)])
for k in group_keys], axis=0)
ax.plot(t, envelope * scale, color='#8b3d50', lw=.9)
ax.set(xlabel='时间 / s', ylabel=small_unit, title=label + ':16 条曲线最大绝对差包络')
for ax in axes[row]:
ax.grid(alpha=.2)
fig.suptitle('八路基线剩余差异:全程、初始衰减段与绝对差\n0–50 s,共同网格 10 ms;事件侧已配对;不代表网格间瞬态的误差上界', fontsize=14)
fig.savefig(out / 'coverage.png', dpi=150)
plt.close(fig)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--output', type=Path, default=ROOT / 'test/mql8-curve-coverage-20260917')
parser.add_argument('--plots', action='store_true')
args = parser.parse_args()
args.output.mkdir(parents=True, exist_ok=True)
sources = dict(cyclic=BASE / 'event-output-comparison', noncyclic=BASE / 'baseline/noncyclic')
result = dict(method='Saved-grid statistics; original phase pairing and epsilon preserved. '
'L1/L2 normalized by each reference curve; no time interpolation. '
'Integrals and durations are grid estimates only. Noncyclic reference uses ordinary output.',
profiles={name: analyze(path) for name, path in sources.items()})
(args.output / 'coverage.json').write_text(json.dumps(result, ensure_ascii=False,
indent=2, allow_nan=False) + '\n', encoding='utf-8')
if args.plots:
plots(sources['cyclic'], args.output)
for name, profile in result['profiles'].items():
print(name, 'above5:', profile['above5Count'], 'any time:', profile['anyCurveAbove5'])
for quantity in ('force', 'mass_flow', 'enthalpy_flow', 'pressure', 'temperature', 'gap', 'velocity'):
print(quantity, json.dumps(profile['groups'][quantity], ensure_ascii=False))
if __name__ == '__main__':
main()
+144
View File
@@ -0,0 +1,144 @@
"""Summarize independent serial measurements; audit times are never performance data."""
from pathlib import Path
import json,statistics
import real_skip_experiment as experiment
OUT=experiment.OUT
def load(path):return json.loads(path.read_text(encoding='utf-8'))
def write(path,value):experiment.write(path,value)
def compare_bytes(actual,reference):
offset=0
with actual.open('rb') as a,reference.open('rb') as b:
while True:
x=a.read(1024*1024);y=b.read(1024*1024)
if x!=y:
local=next((i for i,(u,v) in enumerate(zip(x,y)) if u!=v),min(len(x),len(y)))
different=offset+local
record={'file':str(actual),'reference':str(reference),'firstByte':different}
if actual.name=='jacobians.bin':
width=(1+132+132*132)*8;j,within=divmod(different,width);element=within//8
record.update(jacobian=j+1,field='t' if element==0 else 'y' if element<=132 else 'Jacobian',element=element)
if element>132:record.update(row=(element-133)%132,column=(element-133)//132)
write(OUT/'first-file-mismatch.json',record)
raise AssertionError(record)
if not x:break
offset+=len(x)
return offset
def main():
checks=[]
for label in ['audit-control-run','audit-skip-run','audit-forced-reject']:
for name in ['jacobians','states','outputs','events']:
ref=experiment.BASE.parent/('all-audit' if name=='jacobians' else 'all-run-0')/(name+'.bin')
n=compare_bytes(OUT/label/(name+'.bin'),ref)
checks.append(dict(run=label,field=name,bytes=n,exact=True))
write(OUT/'byte-comparison.json',checks)
rows=load(OUT/'performance.json');audit=load(OUT/'audit-skip-run/validation.json');forced=load(OUT/'audit-forced-reject/validation.json')
assert len(rows)==14 and all(r['exact'] for r in rows)
a=[r for r in rows if not r['skip']];b=[r for r in rows if r['skip']]
assert all(r['counters']['realSkips']==896 and r['counters']['nativeOriginalExecutions']==0 and r['counters']['rejects']==0 for r in b)
def timing(row,key):return row[key] if key in row else row['counters'][key]
keys=['originalSeconds','validationSeconds','overlayPatchSeconds','commitSeconds','fallbackSeconds','pathSeconds','baselineRecordSeconds','jacobianSeconds','solveCpuSeconds','solveSeconds']
def stat(values):return dict(median=statistics.median(values),min=min(values),max=max(values))
summary={side:{k:stat([timing(r,k) for r in rs]) for k in keys} for side,rs in [('control',a),('skip',b)]}
pairs=[]
for i in range(1,8):
c=next(r for r in a if r['label']==f'pair-{i}-control');s=next(r for r in b if r['label']==f'pair-{i}-skip')
stage=sum(s['counters'][k] for k in ['validationSeconds','overlayPatchSeconds','commitSeconds'])
pairs.append(dict(pair=i,originalUs=c['counters']['originalSeconds']/896*1e6,replayStagesUs=stage/896*1e6,
targetNetSavingSeconds=c['counters']['pathSeconds']-s['counters']['pathSeconds'],
originalMinusReplaySeconds=c['counters']['originalSeconds']-stage,
incrementalMetadataSeconds=s['counters']['baselineRecordSeconds']-c['counters']['baselineRecordSeconds'],
jacobianDeltaSeconds=s['jacobianSeconds']-c['jacobianSeconds'],
integrationCpuDeltaSeconds=s['solveCpuSeconds']-c['solveCpuSeconds'],integrationWallDeltaSeconds=s['solveSeconds']-c['solveSeconds']))
summary['paired']={k:stat([r[k] for r in pairs]) for k in pairs[0] if k!='pair'}
summary['pairs']=pairs;write(OUT/'performance-summary.json',summary)
med=lambda side,k:summary[side][k]['median']
orig=summary['paired']['originalUs']['median'];replay=summary['paired']['replayStagesUs']['median']
lines=['# R288 / position16 最小 real skip 实验','',
'## 结论','',
'1. **完整求解结果逐位一致。** 0–10 s 全轨迹,896 个 132×132 Jacobian(15,611,904 个元素)及每个 t/y、states、outputs、事件、最终状态与既有基线一致;没有使用数值容差。',
'2. **896/896 次真实 skip;自然 reject/fallback 为 0。** commit 896 次,原目标 native operation 实际执行 0 次,Reference 双路执行 0 次。',
f'3. **当前 replay 更贵。** 7 轮中位数:原 operation {orig:.3f} µs/次,validation + overlay/patch + commit {replay:.3f} µs/次({replay/orig:.2f} 倍)。目标完整路径 control {med("control","pathSeconds")/896*1e6:.3f} µs/次,real skip {med("skip","pathSeconds")/896*1e6:.3f} µs/次。',
'4. **暂不把这一实现直接扩展到 position52。** 正确性门槛已满足,净收益门槛未满足。应先降低 metadata 采集、事务复制和解释执行成本;本轮不能据此推断 position52 或其他 interval 的收益。','',
'## 范围与实现','',
'- 全部改动仅在独立生成的实验 worker 及 `tests/manual`;生产路径、默认开关、property cache 语义、原 whole-context guard 未改动。',
'- 入口仅为 `lp_color == 6 && region == 288` 的 `case 16`,位于原 `lp_reuse` 失败之后。其他位置继续原执行。',
'- baseline position16 用单独命名空间的 kernels 采集必需的有序事件 metadata。其他 operation 使用原 kernels,没有全局访问插桩。',
'- guard 来自已验证的 shadow 源码,构建时校验其 SHA-256,并断言 guard 函数保持一致;唯一变量替换是将原事务入口 count 改为当前真实 probe 入口 count。',
'- 复制当前 probe 到私有 overlay;按当前 entries 验证 first-match/miss,重定位逻辑 slot,从当前 count 追加。通过后构造地址已转换的有序 write set,再 commit。已有 entries、未写字段和其他 pipe slots 保持原值。',
'- Observer、capacity/scratch、memo lifetime/value、消费字段、valid、pipe branch、未知副作用/非有限值等原保护条件保留;失败只回退原 operation。',
'- 性能版不执行 Reference、不导出访问日志或 Jacobian、不逐次比较完整 context。保留运行所需 metadata、严格 guard、overlay/patch、计时和累计计数。','',
'## 正确性证据','',
'| 检查 | 结果 |','|---|---:|',
'| Jacobian、t/y 与既有 all-audit 文件逐字节比较 | 896/896 一致 |',
'| 每个 Jacobian 的 baseline + 27 group evaluator 出口 | 25,088/25,088 一致 |',
'| 目标 operation 入口 / 出口 | 896 / 896 一致 |',
'| 返回状态、dy/w、active property entries 有序字段/valid、全部 pipe slots | 逐位一致 |',
'| memo 每次 Jacobian 的表内容、owner 绑定、recording 生命周期 | 一致 |',
'| 所有 probe 的 memo entries 只读检查 | 通过 |',
'| gas memo 内容、kernel 绑定及计数 | 一致 |',
'| errno、x87/SSE flags/rounding、warning/observer | 一致 |',
'| count 差异 / slot relocation 的目标 probe | 638 / 571 |',
'| 顺序 append | 1,658 |',
'| PT miss → PT miss / 近零流量无查询 | 829 / 67 |','',
'Audit 对照来自**独立 control 进程真实执行当前 probe**的出口,约 1.88 GB 二进制数据。跨进程地址比较采用 owner/function 绑定身份,数值字段保持原始位模式;不把 C padding 当成数值。memo 表逐 Jacobian 比较,后续每个 probe 同时检查整表未变。目标入口、出口与所有 group 的完整 evaluator 出口均参与比较。audit 的 I/O 保留并恢复 errno、x87 和 SSE 环境。','',
'| 求解器计数 | control / real skip |','|---|---:|',
'| accepted / rejected | 10840 / 918 |','| Newton iterations / convergence failures | 19371 / 798 |',
'| nfev / njev / nlu | 44467 / 896 / 3106 |','| solverStarts / stateTransitions | 4 / 1 |','',
'额外负例:每 128 个目标 probe 注入一次晚期 nonfinite-output reject,发生在 overlay 已执行有序更新之后。7 次 reject 均完成无污染检查,fallback/native 原执行各 7 次,commit/skip 各 889 次;全轨迹仍与同一 control 和既有基线逐位一致。没有放宽 guard,也未改变容差。','',
'## 性能方法与原始数据','',
'Windows / MinGW GCC,原构建优化选项(`-O3 -ffp-contract=off -fno-fast-math`)。完成正确性和回退测试后单独编译性能 worker;control、skip 各预热一次,再交替串行运行 7 组。表中顺序就是实际顺序。每轮 states/outputs/events 指纹和全部指定求解器计数保持一致,skip 每轮均为 896、reject 0、原执行 0。','',
'QPC 粗粒度计时;累计整数 tick,积分过程中不做浮点时间换算。validation 包括语义校验所必需的临时有序更新;overlay/patch 包括当前 context 复制及提交 write set 构造;commit 是真实字段写回。完整 target 路径另计,包含调度、计数和额外计时开销。未减去计时器自身成本,未使用 shadow/audit 时间推断性能。','',
'| 运行顺序 | 原执行 µs/次 | validation µs/次 | overlay/patch µs/次 | commit µs/次 | fallback ms | target 总 ms | baseline pos16 总 ms | Jacobian s | 积分 CPU s | 积分 wall s | skip/reject |',
'|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|']
for r in rows:
c=r['counters'];us=lambda k:c[k]/896*1e6
lines.append(f'| {r["label"]} | {us("originalSeconds"):.3f} | {us("validationSeconds"):.3f} | {us("overlayPatchSeconds"):.3f} | {us("commitSeconds"):.3f} | {c["fallbackSeconds"]*1e3:.3f} | {c["pathSeconds"]*1e3:.3f} | {c["baselineRecordSeconds"]*1e3:.3f} | {r["jacobianSeconds"]:.6f} | {r["solveCpuSeconds"]:.6f} | {r["solveSeconds"]:.6f} | {c["realSkips"]}/{c["rejects"]} |')
lines+=['','baseline pos16:control 为原 baseline operation;skip 包含原 baseline operation + 本次 replay 必需 metadata 采集,不能漏算这部分成本。性能各轮没有自然 reject,因此 fallback 总时间为 0;这不代表一次 fallback 的成本为零,本轮未估计该分支的单次性能。','',
'### 中位数及 min/max','', '| 项目 | control 中位数 [min, max] | real skip 中位数 [min, max] |','|---|---:|---:|']
for k in keys:
def cell(side):
v=summary[side][k];return f'{v["median"]:.9f} [{v["min"]:.9f}, {v["max"]:.9f}]'
lines.append(f'| {k}(s,896 次累计) | {cell("control")} | {cell("skip")} |')
lines+=['','### 配对差值','',
'| 组 | 原计算 − replay 三阶段 ms | target 完整路径净节省 ms | 新增 metadata 采集 ms | Jacobian Δ s | 积分 CPU Δ s | 积分 wall Δ s |','|---|---:|---:|---:|---:|---:|---:|']
for r in pairs:lines.append(f'| {r["pair"]} | {r["originalMinusReplaySeconds"]*1e3:.3f} | {r["targetNetSavingSeconds"]*1e3:.3f} | {r["incrementalMetadataSeconds"]*1e3:.3f} | {r["jacobianDeltaSeconds"]:.6f} | {r["integrationCpuDeltaSeconds"]:.6f} | {r["integrationWallDeltaSeconds"]:.6f} |')
paired=summary['paired']
lines+=['','净节省为正表示节省,Δ = skip − control。','',
f'- 原计算成本 − replay 三阶段成本:配对中位数 **{paired["originalMinusReplaySeconds"]["median"]*1e3:.3f} ms / 896 次**。',
f'- target 完整路径净节省:配对中位数 **{paired["targetNetSavingSeconds"]["median"]*1e3:.3f} ms / 896 次**;范围 [{paired["targetNetSavingSeconds"]["min"]*1e3:.3f}, {paired["targetNetSavingSeconds"]["max"]*1e3:.3f}] ms。',
f'- 此外 baseline metadata 采集增加:配对中位数 **{paired["incrementalMetadataSeconds"]["median"]*1e3:.3f} ms**。',
f'- Jacobian callback 配对 Δ:中位数 {paired["jacobianDeltaSeconds"]["median"]:.6f} s,范围 [{paired["jacobianDeltaSeconds"]["min"]:.6f}, {paired["jacobianDeltaSeconds"]["max"]:.6f}] s。',
f'- 积分 wall 配对 Δ:中位数 {paired["integrationWallDeltaSeconds"]["median"]:.6f} s,范围 [{paired["integrationWallDeltaSeconds"]["min"]:.6f}, {paired["integrationWallDeltaSeconds"]["max"]:.6f}] s。',
'','全局时间受调频、调度和系统负载影响,不能把某轮 Jacobian/积分变快归因于这个 operation。目标路径在全部配对中均更慢,已经足以否定当前实现的局部净收益;本轮不预测整个 local probe 的最终加速比例。','',
'## 成本解释及下一阶段条件','',
'原 operation 在现有 Jacobian memo 的只读复用环境下已经很便宜;当前严格 replay 仍要初始化/复制 156,816 B 的 overlay(含完整 memo),解释事件并构造 write set。运行所需有效 metadata 为 2,224–23,344 B,Plan 固定预留 90,224 B,patch 预留 16,384 B。这些是当前隔离实现的实际成本,不是机制理论上的下限。',
'','因此,本轮证明了目标路径可以安全真实跳过,但没有证明性能优化成立。下一步应先针对事务存储和 metadata 表达做最小化,再重复同样的正确性与交替性能验收;不因 position16 正确就直接扩大到 position52/整个 R475/全部 reuse interval。','',
'## 复现与证据位置','',
'运行目录:`test/r288-real-skip-20260917/`。依赖上一轮生成的 local-probe worker、context-access worker 和 shadow-certified 源码;工具链复用原实验配置,不安装依赖。','',
'```powershell',
'.venv-win/Scripts/python.exe tests/manual/real_skip_experiment.py prepare --audit',
'.venv-win/Scripts/python.exe tests/manual/real_skip_experiment.py prepare --audit --skip',
'.venv-win/Scripts/python.exe tests/manual/real_skip_experiment.py run --audit',
'.venv-win/Scripts/python.exe tests/manual/real_skip_experiment.py run --audit --skip',
'.venv-win/Scripts/python.exe tests/manual/real_skip_experiment.py run --audit --skip --label audit-forced-reject --force 128',
'.venv-win/Scripts/python.exe tests/manual/real_skip_experiment.py prepare',
'.venv-win/Scripts/python.exe tests/manual/real_skip_experiment.py prepare --skip',
'.venv-win/Scripts/python.exe tests/manual/real_skip_experiment.py benchmark --pairs 7',
'.venv-win/Scripts/python.exe tests/manual/analyze_real_skip.py','```','',
'- `audit-{control,skip}-run/validation.json`:正确性、独立执行计数、完整求解器计数及文件指纹。',
'- `audit-forced-reject/validation.json`:真实回退及 rollback 检查。',
'- `byte-comparison.json`:全部 Jacobian/t/y、states、outputs、events 与原始基线的逐字节比较。',
'- `audit-control-run/audit.bin`:所有 group 和目标 operation 的实际执行对照出口。',
'- `performance.json`:14 次按实际顺序记录的原始数据;各轮目录保留独立日志和结果。',
'- `performance-summary.json`:中位数、min/max 和逐对差值。',
'- `{audit,perf}-{control,skip}/build.json`:源文件哈希、guard 一致性和构建记录。','']
report=Path(__file__).with_name('r288_real_skip_report.md');report.write_text('\n'.join(lines),encoding='utf-8')
print(json.dumps(summary['paired'],ensure_ascii=False,indent=2));print(report)
if __name__=='__main__':main()
+178
View File
@@ -0,0 +1,178 @@
"""Write the R288 cost-floor report from raw audit and serial A/B/C results."""
from pathlib import Path
import json,statistics,subprocess,re,hashlib
import specialized_replay_experiment as experiment
import analyze_real_skip as bytecheck
ROOT=experiment.ROOT;OUT=experiment.OUT;HERE=Path(__file__).parent
def read(p):return json.loads(p.read_text(encoding='utf-8'))
def write(p,o):experiment.base.write(p,o)
def stat(v):return dict(median=statistics.median(v),min=min(v),max=max(v))
def fmt(s,d=3):return f'{s["median"]:.{d}f} [{s["min"]:.{d}f}, {s["max"]:.{d}f}]'
def main():
bytecheck.OUT=OUT;checks=[]
for mode in 'CD':
for label in ['audit-skip-run','forced-reject']:
r=read(OUT/mode/label/'validation.json');t=read(OUT/mode/label/'typed-summary.json')
assert r['exact'] and t['contractChecks']==t['oracleChecks']==896
assert t['negativeChecks']==(20 if label=='audit-skip-run' else 0)
for name in ['jacobians','states','outputs','events']:
ref=experiment.base.BASE.parent/('all-audit' if name=='jacobians' else 'all-run-0')/(name+'.bin')
size=bytecheck.compare_bytes(OUT/mode/label/(name+'.bin'),ref)
checks.append(dict(mode=mode,run=label,name=name,bytes=size,exact=True))
write(OUT/'byte-comparison.json',checks)
cc,_,_=experiment.base.ex.builder.toolchain();nm=Path(cc).with_name('nm.exe')
symbols=subprocess.check_output([str(nm),'--defined-only',str(OUT/'C/perf-skip/model.exe')],text=True)
assert not re.search(r'(?m)\b(?:plan|overlay|before_reject|after_reject|oracle_generic|oracle_typed|replay_overlay|ax_access)$',symbols)
write(OUT/'C/perf-skip/stripped-audit-proof.json',dict(noGenericInterpreter=True,noFullOverlay=True,noAuditOracle=True))
for mode in 'CD':
for build in (OUT/mode).glob('*/build.json'):
v=read(build);v['guardMatchesShadow']=False;v['wholeContextGuardUnchanged']=True
v['typedGuardValidation']='C and D audit: 896 dynamic contracts, 896 generic decisions/patches, 20 negative cases, full exit comparison'
write(build,v)
rows=read(OUT/'performance.json');assert len(rows)==27
by={m:[r for r in rows if r['mode']==m] for m in 'ABC'}
metrics={}
for m,rs in by.items():
transformed=[]
for r in rs:
assert r['exact'];c=r['counters'];u=lambda k:c[k]/896*1e6
if m!='A':assert c['realSkips']==896 and c['nativeOriginalExecutions']==c['rejects']==0
transformed.append(dict(original=u('originalSeconds'),validation=u('validationSeconds'),patch=u('overlayPatchSeconds'),commit=u('commitSeconds'),outsideStages=u('pathSeconds')-(u('originalSeconds') if m=='A' else u('validationSeconds')+u('overlayPatchSeconds')+u('commitSeconds')),
stages=u('validationSeconds')+u('overlayPatchSeconds')+u('commitSeconds'),path=u('pathSeconds'),baseline=u('baselineRecordSeconds'),jacobian=r['jacobianSeconds'],cpu=r['solveCpuSeconds'],wall=r['solveSeconds']))
metrics[m]={k:stat([v[k] for v in transformed]) for k in transformed[0]}
pairs=[]
for i in range(1,10):
a=next(r for r in by['A'] if r['label']==f'round-{i}');ac=a['counters']
for m in 'BC':
r=next(r for r in by[m] if r['label']==f'round-{i}');c=r['counters']
stages=c['validationSeconds']+c['overlayPatchSeconds']+c['commitSeconds']
metadata=c['baselineRecordSeconds']-ac['baselineRecordSeconds']
local=(ac['originalSeconds']-stages)*1e6/896
pairs.append(dict(round=i,mode=m,probeSavingUs=local,metadataIncrementUs=metadata*1e6/896,
mechanismSavingMs=((ac['originalSeconds']-stages)-metadata)*1e3,
completePathSavingMs=((ac['pathSeconds']-c['pathSeconds'])-metadata)*1e3,
jacobianDelta=r['jacobianSeconds']-a['jacobianSeconds'],cpuDelta=r['solveCpuSeconds']-a['solveCpuSeconds'],wallDelta=r['solveSeconds']-a['solveSeconds']))
pair_stats={m:{k:stat([p[k] for p in pairs if p['mode']==m]) for k in pairs[0] if k not in ['mode','round']} for m in 'BC'}
summary=dict(metrics=metrics,paired=pair_stats,rounds=pairs)
write(OUT/'performance-summary.json',summary)
profile_folder=OUT/'P/detailed'
if not (profile_folder/'attribution.json').exists():profile_folder=OUT/'P/perf-skip-run'
profile=read(profile_folder/'attribution.json');pc=read(profile_folder/'real-skip.json');frequency=profile['frequency']
cat={v['id']:dict(microsecondsPerProbe=v['ticks']/frequency/896*1e6,calls=v['calls']) for v in profile['categories']}
empty=profile['emptyTimerTicks']/frequency/profile['emptyTimerCalls']*1e6
write(OUT/'attribution-summary.json',dict(emptyBracketUs=empty,categories=cat,dispatchOnlyUs=profile['dispatchTicks']/frequency/profile['dispatchRepeats']/896*1e6,source=str(profile_folder)))
a=metrics['A'];b=metrics['B'];c=metrics['C'];paired=pair_stats['C']
below=c['stages']['median']<a['original']['median'];gain=paired['mechanismSavingMs']['median']>0
conclusion='当前实现对 R288 没有净收益;这种低成本 operation 应直接原计算。' if not gain else '当前测量显示 R288 有净收益;仍只授权本案例,下一步应独立验证 codegen contract 和更昂贵 operation。'
lines=['# R288 / position16:typed semantic replay 成本下限实验','',
'## 三个问题的答案','',
f'1. 去掉完整 overlay 与通用事件解释器后,三阶段 replay 的每轮均值中位数为 **{c["stages"]["median"]:.3f} µs/次**,各轮范围 **{c["stages"]["min"]:.3f}–{c["stages"]["max"]:.3f} µs/次**。包含调度、计时及计数的完整目标路径中位数为 **{c["path"]["median"]:.3f} µs/次**。这是本实现、工具链和机器的实测结果,不是理论最低成本。',
f'2. 同批 A 的原 operation 为 **{a["original"]["median"]:.3f} µs/次**;专用 replay **{"低于" if below else "仍高于"}原计算**。与历史 1.143 µs 不直接跨批比较。',
f'3. 计入 baseline 捕获新增成本,配对完整机制净节省中位数为 **{paired["mechanismSavingMs"]["median"]:.3f} ms / 896 次**;含完整路径计时/统计开销的口径为 **{paired["completePathSavingMs"]["median"]:.3f} ms**。**{conclusion}**','',
'## 范围与事务语义','',
'所有实验仅针对 group6 / R288 / position16,在独立 worker 中、原 whole-context guard 失败之后启用。没有扩展 position52、R475 或其他 interval,没有接入生产默认路径。已有工作区改动不属于本实验的修改范围。','',
'- `fp_validate` 只读当前真实 probe;保留输入位比较、有限值、FP/errno、observer、count/capacity、memo owner/recording、全部 active entry 绑定、pipe branch 等保护。',
'- 两次 PT 查询依次按当前有序 entries 验证 miss;第二次查询还显式检查第一个 pending entry 的虚拟匹配。append 位置由当前 count 决定,不使用 baseline slot。',
'- memo key 的 hash 在 baseline 捕获时计算;probe 仍按相同容量、同一 bounded linear-probe 顺序查找,并逐位比较完整 key 和 value。没有复制 memo,也没有绕过 memo 验证。',
'- 只支持已验证的两个固定 schema:67 次近零流量无查询、829 次 PT miss → PT miss。不支持的路径直接 reject。',
'- 这两个 schema 中,物性计算消费的条目均是本 operation 新建的条目。初始化、字段写入、valid 测试和读取之间的条件由固定 schema 保证,并在正确性版对每个 baseline 的所有原始访问逐条证明;不是把这些 guard 删除。',
'- `fp_prepare` 最多构造两个 pending property entries、一个 pipe[0]、新 count、q[45] 和 density/pipe memo reuse 增量。所有真实写入仅在 `fp_commit` 发生。未被写入的 probe 数据保持原值。','',
'专用版是固定 R288 schema 的 typed 原型,不是通用生产 codegen。静态 schema 的核对表从全轨迹真实 generic 记录自动生成;原 native 源文件受既有 SHA-256 约束,未知源代码变化会停止构建。','',
'## Metadata 与临时存储','',
'| 项目 | generic | specialized |','|---|---:|---:|',
'| 每次 baseline 有效 metadata | 2,224–23,344 B | 240 B |',
'| 持久 metadata 固定预留 | 90,224 B | 240 B |',
'| 捕获临时区 | 通用事件记录器 | 96 B |',
'| context/memo/pipe 完整 overlay | 156,816 B | 0 B |',
'| pending patch | 16,384 B write-set,另有 overlay | 456 B,总计且不重复计算 |','',
'- **静态可确定**:medium 常量、字段布局、读取/写入顺序、valid 位演变、最多两个追加条目、memo 类型、pipe[0]、q[45]。',
'- **每个 baseline 动态捕获**:输入、输出、U/D 物性字段、pipe memo key/value、三个 memo hash、入口 count、owner/Jacobian 生命周期及 FP/errno 条件,共 240 B。',
'- **probe 才解析**:当前有序 entries、query miss、当前 count/capacity、memo 实际位置和值、owner/observer/pipe 条件;验证成功后构造 456 B pending。','',
'## 正确性验收','',
'C 验收版:同一次 baseline 真实执行同时生成 generic 事件与小型 metadata;核对 896 份完整访问 schema、896 次 generic/typed accept/reject 和最终 patch,包括逻辑 slot 映射。',
'D 验收版:使用性能版相同的最小 native 捕获 hooks,逐位核对先前真实 generic baseline 记录(跨进程 memo 地址转换为 owner 绑定身份),然后重复全部求解验收。该离线记录只用于 audit;性能 worker 不读取这些记录。','',
'| 检查 | 结果 |','|---|---:|',
'| Jacobian 全元素与 t/y | 896/896 逐字节一致,15,611,904 个矩阵元素 |',
'| evaluator 返回状态、dy/w 和完整 context 出口 | 25,088/25,088 一致 |',
'| 目标 operation 入口/出口、property count/有序字段/valid、pipe cache | 全部一致 |',
'| memo entries、只读生命周期、owner/kernel 绑定及计数 | 全部一致 |',
'| states/outputs/events、最终状态、warning、FP/errno | 全部一致 |',
'| 正常目标 attempts / skip / reject / 原执行 | 896 / 896 / 0 / 0 |',
'| 负例 guard 判定及无写入检查 | C、D 各 20 项通过 |',
'| 晚期 forced reject | C、D 各 7 次,无污染;889 skip、7 原执行 |',
'| count 差异 / relocation / append | 638 / 571 / 1,658 |','',
'负例包含 observer、无容量/第二次追加不足、pipe hit、memo recording/miss/value、输入改变、晚期非有限输出、过期生命周期、FP flags/rounding、entry owner、U/D 查询转 hit,以及已拒绝 metadata 的 consumed/valid/已有条目更新/未知副作用标记。后四项是错误记录传播检查;逐字段读写与 valid 演变的实质检查来自 896 份完整访问 contract 对照。','',
'| accepted | rejected | Newton iterations | convergence failures | nfev | njev | nlu | solverStarts | stateTransitions |',
'|---:|---:|---:|---:|---:|---:|---:|---:|---:|',
'|10840|918|19371|798|44467|896|3106|4|1|','',
'全部使用位比较和逐字节比较,没有更改容差。性能版二进制符号检查确认无 generic interpreter、完整 overlay 或 audit oracle。','',
'## Generic 成本归因','',
f'单独归因 worker 的总 replay 阶段为 {(pc["validationSeconds"]+pc["overlayPatchSeconds"]+pc["commitSeconds"])/896*1e6:.3f} µs/次;该数值含细粒度插桩,不与历史 22.521 µs 强行逐项相加。空 QPC bracket 均值 {empty:.5f} µs,细粒度嵌套计时会额外放大短事件成本。',
'以下分类均为每个目标 probe 的平均原始计时;父项含子项,明确标记重叠,不能相加当作净成本。','',
'| 分类 | µs/次 | 口径 |','|---|---:|---|']
def row(name,k,note):lines.append(f'| {name} | {cat[k]["microsecondsPerProbe"]:.4f} | {note} |')
lines.append(f'| baseline 原执行 + metadata 捕获 | {pc["baselineRecordSeconds"]/896*1e6:.4f} | 新增成本由后面的同批 A/B 配对得出 |')
row('PT first-match / miss',10,'真实顺序扫描;包含一组自身计时')
row('consumed-field 读取事件',1,'含 metadata 分派和嵌套字段比较')
row('其中:字段值/绑定比较',13,'是上一行的子项')
row('valid 测试事件',8,'含事件分派')
row('memo 验证',12,'完整 key/value 与 bounded lookup;SCALAR 事件的子项')
row('完整 overlay 初始化/复制/绑定',14,'包含 context、memo、全部 pipe')
row('logical slot relocation',11,'包含映射冲突检查;属于 ALLOCATE/QUERY 子项')
for name,k in [('pending entry 字段写入',2),('pending valid 更新',3),('追加与 count 更新',7),('pipe patch 写入',5),('write-set 构造',15),('commit',16)]:row(name,k,'原始 generic 实测,含自身计时')
for name,k in [('公共 guard(映射初始化之前)',17),('overlay 清零/poison 初始化',18),('property context/entries 复制',19),('全部 pipe 复制',20),('memo header/entries 复制',21),('overlay 指针绑定',22)]:
if cat[k]['calls']:row(name,k,'细分项;已经包含在对应父项内')
dispatch=profile['dispatchTicks']/frequency/profile['dispatchRepeats']/896*1e6
lines+=[f'| 仅 event metadata 遍历/类型分派微基准 | {dispatch:.4f} | 每份真实 plan 重复 100 次;不做 guard/write,不能当作真实解释器的独立可加项 |',
f'| 空计时 bracket | {empty:.5f} | 每组含首尾 QPC;累计统计另有开销,最终以低扰动 A/B/C 为准 |','',
'通用解释器的实际成本分布在 metadata 访问、字段比较、slot 映射、写入分派和循环控制中,没有一个能独立相减的精确“dispatch 时间”。归因版保留原始嵌套数据,最终性能版移除这些细粒度计时。','',
'## 低扰动 A/B/C 性能','',
'A=原 operation,B=原 generic replay,C=typed/minimal replay。各预热一次,9 组按 ABC → BCA → CAB 循环,全部串行。无 Reference、完整 context 比较或详细访问日志;保留机制所需 metadata、guard、patch 和少量累计计时。每轮 B/C 均 skip 896、reject 0、原执行 0,并核对采样结果和全部指定计数。','',
'### 中位数 [min, max]','',
'| 项目 | A | B | C |','|---|---:|---:|---:|']
for k in ['original','validation','patch','commit','stages','path','outsideStages','baseline','jacobian','cpu','wall']:
unit='s' if k in ['jacobian','cpu','wall'] else 'µs/次'
lines.append(f'| {k} ({unit}) | {fmt(a[k])} | {fmt(b[k])} | {fmt(c[k])} |')
lines+=['','replay 三阶段先在每一轮内求和再取中位数,因此不必等于三个分项中位数之和。baseline 项包括该次 baseline operation 本身;捕获新增成本应减去 A 的 baseline 项。B 的 patch 项含全 overlay copy + write-set;C 的 patch 项仅小型 pending 构造。outsideStages 是完整路径减去被包围的阶段时间,包含外层计时、统计、调度与元数据 bookkeeping;它不是纯计时器成本。无自然 reject,fallback 总时间为 0,不代表单次 fallback 免费。','',
'### 每轮原始数据(实际执行顺序)','',
'| 轮 / 模式 | 原 op µs | validation µs | patch µs | commit µs | replay 三阶段 µs | 完整路径 µs | baseline µs | Jacobian s | CPU s | wall s |','|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|']
for r in rows:
v=r['counters'];u=lambda k:v[k]/896*1e6
lines.append(f'| {r["label"]} / {r["mode"]} | {u("originalSeconds"):.3f} | {u("validationSeconds"):.3f} | {u("overlayPatchSeconds"):.3f} | {u("commitSeconds"):.3f} | {u("validationSeconds")+u("overlayPatchSeconds")+u("commitSeconds"):.3f} | {u("pathSeconds"):.3f} | {u("baselineRecordSeconds"):.3f} | {r["jacobianSeconds"]:.6f} | {r["solveCpuSeconds"]:.6f} | {r["solveSeconds"]:.6f} |')
lines+=['','### 两种收益口径','',
'- probe 局部净节省 = A 原 operation − validation − patch − commit。',
'- 完整机制净节省 = 896 × probe 局部净节省 − (baseline 捕获总耗时 − A baseline 原执行耗时)。',
'- 再给出包含外层调度、计时与统计的保守口径:A 完整目标路径 − replay 完整目标路径 − baseline 新增成本。',
'- 正数为节省,负数为额外成本。时间均使用同组配对,不拿历史 1.143 µs 作分母。','',
'| 轮 / 模式 | probe 净节省 µs/次 | baseline 新增 µs/次 | 完整机制净节省 ms/896 次 | 含外层开销净节省 ms | Jacobian Δ s | CPU Δ s | wall Δ s |','|---|---:|---:|---:|---:|---:|---:|---:|']
for p in pairs:lines.append(f'| {p["round"]} / {p["mode"]} | {p["probeSavingUs"]:.3f} | {p["metadataIncrementUs"]:.3f} | {p["mechanismSavingMs"]:.3f} | {p["completePathSavingMs"]:.3f} | {p["jacobianDelta"]:.6f} | {p["cpuDelta"]:.6f} | {p["wallDelta"]:.6f} |')
lines+=['','| 配对统计:中位数 [min, max] | B | C |','|---|---:|---:|']
for k in pair_stats['B']:lines.append(f'| {k} | {fmt(pair_stats["B"][k],6)} | {fmt(pair_stats["C"][k],6)} |')
lines+=['','所有 operation 阶段使用 QPC 墙钟计时,包含调度长尾;积分 CPU 时间单独来自求解器统计。系统负载和频率使本批数据存在较大波动(包括 commit 分项的极端值),因此报告同时保留中位数、min/max 和每组配对结果,不将归因插桩或调度延迟声称为精确的算法 CPU 成本。9 组局部与完整机制净收益均为负;不从总 Jacobian/积分墙钟的正负波动推断整个 local probe 的收益,也不声称已证明理论成本下限。计时未做不可靠的逐事件扣减。','',
'## 停止条件与交付','',conclusion,
'本轮到此停止,不继续为 R288 寻找更多微优化,也不扩展到 position52。保留生产原计算路径和原 whole-context guard。','',
'代码入口:`tests/manual/specialized_replay_experiment.py`。核心为 `typed_replay_core.inc`、`typed_replay_runtime.inc`;`typed_contract_check.inc` 与 `typed_oracle.inc` 只用于 audit。报告生成器为 `analyze_typed_replay.py`。','',
'复现顺序(项目根目录,使用既有工具链,不安装依赖):','',
'```powershell',
'.venv-win/Scripts/python.exe tests/manual/specialized_replay_experiment.py prepare --mode P',
'.venv-win/Scripts/python.exe tests/manual/specialized_replay_experiment.py run --mode P',
'.venv-win/Scripts/python.exe tests/manual/specialized_replay_experiment.py prepare --mode C --audit',
'.venv-win/Scripts/python.exe tests/manual/specialized_replay_experiment.py run --mode C --audit',
'.venv-win/Scripts/python.exe tests/manual/specialized_replay_experiment.py run --mode C --audit --label forced-reject --force 128',
'.venv-win/Scripts/python.exe tests/manual/specialized_replay_experiment.py prepare --mode D --audit',
'.venv-win/Scripts/python.exe tests/manual/specialized_replay_experiment.py run --mode D --audit',
'.venv-win/Scripts/python.exe tests/manual/specialized_replay_experiment.py run --mode D --audit --label forced-reject --force 128',
'.venv-win/Scripts/python.exe tests/manual/specialized_replay_experiment.py prepare --mode A',
'.venv-win/Scripts/python.exe tests/manual/specialized_replay_experiment.py prepare --mode B',
'.venv-win/Scripts/python.exe tests/manual/specialized_replay_experiment.py prepare --mode C',
'.venv-win/Scripts/python.exe tests/manual/specialized_replay_experiment.py benchmark --rounds 9',
'.venv-win/Scripts/python.exe tests/manual/analyze_typed_replay.py','```','',
'证据目录:`test/r288-typed-replay-20260917/`。`performance.json` 保存 27 次原始记录;`performance-summary.json` 保存统计和逐组净收益;`byte-comparison.json` 保存逐字节结果;C/D 的 `validation.json` 和 `typed-summary.json` 保存全部正确性计数。P 的 `attribution.json` 保存原始 tick、调用次数和计时器测量。','']
(HERE/'r288_typed_replay_report.md').write_text('\n'.join(lines),encoding='utf-8')
print(json.dumps(dict(A=a['original'],B=b['stages'],C=c['stages'],C_path=c['path'],net=paired),ensure_ascii=False,indent=2))
if __name__=='__main__':main()
+102
View File
@@ -0,0 +1,102 @@
"""Summarize full-trajectory position379 keys and directly timed tail work."""
from collections import Counter,defaultdict
import hashlib,json,statistics,struct
from pathlib import Path
ROOT=Path(__file__).resolve().parents[2]
OUT=ROOT/'test/position379-tail-20260917'
RECORD=struct.Struct('<Qii5QddIHHiIHHi3Qii')
def stats(xs):
return dict(count=len(xs),mean=statistics.mean(xs),median=statistics.median(xs),min=min(xs),max=max(xs),total=sum(xs))
def analyze(folder):
info=json.loads((folder/'tail.json').read_text());assert info['recordBytes']==RECORD.size==128
records=list(RECORD.iter_unpack((folder/'tail-records.bin').read_bytes()))
assert len(records)==info['entries']
clock=info['frequency'];scale=1e6/clock
empty=statistics.median(x[0] for x in struct.iter_unpack('<Q',(folder/'empty-clock.bin').read_bytes()))
groups=defaultdict(list)
for r in records:groups[r[0]].append(r)
jacobians=[];key_hits=0;general_hits=0;group_hits=Counter();env_changes=0;output_mismatches=0
environment_changes={name:Counter() for name in ('mxcsr','x87_control','x87_status','errno')}
rejects=Counter();baseline_env_changes=0
for j,rr in groups.items():
baseline=[r for r in rr if r[1]==-1];assert len(baseline)==1
b=baseline[0];assert len({r[1] for r in rr})==len(rr)
baseline_env_changes+=b[10:14]!=b[14:18]
buckets=defaultdict(list)
for r in rr:
key=r[3:8];buckets[key].append(r[1]);env_changes+=r[10:14]!=r[14:18]
for i,name in enumerate(environment_changes):
if r[10+i]!=r[14+i]:environment_changes[name][r[10+i],r[14+i]]+=1
if r[2]:
assert r[10:14]==r[14:18]==b[10:14]==b[14:18]
assert r[3:8]==b[3:8]
elif r[1]>=0:
if b[10:14]!=b[14:18]:rejects['baseline_changes_x87_status']+=1
elif r[10:14]!=b[10:14]:rejects['environment_differs']+=1
elif key!=b[3:8]:rejects['key_differs']+=1
else:rejects['other']+=1
if r[1]>=0 and key==b[3:8]:
key_hits+=1;group_hits[r[1]]+=1
if struct.pack('<dd',r[8],r[9])!=struct.pack('<dd',b[8],b[9]):output_mismatches+=1
general_hits+=len(rr)-len(buckets)
jacobians.append(dict(jacobian=j,baselineKey=[f'{x:016x}' for x in b[3:8]],
uniqueKeys=len(buckets),baselineSharingGroups=[r[1] for r in rr if r[1]>=0 and r[3:8]==b[3:8]],
groups=[dict(key=[f'{x:016x}' for x in key],groups=colors) for key,colors in buckets.items()],
keyHits=sum(r[1]>=0 and r[3:8]==b[3:8] for r in rr),
guardedHits=sum(r[2] for r in rr)))
assert len(groups)==896 and output_mismatches==0
baseline=[r for r in records if r[1]<0];probes=[r for r in records if r[1]>=0];hits=[r for r in records if r[2]];executed=[r for r in records if r[21]]
assert sum(r[18] for r in records)==info['tailTicks']
# Conservative gate: charge raw lookup/capture/reset (including their timer
# overhead), save tail work after subtracting one empty timer per hit.
saved=sum(max(0,r[18]-empty) for r in hits)*scale
lookup=sum(r[19] for r in probes)*scale
baseline_cost=(info['baseLookupTicks']+info['captureTicks']+info['resetTicks'])*scale
guarded_hit_rate=len(hits)/len(probes)
result=dict(label=folder.name,clockHz=clock,emptyClockNs=empty/clock*1e9,entries=len(records),tailExecutions=len(executed),baselineEntries=len(baseline),probeEntries=len(probes),
tailRawUs=stats([r[18]*scale for r in executed]),tailCorrectedUs=stats([max(0,r[18]-empty)*scale for r in executed]),
hitTailCorrectedUs=stats([max(0,r[18]-empty)*scale for r in hits if r[21]]) if hits and any(r[21] for r in hits) else None,
lookupHitUs=stats([r[19]*scale for r in hits]),lookupMissUs=stats([r[19]*scale for r in probes if not r[2]]),
baselineAddedUsPerJacobian=baseline_cost/896,keyHits=key_hits,keyHitRate=key_hits/len(probes),
baselineCaptureWithoutResetUs=stats([(r[19]+r[20])*scale for r in baseline]),
unlimitedMemoTheoreticalHits=general_hits,unlimitedMemoHitRate=general_hits/len(probes),
guardedHits=len(hits),guardedHitRate=guarded_hit_rate,envChanges=env_changes,bitKeyOutputMismatches=output_mismatches,
baselineEnvChanges=baseline_env_changes,rejects=dict(rejects),
environmentChanges={name:[dict(before=f'0x{a:x}',after=f'0x{b:x}',count=n) for (a,b),n in c.items()] for name,c in environment_changes.items()},
uniqueKeysHistogram=dict(Counter(j['uniqueKeys'] for j in jacobians)),groupHits=dict(group_hits),
rawTailSavedMs=sum(r[18] for r in hits)*scale/1000,correctedTailSavedMs=saved/1000,
allProbeLookupMs=lookup/1000,baselineAddedMs=baseline_cost/1000,
conservativeDiagnosticNetMs=(saved-lookup-baseline_cost)/1000,
hitBudgetUsAfterBaselineAndMisses=(saved-baseline_cost-sum(r[19] for r in probes if not r[2])*scale)/len(hits) if hits else 0,
inputSha256=hashlib.sha256((folder/'tail-records.bin').read_bytes()).hexdigest())
(folder/'key-groups.json').write_text(json.dumps(jacobians,indent=2)+'\n',encoding='utf-8')
(folder/'analysis.json').write_text(json.dumps(result,indent=2)+'\n',encoding='utf-8')
return result
def main():
rows=[analyze(p) for p in sorted(OUT.glob('diagnostic-*')) if (p/'tail-records.bin').exists()]
assert rows
# An original-execution miss may legitimately change FP status. Only the
# proposed hit subset must have zero effect (asserted above).
gate=all(r['conservativeDiagnosticNetMs']>0 and r['lookupHitUs']['mean']<r['hitTailCorrectedUs']['mean'] and r['keyHitRate']>.8 for r in rows)
timing=[]
for folder in sorted(OUT.glob('tail-only-*')):
if not (folder/'tail-records.bin').exists():continue
info=json.loads((folder/'tail.json').read_text());records=list(RECORD.iter_unpack((folder/'tail-records.bin').read_bytes()))
assert len(records)==info['entries']==info['executions']==24192
empty=statistics.median(x[0] for x in struct.iter_unpack('<Q',(folder/'empty-clock.bin').read_bytes()))
scale=1e6/info['frequency']
timing.append(dict(label=folder.name,entries=len(records),
tailRawUs=stats([r[18]*scale for r in records]),
tailCorrectedUs=stats([max(0,r[18]-empty)*scale for r in records]),
warning='K-only control: all key/environment sampling occurs AFTER tail. Ignore hit/G/H in this worker.',
inputSha256=hashlib.sha256((folder/'tail-records.bin').read_bytes()).hexdigest()))
result=dict(rounds=rows,tailOnlyTiming=timing,profitableDiagnosticGate=gate,
note='Preliminary gate only: full in-solver mechanism cost and exact context audit required before a benefit claim.')
(OUT/'diagnostic-analysis.json').write_text(json.dumps(result,indent=2)+'\n',encoding='utf-8')
print(json.dumps(result,indent=2))
if __name__=='__main__':main()
+120
View File
@@ -0,0 +1,120 @@
"""Exact numerical gates and measured performance report for local probes."""
from pathlib import Path
import hashlib, json, statistics, subprocess
ROOT=Path(__file__).resolve().parents[2]
OUT=ROOT/'test/local-probe-20260917'
def read(path):return json.loads((OUT/path).read_text(encoding='utf-8'))
def table(headers,rows):return '\n| '+' | '.join(headers)+' |\n| '+' | '.join(['---']*len(headers))+' |\n'+'\n'.join('| '+' | '.join(map(str,row))+' |' for row in rows)+'\n'
def compare_matrices(left,right,n):
size=8*(1+n+n*n);checked=0
with left.open('rb') as a,right.open('rb') as b:
while True:
x,y=a.read(size),b.read(size)
if not x and not y:break
assert len(x)==len(y)==size,('matrix record size',checked)
assert x[:8*(1+n)]==y[:8*(1+n)],('Jacobian input t/y differs',checked)
assert x[8*(1+n):]==y[8*(1+n):],('Jacobian matrix bits differ',checked)
checked+=1
return dict(matrices=checked,entries=checked*n*n,differentEntries=0,inputTimesAndStatesIdentical=True)
def main():
plan=read('plan.json');reference=read('reference-audit/measurement.json')
cases=['group26-final-audit','small-final-audit','all-audit']
exact=['statesSha256','outputsSha256','eventsSha256','finalState','final','propertyWarnings',
'acceptedSteps','rejectedSteps','stateTransitions','solverStarts','nfev','njev','nlu']
checks=[]
for path in sorted(OUT.glob('*/measurement.json')):
obj=json.loads(path.read_text(encoding='utf-8'))
differences=[k for k in exact if obj[k]!=reference[k]]
for k in ('newtonIterations','newtonConvergenceFailures'):
if obj['diagnostic'][k]!=reference['diagnostic'][k]:differences.append(k)
checks.append(dict(run=path.parent.name,differences=differences))
assert not any(x['differences'] for x in checks),checks
matrices={name:compare_matrices(OUT/'reference-audit/jacobians.bin',OUT/name/'jacobians.bin',len(plan['stateKeys'])) for name in cases}
allrun=read('all-audit/measurement.json');diag=allrun['diagnostic'];groups=[]
for theory,actual in zip(plan['groups'],diag['groups']):
assert all(a+b==896 for a,b in zip(actual['executed'],actual['skipped']))
assert not any(actual['skipped'][i] for i in theory['affectedOperations'])
groups.append({**theory,**actual,'executedTotal':sum(actual['executed']),'skippedTotal':sum(actual['skipped'])})
metrics=['solveCpuSeconds','solveSeconds','processSeconds','jacobianSeconds']
measurements=[];medians={}
for name in ('reference','all'):
runs=[read(f'{name}-run-{i}/measurement.json') for i in range(5)]
for i,r in enumerate(runs):
measurements.append(dict(mode=name,run=i,**{k:r[k] for k in metrics[:-1]},jacobianSeconds=r['diagnostic']['jacobianSeconds']))
medians[name]={k:statistics.median(r['diagnostic'][k] if k=='jacobianSeconds' else r[k] for r in runs) for k in metrics}
reductions={k:1-medians['all'][k]/medians['reference'][k] for k in metrics}
source_meta=read('audit/build-metadata.json')
source_unchanged=all(hashlib.sha256((ROOT/'native'/key).read_text(encoding='utf-8').encode()).hexdigest()==value for key,value in source_meta['sourceHashes'].items())
assert source_unchanged
# The original evaluator, ordinary residual, temperature checker and linear
# solver are not patched. The added evaluator is reachable only from lp_eval.
original=(OUT/'original-model.c').read_text(encoding='utf-8')
for mode in ('audit','worker'):
emitted=(OUT/mode/'model.c').read_text(encoding='utf-8')
assert original in emitted,'original model source changed'
old=ROOT/'test/probe-state-residual-20260916/baseline-run-1'
prior={}
for name in ('states','outputs'):
if (old/f'{name}.bin').exists():
with (old/f'{name}.bin').open('rb') as f:prior[name]=hashlib.file_digest(f,'sha256').hexdigest()==reference[name+'Sha256']
assert all(prior.values())
result=dict(numericalChecks=checks,matrixComparisons=matrices,
shadow=dict(comparisons=diag['auditComparisons'],differentComparisons=diag['auditDifferences'],
comparedDerivativeAndOutputValues=diag['auditComparisons']*(len(plan['stateKeys'])+len(reference['final']))),
originalNativeSourcesUnchanged=source_unchanged,originalGeneratedModelVerbatim=True,priorUninstrumentedOutputsEqual=prior,
medians=medians,reductions=reductions,measurements=measurements,groups=groups,
workspaceBytes=diag['workspaceBytes'],copiedBytes=diag['contextCopiedBytes'],comparedBytes=diag['contextComparedBytes'])
(OUT/'comparison.json').write_text(json.dumps(result,ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
report=[]
report.append('**局部 Jacobian probe 实验结果(2026-09-17)**\n')
report.append('本次增加独立实验构建工具,未接入前端或生产默认路径。生产 ordinary residual、物性算法、accepted-step check、线性求解器及原始生成模型源码保持不变;实验 worker 只在 Jacobian callback 中选择额外生成的局部求值函数。对象为 `tests/data/test-mql-8-corrected.json`,原提交 `1aac220`,CVODE 7.4.0,0–10 s,BDF,rtol=1e-8,沿用原 atol、步长和线性求解设置。\n')
report.append('**实施范围与保护**\n')
report.append('从现有 StateDependencies 和 EvaluationSchedule 保守计算各 group 的状态依赖闭包,未解析用户 C 或手写组件白名单。未知依赖按全部状态处理;当前实验拒绝包含循环代数块的模型。每组生成连续的不受扰动调度区间。扰动时间和组外状态必须与本次 Jacobian baseline 逐位一致,跨 Jacobian 基准失效。\n')
report.append('原型只裁剪 flow/stream/linear/alias 调度段。气体状态准备、后续节点能量、端口传播、机械方程、各管路诊断输出和有限值检查继续执行原代码。纯代数区间依据闭包复制基准的所有区间输出;包含上下文调用的区间,还要求入口的完整 live property states、count/capacity、observer/Jacobian 指针及全部 pipe cache 与 baseline 逐位一致,再恢复基准出口上下文和所有区间输出。任何不一致均执行原计算,无近似匹配、无放宽容差。\n')
report.append('保留 property context 中的 h/rho/黏度/等熵派生字段及种子顺序,避免只按显式 p,T 或 p,h 误判等价。已有 Jacobian 标量表在 baseline 后保持只读,跳过调用只减少其评估/复用统计,不改变表内数值。温度观察路径不走局部函数。\n')
report.append('初版第26组通过数值验证,但无条件保存所有快照导致约5.11 GB复制。最终版按启用组选择快照,并在纯代数操作之间共享上下文检查点:321个调度边界只需57个上下文检查点。只共享确定不含上下文调用的检查点;未放宽任何数值或上下文比较条件。\n')
report.append('**先验证数值等价**\n')
report.append(table(['验证阶段','完整矩阵数','逐位比较矩阵元素数','矩阵差异','每次Jacobian求值的dy/w对照'],[(name,matrices[name]['matrices'],matrices[name]['entries'],0,'25,088次,差异0') for name in cases]))
report.append('每份矩阵记录同时比较 t、132维输入 y 和132×132矩阵的原始双精度位,包含稀疏结构之外的零元素。另用独立 Jacobian memo workspace 在同一(t,y)执行原实现,比较每次 baseline/probe 的全部132个dy及1784个w;任意差异立即终止。该影子计算只在 audit worker 启用,不计入求解器nfev;性能 worker 完全关闭影子计算、矩阵落盘及内核入口计数。\n')
report.append(table(['项目','原实现','局部全部group','结果'],[
('states / outputs 原始二进制','完整文件','完整文件','逐字节一致'),('warning','全部告警字段','全部告警字段','一致'),
('event','事件时刻及132维事件后状态','同左','逐位一致'),
*[(k,reference[k],allrun[k],'一致') for k in ('acceptedSteps','rejectedSteps','stateTransitions','solverStarts','nfev','njev','nlu')],
('Newton iterations',reference['diagnostic']['newtonIterations'],diag['newtonIterations'],'一致'),
('Newton convergence failures',reference['diagnostic']['newtonConvergenceFailures'],diag['newtonConvergenceFailures'],'一致')]))
report.append('所有已保存运行的完整结果均对照通过;还核对了前一轮真正未插桩 worker 的 states/outputs,两者逐字节相同。图闭包及循环块拒绝的5项单元检查通过;原生 guard 测试验证了同p,T但派生rho改变、pipe cache改变、上下文长度/指针改变、t改变、组外状态改变和跨基准失效均不会误命中,并验证纯代数区间恢复不会覆盖当前物性上下文。未发现需要用容差掩盖的依赖裁剪差异。\n')
report.append('**实际执行范围**\n')
total=sum(g['executedTotal']+g['skippedTotal'] for g in groups);skipped=sum(g['skippedTotal'] for g in groups)
report.append(f'Jacobian次数仍为896,每个Jacobian仍调用1次baseline及27次probe,合计25,088次,其中probe为24,192次。probe 调度操作从 **{total:,}** 次降为 **{total-skipped:,}** 次,实际跳过 **{skipped:,}({skipped/total:.2%})**。与此前88.12%的结构无关操作比例相比,完整上下文保护、保留的诊断/后续操作限制了可安全跳过的范围。\n')
report.append(table(['group','扰动状态数','图上受影响操作数/484','实际执行操作/896次probe','跳过比例','上下文保护回退次数'],[(g['color'],len(g['stateIndices']),len(g['affectedOperations']),g['executedTotal'],f'{g["skippedTotal"]/(484*896):.2%}',g['contextMisses']) for g in groups]))
report.append('每组的被扰动状态、受影响操作编号、每一项操作的执行和跳过计数均保存在 `comparison.json`;操作编号、名称、输入输出和依赖状态在 `plan.json`。所有图上可能受影响操作的跳过次数均为0。下面统计24,192次probe实际进入内核的次数,不含baseline、影子计算或普通residual。\n')
kernel_rows=[]
for i,name in enumerate(diag['kernelNames']):
a=sum(row[i] for row in reference['diagnostic']['kernelCalls'][1:]);b=sum(row[i] for row in diag['kernelCalls'][1:])
kernel_rows.append((name,a,b,f'{1-b/a:.2%}' if a else '—'))
report.append(table(['实际计算','原实现','局部probe','减少'],kernel_rows))
report.append('gas、PH、density和pipe root的实际调用未变;已有精确缓存本来就已避免大量无关求根。此次节省来自流量、等熵、黏度及属性查找的实际执行。管路诊断仍全部执行,未将其删除或替换为未验证的近似值。\n')
report.append('**实际耗时**\n')
report.append('同一不含影子核验的可执行文件,通过 LOCAL_PROBE_MASK=0 和全组mask切换;排除一次预热,5对交替顺序串行测量,每次均完整输出states/outputs。Jacobian为整个callback的QPC墙钟时间;积分CPU为现有整进程CPU计时;完整native仿真时间从启动worker到输出完成并退出,包含DLL加载、初始化、积分、采样、输出重放/编码/落盘及退出。该数字不包含浏览器、API排队或已完成的编译。\n')
names={'jacobianSeconds':'Jacobian阶段墙钟','solveCpuSeconds':'积分CPU','solveSeconds':'积分墙钟','processSeconds':'完整native仿真墙钟'}
report.append(table(['中位数','原实现 / s','局部全部group / s','减少'],[(names[k],f'{medians["reference"][k]:.6f}',f'{medians["all"][k]:.6f}',f'{reductions[k]:.2%}') for k in ('jacobianSeconds','solveCpuSeconds','solveSeconds','processSeconds')]))
report.append(table(['序号/模式','Jacobian / s','积分CPU / s','积分墙钟 / s','完整native / s'],[(f'{r["run"]}/{r["mode"]}',*[f'{r[k]:.6f}' for k in ('jacobianSeconds','solveCpuSeconds','solveSeconds','processSeconds')]) for r in measurements]))
report.append('全部样本保留,包括原实现第2次较慢的样本,没有按有利结果筛选。当前测量显示收益,但绝对秒数受机器负载和频率影响;不能把前一日7.07s与今日结果直接相减归为优化收益。\n')
report.append(f'最终局部工作区占 **{diag["workspaceBytes"]:,} 字节**。整轮额外上下文复制 **{diag["contextCopiedBytes"]:,} 字节**、比较请求覆盖 **{diag["contextComparedBytes"]:,} 字节**;memcmp可能提前终止,后者不是硬件实际读取字节数。上述机制的开销已经包含在性能测量中。还包括switch分派、组外状态检查和恢复输出。\n')
cold=[]
for name in ('reference','all'):
path=OUT/f'cold-{name}-pipeline.json'
if path.exists():cold.append(json.loads(path.read_text()))
if cold:
report.append('另外从工程JSON开始执行实验的冷构建完整链(读取/方程生成/实验代码生成/GCC编译/DLL准备/worker运行/输出),实测如下。两种运行使用同一实验构建机制,冷编译时间会波动;这不是前端API链路测量,也不能用单次冷启动数据判断probe收益。\n')
report.append(table(['冷构建链','准备与构建 / s','完整native / s','完整实验流水线 / s'],[(x['mode'],f'{x["prepareSeconds"]:.6f}',f'{x["workerSeconds"]:.6f}',f'{x["pipelineSeconds"]:.6f}') for x in cold]))
report.append('**边界与后续方向**\n')
report.append('此版本可作为验证原型,暂不建议直接接入默认求解路径:仅支持当前无循环调度的生成模型,快照内存随模型规模增长,实验状态为独立worker内的静态运行期对象,不适合直接嵌入同进程并行仿真。较大group的属性上下文经常变化,保守整上下文比较会使本可独立的区间回退,收益小于最初理论无关计算预算。\n')
report.append('下一步若继续推进,应先研究保留完整读写语义的更细粒度context依赖,再扩展到诊断/其他阶段。不能直接删除上下文保护,也不能把88.12%的无关操作比例解释为可实现的同等加速。当前实验已证明可以在不改变数值轨迹的前提下减少一部分probe计算,但没有消除全部无关执行。\n')
(OUT/'report.md').write_text('\n'.join(report),encoding='utf-8')
print(json.dumps(dict(medians=medians,reductions=reductions,matrices=matrices,skippedOperations=skipped,operationReduction=skipped/total,numericalRuns=len(checks)),ensure_ascii=False,indent=2))
if __name__=='__main__':main()
+339
View File
@@ -0,0 +1,339 @@
# Context 访问级清单:实测数据
由 `analyze_context_access.py` 从访问事件生成。slot、Jacobian 和 position 均从 0 开始;group=-1 为 baseline。
以下以 Jacobian 200 为主案例,补充 position 52 的 PH 命中/未命中路径。完整逐次记录位于 `test/context-access-20260917/operations.json`;原始访问事件在 `audit/access.jsonl`。
## Jacobian 1,group 18,position 52
t=1.3389986581645337e-08;count 11 → 12;capacity=256;输出 `0.033351539732391834`;model evaluator 返回 `1`。
### 查询与首次匹配
| 顺序 | 类型 | 完整 key:p, T 或 h, R, cp, Tref, slope, mu, muT, S | medium kind | 首个匹配 slot | 扫描条数 |
|---|---|---|---|---|---|
| 1 | PH | `141437.08341422619, 200554.29912985844, 2077.2643940499802, 5193.1609851249505, 293.14999999999998, 0, 1.9599999999999999e-05, 293.14999999999998, 79.400000000000006` | 1 | 7 | 8 |
| 2 | PT | `141437.08341422619, 336.75814451285157, 2077.2643940499802, 5193.1609851249505, 293.14999999999998, 0, 1.9599999999999999e-05, 293.14999999999998, 79.400000000000006` | 1 | 7 | 8 |
| 3 | PT | `100000.73403195803, 293.15086386132788, 2077.2643940499802, 5193.1609851249505, 293.14999999999998, 0, 1.9599999999999999e-05, 293.14999999999998, 79.400000000000006` | 1 | miss | 11 |
### 实际选中后的字段读取
| 域 / slot | 字段 |
|---|---|
| pipes[28] | `valid` |
| states[7] | `T`, `isentropic_exponent`, `isentropic_factor`, `medium`, `medium.Tref`, `medium.cp`, `medium.real_helium`, `medium.slope`, `mu`, `p`, `rho`, `temperatures` |
| states[11] | `T`, `isentropic_exponent`, `isentropic_factor`, `jacobian`, `medium.R`, `medium.S`, `medium.Tref`, `medium.cp`, `medium.mu`, `medium.muT`, `medium.real_helium`, `medium.slope`, `p`, `rho`, `temperatures` |
查询扫描另外按短路次序读取 `valid & PT/H`、`p`、`T/h`、medium;未匹配项的字段不等于被用于物性计算。
### 选中后的 valid 测试
| 顺序 | slot | mask | 结果 |
|---|---|---|---|
| 1 | 7 | 8 | 8 |
| 2 | 7 | 16 | 16 |
| 3 | 11 | 16 | 0 |
| 4 | 11 | 4 | 0 |
| 5 | 7 | 4 | 4 |
### 必须保留的 probe 数据
保留入口 `states[0:11]` 的全部字段;此路径没有写已有物性条目。仅追加以下新条目,修改本 operation 的 pipe 槽;其余 pipe 槽保持 probe 值。
### 有序写入动作
| 顺序 | 目标 | 写入值 | 已知同值写入 |
|---|---|---|---|
| 1 | `context[0].count` | 12 | — |
| 2 | `states[11].*` | 完整 struct 清零(显式写入) | — |
| 3 | `states[11].medium` | 复制上述完整 medium | — |
| 4 | `states[11].p` | 100000.73403195803 | — |
| 5 | `states[11].T` | 293.15086386132788 | — |
| 6 | `states[11].valid` | 1 | — |
| 7 | `states[11].temperatures` | NULL | 是 |
| 8 | `states[11].jacobian` | 当前 context 的 Jacobian memo 指针 | — |
| 9 | `states[11].rho` | 0.16419111112032281 | — |
| 10 | `states[11].valid` | 5 | — |
| 11 | `states[11].isentropic_factor` | 0.59983509720368744 | — |
| 12 | `states[11].isentropic_exponent` | 0.4000169505487694 | — |
| 13 | `states[11].valid` | 21 | — |
| 14 | `pipes[28].valid` | 0 | 是 |
| 15 | `pipes[28].medium` | 复制上述完整 medium | — |
| 16 | `pipes[28].p1` | 141437.08341422619 | — |
| 17 | `pipes[28].p2` | 100000.73403195803 | — |
| 18 | `pipes[28].T` | 336.75814451285157 | — |
| 19 | `pipes[28].diameter` | 0.02 | — |
| 20 | `pipes[28].length` | 1 | — |
| 21 | `pipes[28].roughness` | 0.0022499999999999998 | — |
| 22 | `pipes[28].kind` | 1 | — |
| 23 | `pipes[28].flow` | 0.033351539732391834 | — |
| 24 | `pipes[28].valid` | 1 | — |
新条目分配分支均为 positive/finite key 且 `count < capacity`;每次在当前 count 追加,再 count++。未初始化槽的 struct 首次清零不计入“已知同值写入”。
### Fallback 原因
查询 key/首匹配逻辑条目不同、已消费字段或已测试 valid 位不同、容量不足转 scratch、observer 非空、pipe 命中分支改变,或无法建立无冲突的 slot 映射时,执行原 operation。此清单不授权放宽现有 guard。
## Jacobian 2,group 18,position 52
t=2.5903206074281044e-08;count 16 → 18;capacity=256;输出 `0.13649710784608116`;model evaluator 返回 `1`。
### 查询与首次匹配
| 顺序 | 类型 | 完整 key:p, T 或 h, R, cp, Tref, slope, mu, muT, S | medium kind | 首个匹配 slot | 扫描条数 |
|---|---|---|---|---|---|
| 1 | PH | `755686.80622119282, 1871284.0942772815, 2077.2643940499802, 5193.1609851249505, 293.14999999999998, 0, 1.9599999999999999e-05, 293.14999999999998, 79.400000000000006` | 1 | miss | 16 |
| 2 | PT | `755686.80622119282, 658.33729595925956, 2077.2643940499802, 5193.1609851249505, 293.14999999999998, 0, 1.9599999999999999e-05, 293.14999999999998, 79.400000000000006` | 1 | miss | 16 |
| 3 | PT | `755686.80622119282, 658.33729595925956, 2077.2643940499802, 5193.1609851249505, 293.14999999999998, 0, 1.9599999999999999e-05, 293.14999999999998, 79.400000000000006` | 1 | 16 | 17 |
| 4 | PT | `100004.87065313551, 293.15011545458538, 2077.2643940499802, 5193.1609851249505, 293.14999999999998, 0, 1.9599999999999999e-05, 293.14999999999998, 79.400000000000006` | 1 | miss | 17 |
### 实际选中后的字段读取
| 域 / slot | 字段 |
|---|---|
| pipes[28] | `valid` |
| states[16] | `T`, `isentropic_exponent`, `isentropic_factor`, `jacobian`, `medium`, `medium.R`, `medium.S`, `medium.Tref`, `medium.cp`, `medium.mu`, `medium.muT`, `medium.real_helium`, `medium.slope`, `mu`, `p`, `rho`, `temperatures` |
| states[17] | `T`, `isentropic_exponent`, `isentropic_factor`, `jacobian`, `medium.R`, `medium.S`, `medium.Tref`, `medium.cp`, `medium.mu`, `medium.muT`, `medium.real_helium`, `medium.slope`, `p`, `rho`, `temperatures` |
查询扫描另外按短路次序读取 `valid & PT/H`、`p`、`T/h`、medium;未匹配项的字段不等于被用于物性计算。
### 选中后的 valid 测试
| 顺序 | slot | mask | 结果 |
|---|---|---|---|
| 1 | 16 | 2 | 0 |
| 2 | 16 | 8 | 0 |
| 3 | 16 | 16 | 0 |
| 4 | 16 | 4 | 0 |
| 5 | 17 | 16 | 0 |
| 6 | 17 | 4 | 0 |
| 7 | 16 | 4 | 4 |
### 必须保留的 probe 数据
保留入口 `states[0:16]` 的全部字段;此路径没有写已有物性条目。仅追加以下新条目,修改本 operation 的 pipe 槽;其余 pipe 槽保持 probe 值。
### 有序写入动作
| 顺序 | 目标 | 写入值 | 已知同值写入 |
|---|---|---|---|
| 1 | `context[0].count` | 17 | — |
| 2 | `states[16].*` | 完整 struct 清零(显式写入) | — |
| 3 | `states[16].medium` | 复制上述完整 medium | — |
| 4 | `states[16].p` | 755686.80622119282 | — |
| 5 | `states[16].T` | 658.33729595925956 | — |
| 6 | `states[16].valid` | 1 | — |
| 7 | `states[16].temperatures` | NULL | 是 |
| 8 | `states[16].jacobian` | 当前 context 的 Jacobian memo 指针 | — |
| 9 | `states[16].h` | 1871284.0942772815 | — |
| 10 | `states[16].valid` | 3 | — |
| 11 | `states[16].mu` | 3.4336896373307344e-05 | — |
| 12 | `states[16].valid` | 11 | — |
| 13 | `states[16].rho` | 0.55213584825878159 | — |
| 14 | `states[16].valid` | 15 | — |
| 15 | `states[16].isentropic_factor` | 0.59936841974574895 | — |
| 16 | `states[16].isentropic_exponent` | 0.4000342868314512 | — |
| 17 | `states[16].valid` | 31 | — |
| 18 | `context[0].count` | 18 | — |
| 19 | `states[17].*` | 完整 struct 清零(显式写入) | — |
| 20 | `states[17].medium` | 复制上述完整 medium | — |
| 21 | `states[17].p` | 100004.87065313551 | — |
| 22 | `states[17].T` | 293.15011545458538 | — |
| 23 | `states[17].valid` | 1 | — |
| 24 | `states[17].temperatures` | NULL | 是 |
| 25 | `states[17].jacobian` | 当前 context 的 Jacobian memo 指针 | — |
| 26 | `states[17].rho` | 0.16419832110282509 | — |
| 27 | `states[17].valid` | 5 | — |
| 28 | `states[17].isentropic_factor` | 0.59983509001364188 | — |
| 29 | `states[17].isentropic_exponent` | 0.40001695130827536 | — |
| 30 | `states[17].valid` | 21 | — |
| 31 | `pipes[28].valid` | 0 | 是 |
| 32 | `pipes[28].medium` | 复制上述完整 medium | — |
| 33 | `pipes[28].p1` | 755686.80622119282 | — |
| 34 | `pipes[28].p2` | 100004.87065313551 | — |
| 35 | `pipes[28].T` | 658.33729595925956 | — |
| 36 | `pipes[28].diameter` | 0.02 | — |
| 37 | `pipes[28].length` | 1 | — |
| 38 | `pipes[28].roughness` | 0.0022499999999999998 | — |
| 39 | `pipes[28].kind` | 1 | — |
| 40 | `pipes[28].flow` | 0.13649710784608116 | — |
| 41 | `pipes[28].valid` | 1 | — |
新条目分配分支均为 positive/finite key 且 `count < capacity`;每次在当前 count 追加,再 count++。未初始化槽的 struct 首次清零不计入“已知同值写入”。
### Fallback 原因
查询 key/首匹配逻辑条目不同、已消费字段或已测试 valid 位不同、容量不足转 scratch、observer 非空、pipe 命中分支改变,或无法建立无冲突的 slot 映射时,执行原 operation。此清单不授权放宽现有 guard。
## Jacobian 200,group 6,position 16
t=0.37285444606381923;count 13 → 15;capacity=256;输出 `-3.34452871903457e-06`;model evaluator 返回 `1`。
### 查询与首次匹配
| 顺序 | 类型 | 完整 key:p, T 或 h, R, cp, Tref, slope, mu, muT, S | medium kind | 首个匹配 slot | 扫描条数 |
|---|---|---|---|---|---|
| 1 | PT | `15019678.822746754, 291.84964142606623, 2077.2643940499802, 5193.1609851249505, 293.14999999999998, 0, 1.9599999999999999e-05, 293.14999999999998, 79.400000000000006` | 1 | miss | 13 |
| 2 | PT | `15019662.638804033, 291.84951601402554, 2077.2643940499802, 5193.1609851249505, 293.14999999999998, 0, 1.9599999999999999e-05, 293.14999999999998, 79.400000000000006` | 1 | miss | 14 |
### 实际选中后的字段读取
| 域 / slot | 字段 |
|---|---|
| pipes[0] | `valid` |
| states[13] | `T`, `isentropic_exponent`, `isentropic_factor`, `jacobian`, `medium`, `medium.R`, `medium.S`, `medium.Tref`, `medium.cp`, `medium.mu`, `medium.muT`, `medium.real_helium`, `medium.slope`, `mu`, `p`, `rho`, `temperatures` |
| states[14] | `T`, `isentropic_exponent`, `isentropic_factor`, `jacobian`, `medium.R`, `medium.S`, `medium.Tref`, `medium.cp`, `medium.mu`, `medium.muT`, `medium.real_helium`, `medium.slope`, `p`, `rho`, `temperatures` |
查询扫描另外按短路次序读取 `valid & PT/H`、`p`、`T/h`、medium;未匹配项的字段不等于被用于物性计算。
### 选中后的 valid 测试
| 顺序 | slot | mask | 结果 |
|---|---|---|---|
| 1 | 13 | 8 | 0 |
| 2 | 13 | 16 | 0 |
| 3 | 13 | 4 | 0 |
| 4 | 14 | 16 | 0 |
| 5 | 14 | 4 | 0 |
| 6 | 13 | 4 | 4 |
### 必须保留的 probe 数据
保留入口 `states[0:13]` 的全部字段;此路径没有写已有物性条目。仅追加以下新条目,修改本 operation 的 pipe 槽;其余 pipe 槽保持 probe 值。
### 有序写入动作
| 顺序 | 目标 | 写入值 | 已知同值写入 |
|---|---|---|---|
| 1 | `context[0].count` | 14 | — |
| 2 | `states[13].*` | 完整 struct 清零(显式写入) | — |
| 3 | `states[13].medium` | 复制上述完整 medium | — |
| 4 | `states[13].p` | 15019678.822746754 | — |
| 5 | `states[13].T` | 291.84964142606623 | — |
| 6 | `states[13].valid` | 1 | — |
| 7 | `states[13].temperatures` | NULL | 是 |
| 8 | `states[13].jacobian` | 当前 context 的 Jacobian memo 指针 | — |
| 9 | `states[13].mu` | 1.9556955415450165e-05 | — |
| 10 | `states[13].valid` | 9 | — |
| 11 | `states[13].rho` | 23.797175883728954 | — |
| 12 | `states[13].valid` | 13 | — |
| 13 | `states[13].isentropic_factor` | 0.5638664372147334 | — |
| 14 | `states[13].isentropic_exponent` | 0.39880100414729064 | — |
| 15 | `states[13].valid` | 29 | — |
| 16 | `context[0].count` | 15 | — |
| 17 | `states[14].*` | 完整 struct 清零(显式写入) | — |
| 18 | `states[14].medium` | 复制上述完整 medium | — |
| 19 | `states[14].p` | 15019662.638804033 | — |
| 20 | `states[14].T` | 291.84951601402554 | — |
| 21 | `states[14].valid` | 1 | — |
| 22 | `states[14].temperatures` | NULL | 是 |
| 23 | `states[14].jacobian` | 当前 context 的 Jacobian memo 指针 | — |
| 24 | `states[14].rho` | 23.797161425154815 | — |
| 25 | `states[14].valid` | 5 | — |
| 26 | `states[14].isentropic_factor` | 0.56386646597889145 | — |
| 27 | `states[14].isentropic_exponent` | 0.39880100666377111 | — |
| 28 | `states[14].valid` | 21 | — |
| 29 | `pipes[0].valid` | 0 | 是 |
| 30 | `pipes[0].medium` | 复制上述完整 medium | — |
| 31 | `pipes[0].p1` | 15019662.638804033 | — |
| 32 | `pipes[0].p2` | 15019678.822746754 | — |
| 33 | `pipes[0].T` | 291.84964142606623 | — |
| 34 | `pipes[0].diameter` | 0.014 | — |
| 35 | `pipes[0].length` | 1 | — |
| 36 | `pipes[0].roughness` | 0.0032142857142857142 | — |
| 37 | `pipes[0].kind` | 1 | — |
| 38 | `pipes[0].flow` | -3.34452871903457e-06 | — |
| 39 | `pipes[0].valid` | 1 | — |
新条目分配分支均为 positive/finite key 且 `count < capacity`;每次在当前 count 追加,再 count++。未初始化槽的 struct 首次清零不计入“已知同值写入”。
### Fallback 原因
查询 key/首匹配逻辑条目不同、已消费字段或已测试 valid 位不同、容量不足转 scratch、observer 非空、pipe 命中分支改变,或无法建立无冲突的 slot 映射时,执行原 operation。此清单不授权放宽现有 guard。
## Jacobian 200,group 18,position 52
t=0.37285444606381923;count 75 → 77;capacity=256;输出 `-1.4080633385291488e-06`;model evaluator 返回 `1`。
### 查询与首次匹配
| 顺序 | 类型 | 完整 key:p, T 或 h, R, cp, Tref, slope, mu, muT, S | medium kind | 首个匹配 slot | 扫描条数 |
|---|---|---|---|---|---|
| 1 | PT | `15019625.056587901, 529.09774891777454, 2077.2643940499802, 5193.1609851249505, 293.14999999999998, 0, 1.9599999999999999e-05, 293.14999999999998, 79.400000000000006` | 1 | miss | 75 |
| 2 | PT | `15019613.376778807, 529.09758439456209, 2077.2643940499802, 5193.1609851249505, 293.14999999999998, 0, 1.9599999999999999e-05, 293.14999999999998, 79.400000000000006` | 1 | miss | 76 |
### 实际选中后的字段读取
| 域 / slot | 字段 |
|---|---|
| pipes[28] | `valid` |
| states[75] | `T`, `isentropic_exponent`, `isentropic_factor`, `jacobian`, `medium`, `medium.R`, `medium.S`, `medium.Tref`, `medium.cp`, `medium.mu`, `medium.muT`, `medium.real_helium`, `medium.slope`, `mu`, `p`, `rho`, `temperatures` |
| states[76] | `T`, `isentropic_exponent`, `isentropic_factor`, `jacobian`, `medium.R`, `medium.S`, `medium.Tref`, `medium.cp`, `medium.mu`, `medium.muT`, `medium.real_helium`, `medium.slope`, `p`, `rho`, `temperatures` |
查询扫描另外按短路次序读取 `valid & PT/H`、`p`、`T/h`、medium;未匹配项的字段不等于被用于物性计算。
### 选中后的 valid 测试
| 顺序 | slot | mask | 结果 |
|---|---|---|---|
| 1 | 75 | 8 | 0 |
| 2 | 75 | 16 | 0 |
| 3 | 75 | 4 | 0 |
| 4 | 76 | 16 | 0 |
| 5 | 76 | 4 | 0 |
| 6 | 75 | 4 | 4 |
### 必须保留的 probe 数据
保留入口 `states[0:75]` 的全部字段;此路径没有写已有物性条目。仅追加以下新条目,修改本 operation 的 pipe 槽;其余 pipe 槽保持 probe 值。
### 有序写入动作
| 顺序 | 目标 | 写入值 | 已知同值写入 |
|---|---|---|---|
| 1 | `context[0].count` | 76 | — |
| 2 | `states[75].*` | 完整 struct 清零(显式写入) | — |
| 3 | `states[75].medium` | 复制上述完整 medium | — |
| 4 | `states[75].p` | 15019625.056587901 | — |
| 5 | `states[75].T` | 529.09774891777454 | — |
| 6 | `states[75].valid` | 1 | — |
| 7 | `states[75].temperatures` | NULL | 是 |
| 8 | `states[75].jacobian` | 当前 context 的 Jacobian memo 指针 | — |
| 9 | `states[75].mu` | 2.9451615911502958e-05 | — |
| 10 | `states[75].valid` | 9 | — |
| 11 | `states[75].rho` | 13.352686365896215 | — |
| 12 | `states[75].valid` | 13 | — |
| 13 | `states[75].isentropic_factor` | 0.58140881158784896 | — |
| 14 | `states[75].isentropic_exponent` | 0.39986603165187307 | — |
| 15 | `states[75].valid` | 29 | — |
| 16 | `context[0].count` | 77 | — |
| 17 | `states[76].*` | 完整 struct 清零(显式写入) | — |
| 18 | `states[76].medium` | 复制上述完整 medium | — |
| 19 | `states[76].p` | 15019613.376778807 | — |
| 20 | `states[76].T` | 529.09758439456209 | — |
| 21 | `states[76].valid` | 1 | — |
| 22 | `states[76].temperatures` | NULL | 是 |
| 23 | `states[76].jacobian` | 当前 context 的 Jacobian memo 指针 | — |
| 24 | `states[76].rho` | 13.35268032881546 | — |
| 25 | `states[76].valid` | 5 | — |
| 26 | `states[76].isentropic_factor` | 0.58140882193020493 | — |
| 27 | `states[76].isentropic_exponent` | 0.39986603223234823 | — |
| 28 | `states[76].valid` | 21 | — |
| 29 | `pipes[28].valid` | 0 | 是 |
| 30 | `pipes[28].medium` | 复制上述完整 medium | — |
| 31 | `pipes[28].p1` | 15019613.376778807 | — |
| 32 | `pipes[28].p2` | 15019625.056587901 | — |
| 33 | `pipes[28].T` | 529.09774891777454 | — |
| 34 | `pipes[28].diameter` | 0.02 | — |
| 35 | `pipes[28].length` | 1 | — |
| 36 | `pipes[28].roughness` | 0.0022499999999999998 | — |
| 37 | `pipes[28].kind` | 1 | — |
| 38 | `pipes[28].flow` | -1.4080633385291488e-06 | — |
| 39 | `pipes[28].valid` | 1 | — |
新条目分配分支均为 positive/finite key 且 `count < capacity`;每次在当前 count 追加,再 count++。未初始化槽的 struct 首次清零不计入“已知同值写入”。
### Fallback 原因
查询 key/首匹配逻辑条目不同、已消费字段或已测试 valid 位不同、容量不足转 scratch、observer 非空、pipe 命中分支改变,或无法建立无冲突的 slot 映射时,执行原 operation。此清单不授权放宽现有 guard。
+69
View File
@@ -0,0 +1,69 @@
/* Access events are primary evidence; snapshots are only the replay oracle. */
#include "context_access_diag.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
static FILE *ax_file;
static NativePropertyCache *ax_cache;
static NativePipeCache *ax_pipes;
static int ax_active,ax_pending;
static unsigned long long ax_id;
static void hex(const void *p,size_t n){const unsigned char *b=p;for(size_t i=0;i<n;i++)fprintf(ax_file,"%02x",b[i]);}
static void blob(const char *name,const void *p,size_t n){fprintf(ax_file,"\"%s\":\"",name);hex(p,n);fputc('"',ax_file);}
static void snapshot(const char *phase){
fprintf(ax_file,"{\"event\":\"snapshot\",\"phase\":\"%s\",\"count\":%llu,",phase,(unsigned long long)ax_cache->count);
blob("context",ax_cache,sizeof(*ax_cache));fputc(',',ax_file);
blob("states",ax_cache->states,ax_cache->count*sizeof(*ax_cache->states));fputc(',',ax_file);
blob("pipes",ax_pipes,40*sizeof(*ax_pipes));fputs("}\n",ax_file);
}
static int locate(const void *ptr,size_t n,const char **domain,size_t *offset){
uintptr_t p=(uintptr_t)ptr;
const void *bases[]={ax_cache,ax_cache?ax_cache->states:NULL,ax_pipes};
size_t sizes[]={sizeof(*ax_cache),ax_cache?ax_cache->capacity*sizeof(*ax_cache->states):0,40*sizeof(*ax_pipes)};
const char *names[]={"context","states","pipes"};
for(int i=0;i<3;i++)if(bases[i] && p>=(uintptr_t)bases[i] && p-(uintptr_t)bases[i]+n<=sizes[i]){
*domain=names[i];*offset=p-(uintptr_t)bases[i];return 1;
}
return 0;
}
void ax_access(const char *action,const void *p,size_t n,const char *field,const char *fn){
if(!ax_active){return;}const char *domain;size_t offset;
if(!locate(p,n,&domain,&offset))return;
fprintf(ax_file,"{\"event\":\"%s\",\"domain\":\"%s\",\"offset\":%llu,\"field\":\"%s\",\"function\":\"%s\",",action,domain,(unsigned long long)offset,field,fn);
blob("value",p,n);fputs("}\n",ax_file);
}
void ax_bind(NativePropertyCache *p,NativePipeCache *pipes){ax_cache=p;ax_pipes=pipes;}
void ax_begin(unsigned long long jac,int group,int pos,double t,const double *inputs,int n){
ax_active=(pos==52 && (group==-1 || group==18)) || (pos==16 && (group==-1 || group==6));
if(!ax_active){return;}
if(!ax_file){ax_file=fopen("access.jsonl","wb");if(!ax_file)abort();setvbuf(ax_file,NULL,_IOFBF,1024*1024);}
ax_pending=1;
fprintf(ax_file,"{\"event\":\"begin\",\"id\":%llu,\"jac\":%llu,\"group\":%d,\"position\":%d,\"t\":%.17g,\"stateSize\":%llu,\"pipeSize\":%llu,",++ax_id,jac,group,pos,t,(unsigned long long)sizeof(NativePropertyState),(unsigned long long)sizeof(NativePipeCache));
blob("inputs",inputs,n*sizeof(double));fputs("}\n",ax_file);snapshot("entry");
}
void ax_end(const double *outputs,int n){if(!ax_active){return;}snapshot("exit");fputs("{\"event\":\"end\",\"completed\":true,",ax_file);blob("outputs",outputs,n*sizeof(double));fputs("}\n",ax_file);ax_active=0;}
void ax_result(int result){if(ax_pending){fprintf(ax_file,"{\"event\":\"eval_return\",\"result\":%d}\n",result);ax_pending=0;}}
void ax_finish(void){if(ax_file){fclose(ax_file);ax_file=NULL;}}
void ax_query(const char *kind,const NativeMedium *m,double p,double second){
if(!ax_active){return;}double key[]={p,second,m->R,m->cp,m->Tref,m->slope,m->mu,m->muT,m->S};
fprintf(ax_file,"{\"event\":\"query\",\"kind\":\"%s\",\"mediumKind\":%d,\"count\":%llu,",kind,m->real_helium,(unsigned long long)ax_cache->count);
blob("key",key,sizeof(key));fputs("}\n",ax_file);
}
void ax_match(const char *kind,NativePropertyState *s){if(!ax_active){return;}fprintf(ax_file,"{\"event\":\"match\",\"kind\":\"%s\",\"hit\":%s,\"slot\":%lld}\n",kind,s?"true":"false",s?(long long)(s-ax_cache->states):-1LL);}
void ax_new(NativePropertyCache *cache,NativePropertyState *s,int valid){
if(!ax_active){return;}const char *domain;size_t offset;int stored=locate(s,sizeof(*s),&domain,&offset) && !strcmp(domain,"states");
fprintf(ax_file,"{\"event\":\"allocate\",\"validInput\":%s,\"slot\":%lld,\"countAfter\":%llu,\"capacity\":%llu,\"branch\":\"%s\"}\n",valid?"true":"false",stored?(long long)(offset/sizeof(*s)):-1LL,(unsigned long long)(cache?cache->count:0),(unsigned long long)(cache?cache->capacity:0),stored?"append":"scratch");
}
unsigned ax_test(NativePropertyState *s,unsigned mask,const char *fn){
unsigned value=s->valid&mask;
if(ax_active){
const char *domain;size_t offset;
int stored=locate(s,sizeof(*s),&domain,&offset) && !strcmp(domain,"states");
fprintf(ax_file,"{\"event\":\"valid_test\",\"slot\":%lld,\"mask\":%u,\"value\":%u,\"function\":\"%s\"}\n",stored?(long long)(offset/sizeof(*s)):-1LL,mask,value,fn);
}
return value;
}
void ax_scalar(const char *action,NativeJacobianScalars *cache,int kind,int medium,const double *inputs,size_t n,int hit,const double *value){
if(!ax_active){return;}fprintf(ax_file,"{\"event\":\"scalar_%s\",\"kind\":%d,\"mediumKind\":%d,\"hit\":%d,\"recording\":%d,\"capacity\":%llu,",action,kind,medium,hit,cache?cache->recording:-1,(unsigned long long)(cache?cache->capacity:0));
blob("key",inputs,n*sizeof(double));if(value){fputc(',',ax_file);blob("value",value,sizeof(double));}fputs("}\n",ax_file);
}
+21
View File
@@ -0,0 +1,21 @@
#ifndef CONTEXT_ACCESS_DIAG_H
#define CONTEXT_ACCESS_DIAG_H
#include "kernels.h"
#include <string.h>
/* GCC-only diagnostic worker: each expression is evaluated once. */
void ax_access(const char *,const void *,size_t,const char *,const char *);
void ax_bind(NativePropertyCache *,NativePipeCache *);
void ax_begin(unsigned long long,int,int,double,const double *,int);
void ax_end(const double *,int);
void ax_result(int);
void ax_finish(void);
void ax_query(const char *,const NativeMedium *,double,double);
void ax_match(const char *,NativePropertyState *);
void ax_new(NativePropertyCache *,NativePropertyState *,int);
unsigned ax_test(NativePropertyState *,unsigned,const char *);
void ax_scalar(const char *,NativeJacobianScalars *,int,int,const double *,size_t,int,const double *);
#define AX_R(x) ({ __typeof__(x) ax_v=(x); ax_access("read",&(x),sizeof(x),#x,__func__); ax_v; })
#define AX_W(x,v) do { (x)=(v); ax_access("write",&(x),sizeof(x),#x,__func__); } while(0)
#define AX_OR(x,v) do { (x)|=(v); ax_access("write",&(x),sizeof(x),#x,__func__); } while(0)
#define AX_INC(x) ({ __typeof__(x) ax_v=(x)++; ax_access("write",&(x),sizeof(x),#x,__func__); ax_v; })
#endif
+169
View File
@@ -0,0 +1,169 @@
# 两个 operation 的 context 访问级诊断
## 结论
两个案例均能按“查询 key 和首次匹配关系 → 消费字段 → 有序更新与追加”建立局部清单。完整仿真中,每例各验证 896 对 baseline/probe:查询 key、命中状态、逻辑条目对应后的消费值、valid 测试及有序写入一致,operation 输出逐位一致。
- **group 18 / R475 / position 52**:Jacobian 200 中 `states[74].p` 确实被两次 PT 查询扫描读取,但两次都在压力比较处排除。它没有成为物性计算输入;`states[74].T/h/rho/isentropic` 均未被该 operation 读取。因此,**本次入口差异不影响查询选择和计算**,不能表述为“这个字段完全没被读取”,也不能无条件忽略任意压力变化。
- **group 6 / R288 / position 16**:Jacobian 200 的入口 count 从 baseline 12 变为 probe 13。两次 PT 查询均 miss,逻辑新条目应从 baseline 12/13 映射到 probe 13/14;退出 count 应为 15。probe 原有 slot 12 必须保留。
这是独立诊断 worker 的验证结果。生产求解器、现有 local-probe guard 和整段回放策略没有修改;不能据此直接允许整个 R475 复用。
具体数值、每次查询的完整 key、valid 测试和逐项有序写入见 [实测清单](context_access_checklists.md)。以下解释这些清单如何用于后续设计。
## 验证范围与方法
输入 `tests/data/test-mql-8-corrected.json`,BDF,0–10 s,采样间隔 0.01 s,rtol=1e-8。位置、group、Jacobian 序号均采用已有诊断的 0 起始编号。Windows / GCC 8.1.0;本次没有执行 Linux 验收。
独立 worker 来源为 `test/context-fallback-20260917/worker`。生成时,仅在副本内包装实际字段读取、valid 位测试、赋值、结构体清零、count++、查询和分配点,保留原表达式、短路条件和原 guard。Jacobian scalar get 经输出指针写入 `rho` 的路径也单独记录。
原始事件带有执行顺序、函数、context 域、字节偏移及原始位值。PT/PH 查询记录 medium kind、全部七个 medium 浮点参数、p 和 T/h;校验器独立扫描当前重放状态,检查所有匹配项、首个匹配 slot,以及实际扫描到的 valid 测试序列。miss 后的 `allocate` 事件给出所选追加 slot。
观察器和 memo 指针还校验了当前 context 的归属,以及同一 Jacobian 内 baseline/probe 的绑定一致性,不只是比较“是否非空”。日志中的原始地址仅用于本次进程诊断,不作为跨进程、跨机器的逻辑 key。
**前后快照仅作校验终点**:逐条读取须与当时 context 一致;逐条写入须重建退出 context 的每个有效字节,包括未改动旧条目和其余 pipe 槽。另独立检查原代码规定的新条目初始化顺序及 pipe 的 11 次写入,因而删除 `pipe.valid=0` 这类同值写入也会验收失败。未初始化槽清零前的内容未知,不把它计作“已知同值写入”。
`operations.json` 中的 `consumed` 是选择完成后的实际字段读取集合;查询扫描的 `p/T/h/medium` 和 valid 测试在原始事件中单独保留。新条目内部的计算读取也在集合内,但这些值不是需要和入口旧条目比较的前置条件。
## 案例一:group 18,R475 内 position 52
operation 为 `flow:amesim_pnl0001_17.port_1`,输出 `q[152]`,使用 `pipe_cache[28]`。
### 查询及必要前置条件
Jacobian 200,t≈0.372854446 s,入口 count=75,capacity=256。
| 项目 | baseline | probe |
|---|---:|---:|
| states[74].p | 15019640.74937454 | 15019641.148960622 |
| states[74].T | 460.10768147887495 | 460.1076863719882 |
| states[74].valid | 21 | 21 |
| 上游 PT key 的 p | 15019625.056587901 | 相同 |
| 上游 PT key 的 T | 529.0977489177745 | 相同 |
| 下游 PT key 的 p | 15019613.376778807 | 相同 |
| 下游 PT key 的 T | 529.0975843945621 | 相同 |
完整 medium key 为 `(real_helium=1, R=2077.26439404998, cp=5193.1609851249505, Tref=293.15, slope=0, mu=1.96e-5, muT=293.15, S=79.4)`。实际二进制位值保存在事件和清单来源 JSON 中,不依赖上表小数显示精度。
两次 PT 都 miss,分别扫描 75、76 项,追加到 slot 75、76。slot 74 的两次访问均为 `valid & 1 → p == query.p`;压力不等后短路,未读取其 T 或 medium。满足同样排除关系即可允许这项差异,不要求两次入口的 slot 74 压力相等。
此时 `p[44] <= g[40].p`:温度来自 `g[40].T`,没有 PH 查询,`h[178]` 没有被本次分支消费。需要保持压力方向、近零流量分支、实际温度来源、medium 和管道常量一致。
### 实际读取 → 必须保留的 probe 数据
- 上游新条目:p、T、medium、mu、rho、isentropic_factor、isentropic_exponent,以及温度观察器/Jacobian memo 指针。
- 下游新条目:p、T、medium、rho、两个 isentropic 字段和上述指针;没有消费下游 mu/h。
- 选中后的 valid 测试依次为:上游 MU=0、ISENTROPIC=0、RHO=0;下游 ISENTROPIC=0、RHO=0;再次上游 RHO=4。
- `pipe_cache[28]` 入口只读 valid=0,其余旧键字段被短路跳过。
- 保留 probe 的 `states[0:75]` 全部内容,尤其是 slot 74 的新 p/T;其他 39 个 pipe 槽保持 probe 值。这条实测路径没有写任何已有物性条目。
### 有序字段更新与追加动作
1. count 75→76;在 slot 75 整体清零,依次写 medium、p、T、valid=PT、temperatures、jacobian。
2. 写 mu,`valid |= MU`;写 rho,`valid |= RHO`;写 factor、exponent,`valid |= ISENTROPIC`。上游最终 valid=29。
3. count 76→77;同样初始化 slot 76;写 rho、RHO 位、factor、exponent、ISENTROPIC 位。下游最终 valid=21,未写 h/mu 的计算值,但初始化清零必须执行。
4. pipe[28] 按顺序写 valid=0、medium、p1、p2、T、diameter、length、roughness、kind、flow、valid=1。首次 valid=0 是真实的 0→0 写入。
5. 输出 `q[152]=-1.4080633385291488e-6`;model evaluator 返回 1。
### 不能省略的其他实测分支
| position 52 查询序列 | probe 次数 | 代表 Jacobian |
|---|---:|---:|
| PT miss → PT miss | 727 | 200 |
| PH miss → PT miss(登记 h)→ PT hit → PT miss | 167 | 2 |
| PH hit → PT hit → PT miss | 1 | 1 |
| 无物性查询,近零流量 | 1 | 0 |
PH miss 路径需要测试 H 位并显式写 h、`valid |= H`,随后 pipe 的 PT 查询复用这个刚创建的逻辑条目;不能把后一次 hit 另当一次追加。PH hit 路径实际消费已有条目的 T,随后读取其已有效的 mu/rho/isentropic 字段;这些入口载荷必须比较。Jacobian 1 中首次匹配为 slot 7,仅新增下游条目。两条补充分支的完整 key 和写入顺序已收入实测清单。
全程有 24 对入口 count 不同,偏移为 +1 或 +2;其查询和逻辑字段仍一致。位置 52 也需要动态 slot 映射,不能固定为 75/76。
## 案例二:group 6,R288 / position 16
operation 为 `flow:amesim_pnl0001_1.port_1`,输出 `q[45]`,使用 `pipe_cache[0]`。
### 查询及必要前置条件
Jacobian 200 的 medium 与上例相同:
| 逻辑条目 / 查询顺序 | p | T | baseline slot | probe slot |
|---|---:|---:|---:|---:|
| 上游 U | 15019678.822746754 | 291.8496414260662 | miss → 12 | miss → 13 |
| 下游 D | 15019662.638804033 | 291.84951601402554 | miss → 13 | miss → 14 |
baseline 扫描 12、13 项;probe 扫描 13、14 项。probe 增加的入口项都不匹配当前查询,且两次追加时均有容量。**相同逻辑条目由查询 key 和创建次序定义,不能用“相同数组下标”定义。**
此时 `p[3] <= g[4].p`,使用 `g[4].T`,未消费 `h[64]`。需要比较实际压力/温度输入、medium/管道常量,以及查询和分支前置条件。
### 实际读取 → 必须保留的 probe 数据
U、D 的消费字段及 valid 测试与上例的两次 PT miss 路径一致,只需映射到 probe slot 13/14。pipe[0] 只消费入口 valid=0。
**保留 probe `states[0:13]` 的每个字段,包括 slot 12,以及其他全部 pipe 槽。** 本 operation 没有对已有物性条目的写入。入口 count 不需要等于 baseline,但它决定下一次追加位置,不能忽略其作用。
### 有序字段更新与追加动作
1. 在 probe 当前尾部追加 U:count 13→14,slot 13 完整初始化,写 mu/rho/factor/exponent 和对应 valid 位,最终 valid=29。
2. 重新按更新后的 probe context 查询 D;miss 后 count 14→15,slot 14 初始化,写 rho/factor/exponent 和对应 valid 位,最终 valid=21。
3. pipe[0] 执行同样的 11 次有序写入,包括同值 valid=0。
4. `q[45]=-3.34452871903457e-6`,model evaluator 返回 1。
把 baseline slot 12/13 的退出快照直接覆盖到 probe 会损坏原 slot 12;把 count 恢复为 baseline 14 会丢失 probe 的有效尾项。正确动作是“保留 probe 前缀,在 probe 当前 count 顺序追加”。
896 对中,638 对存在 count 差异,最大偏移 +4;829 对走两次 PT miss,67 对走近零流量路径且不增加 count。全部能建立一致的逻辑映射。
## 比较、保留、写回与 fallback 的边界
| 入口差异 / 动作 | 本次清单要求 |
|---|---|
| 未选中的条目 p/T/h/medium 不同 | 可允许数值不同,但必须验证仍被当前查询排除,不改变首次匹配关系 |
| count / slot 平移 | 可允许;用当前 probe count 检查容量,按 key/首次命中和创建顺序建立映射 |
| 选中已有条目的有效载荷不同 | 必须比较本分支实际消费的 T、mu、rho、factor、exponent 等;不一致则 fallback |
| valid 位不同 | 比较实际测试的掩码结果;未知分支不可放行。`valid |= flag` 应保留其余 probe 位,不能泛化为整字覆盖 |
| 入口未被读取的 h 或 pipe 旧键值不同 | 本分支可以忽略比较;仍须保留未被实际写入的 probe 数据 |
| 新条目 | 当前尾部追加;完整初始化再按事件次序赋值,同值写入也不能删除 |
| 已有条目更新 | 两例的完整运行未观察到。插桩已覆盖相关赋值,但不能声称已实测该分支;若后续出现,需要保留其原有有效字段、按映射定点更新 |
| 容量不足、无效 key、scratch 路径 | 本次未出现。清单要求 fallback,不能套用“必定追加两项” |
| 非空 temperature observer | 本次均为 NULL;非空会有外部观察器副作用,未验证,fallback |
| pipe 入口命中分支改变 | 本次目标槽均 valid=0;若变为有效需重新核验完整 pipe key、flow 载荷和分支,不能沿用本清单 |
| 首匹配不同 / 重复 key 改变匹配先后 | fallback;单纯“某处有同 key”不够 |
Jacobian scalar memo 是另一个需要保留的作用域:其 key、get 命中、put 尝试、recording/capacity 已记录。Jacobian 200 baseline 的两次 density 和一次 pipe scalar get 为 miss,probe 均 hit;返回数值一致。此处不能简单回放 baseline 的 memo 写入或统计副作用。当前诊断不修改该 memo;后续局部复用必须维持其生命周期和统计语义。
operation 本身是一次 double 赋值,没有独立 int 状态码。记录的是实际输出/pipe flow,以及包围它的 model evaluator 返回值;没有虚构 operation 成功码。
## 验收结果
- 3,584 次 operation(2 案例 × baseline/probe × 896),全部访问校验和退出 context 重放一致。
- 1,382,612 次字段/基址读取逐条核对通过;valid 掩码测试另外记录并校验。
- 每例 896 对的完整查询 key、hit/miss、首次匹配关系经过独立验证;按逻辑映射后的消费字段集合、valid 测试及有序写入一致。
- 136,606 次显式写入,其中 **10,614 次已知同值写入**;检查包括 pipe valid 的 0→0,未把未初始化槽的清零算作已知同值写入。
- 状态、输出、事件及所有 896 个 132×132 Jacobian 的 SHA-256 与已有未插桩基线一致;矩阵元素共 15,611,904 个。
- accepted=10,840,rejected=918,nfev=44,467,njev=896,nlu=3,106;Newton iterations=19,371,Newton convergence failures=798,均与基线一致。
- 2,688 次包含目标 operation 的 model evaluator 调用均返回 1;baseline 每次含两个目标 operation,所以该数小于 operation 数量。
- 证据校验器另有 6 个负例:遗漏同值写入、rho 读取错误、追加使用 baseline slot、恢复 baseline count、错误首匹配、错误 valid 位结果。它们是对证据校验器的反例测试,**不是已实现的生产 fallback 测试**。
插桩会引入大量日志开销,本次时长不用于评价优化收益。容量耗尽、scratch、非空 observer、已有物性条目更新及 pipe 命中分支未在这两个案例内实测,仍是未来实现时的明确边界。
## 复现与交付文件
需先保留已有 local-probe、context-fallback worker 及其 baseline 验证文件。它们的生成入口分别是 `local_probe_experiment.py`、`diagnose_context_fallback.py`;本次脚本会对缺失来源报错,不生成替代基线。
```powershell
.venv-win\Scripts\python.exe tests/manual/diagnose_context_access.py prepare
.venv-win\Scripts\python.exe tests/manual/diagnose_context_access.py run
.venv-win\Scripts\python.exe tests/manual/analyze_context_access.py
.venv-win\Scripts\python.exe tests/manual/verify_context_access_evidence.py
```
所有路径由脚本所在项目目录推导。原始证据和生成 worker 位于 `test/context-access-20260917/`,属于被 Git 忽略的运行产物:
- `worker/build.json`:来源及插桩源码 SHA-256。
- `audit/access.jsonl`:逐次实际查询、读取、测试、写入、分配、输出和返回事件。
- `audit/measurement.json`:全量仿真数值一致性哈希。
- `summary.json`:逐 Jacobian 的两路径对应关系。
- `operations.json`:全部 3,584 次 operation 的查询、消费字段、valid 测试、有序更新与追加。
- `jacobian-200.json`:两个主案例的原始事件和结构化清单。
- `negative-checks.json`:六项证据负例结果。
本报告及 [实测数值清单](context_access_checklists.md) 保存在 `tests/manual/`,可与诊断脚本一起提交。
+213
View File
@@ -0,0 +1,213 @@
/* Included after the unchanged local_probe_support.c, so baseline snapshots
are observed directly. None of the original guard or restore code is edited. */
#include "context_fallback_diag.h"
#include <stddef.h>
/* DIAG_TABLES */
int dx_mode,dx_selected,dx_region=-1,dx_position=-1,dx_shadow;
static uint64_t dx_jac,dx_sampled,dx_tick0,dx_q0,dx_region_tick,dx_op_tick;
static unsigned dx_stride=16,dx_seed=1,dx_pick;
static double dx_time;
typedef struct {
uint64_t attempts,compares,failures,region_ticks,op_ticks,ops;
uint64_t input_diff_ops,output_diff_ops,exit_output_diff,exit_context_diff;
} DxRegion;
static DxRegion dx_stats[LP_NC][LP_NR];
static uint64_t dx_op_count[LP_NC][LP_NO],dx_op_ticks[LP_NC][LP_NO];
static uint64_t dx_kernel_count[LP_NR][DX_NK],dx_kernel_inclusive[LP_NR][DX_NK],dx_kernel_exclusive[LP_NR][DX_NK];
static double dx_inputs[DX_NIN],dx_outputs[DX_NOUT];
static FILE *dx_fail_file,*dx_matrix_file;
static FILE *dx_input_file;
static FILE *dx_create_file;
static int dx_trace,dx_details,dx_first_context=-999,dx_seen[LP_NB],dx_source[LP_NB];
static Snapshot dx_history[LP_NB];
static struct {uint64_t start,child;int kind,region;} dx_stack[64];
static int dx_depth;
typedef struct {const char *name;size_t offset,size;int floating;} DxField;
#define DX_FIELD(type,field,float_flag) {#field,offsetof(type,field),sizeof(((type*)0)->field),float_flag}
#define DX_MEDIUM(type,field,float_flag) {"medium." #field,offsetof(type,medium)+offsetof(NativeMedium,field),sizeof(((NativeMedium*)0)->field),float_flag}
#define DX_MEDIUM_FIELDS(type) DX_MEDIUM(type,real_helium,0),DX_MEDIUM(type,R,1),DX_MEDIUM(type,cp,1),DX_MEDIUM(type,Tref,1),DX_MEDIUM(type,slope,1),DX_MEDIUM(type,mu,1),DX_MEDIUM(type,muT,1),DX_MEDIUM(type,S,1)
static const DxField dx_state_fields[]={DX_MEDIUM_FIELDS(NativePropertyState),
DX_FIELD(NativePropertyState,p,1),DX_FIELD(NativePropertyState,T,1),DX_FIELD(NativePropertyState,h,1),
DX_FIELD(NativePropertyState,rho,1),DX_FIELD(NativePropertyState,mu,1),DX_FIELD(NativePropertyState,isentropic_factor,1),
DX_FIELD(NativePropertyState,isentropic_exponent,1),DX_FIELD(NativePropertyState,valid,0),
DX_FIELD(NativePropertyState,temperatures,0),DX_FIELD(NativePropertyState,jacobian,0)};
static const DxField dx_pipe_fields[]={DX_MEDIUM_FIELDS(NativePipeCache),
DX_FIELD(NativePipeCache,p1,1),DX_FIELD(NativePipeCache,p2,1),DX_FIELD(NativePipeCache,T,1),
DX_FIELD(NativePipeCache,diameter,1),DX_FIELD(NativePipeCache,length,1),DX_FIELD(NativePipeCache,roughness,1),
DX_FIELD(NativePipeCache,flow,1),DX_FIELD(NativePipeCache,kind,0),DX_FIELD(NativePipeCache,valid,0)};
typedef struct {int kind,index,field;size_t offset,size;uint64_t baseline,trial;} DxDifference;
static DxDifference dx_difference(NativePropertyCache*,NativePipeCache*,Snapshot*);
static void dx_boundary(int pos,int source,NativePropertyCache *p,NativePipeCache *pipes){
if(!dx_trace || lp_color<0)return;
int v=dx_version[pos];if(dx_seen[v])return;
Snapshot *s=&dx_history[v];memset(s,0,sizeof(*s));
s->count=p->count;s->capacity=p->capacity;s->temperatures=p->temperatures;s->jacobian=p->jacobian;
memcpy(s->states,p->states,p->count*sizeof(*p->states));memcpy(s->pipes,pipes,sizeof(s->pipes));
dx_seen[v]=1;dx_source[v]=source;
if(dx_first_context==-999 && dx_difference(p,pipes,&saved->snapshots[v]).kind)dx_first_context=source;
}
static int dx_field_equal(Snapshot *a,Snapshot *b,DxDifference d){
if(d.kind==1){uint64_t x[]={a->count,a->capacity,(uintptr_t)a->temperatures,(uintptr_t)a->jacobian};uint64_t y[]={b->count,b->capacity,(uintptr_t)b->temperatures,(uintptr_t)b->jacobian};return x[d.field]==y[d.field];}
if(d.kind==2 && ((size_t)d.index>=a->count || (size_t)d.index>=b->count))return (size_t)d.index>=a->count && (size_t)d.index>=b->count;
unsigned char *x=(unsigned char*)(d.kind==2?(void*)&a->states[d.index]:(void*)&a->pipes[d.index]);
unsigned char *y=(unsigned char*)(d.kind==2?(void*)&b->states[d.index]:(void*)&b->pipes[d.index]);
return memcmp(x+d.offset,y+d.offset,d.size)==0;
}
static int dx_origin(DxDifference d,int v,int *first){
int previous_equal=1,origin=-999;*first=-999;
for(int i=0;i<=v;i++)if(dx_seen[i]){
int equal=dx_field_equal(&dx_history[i],&saved->snapshots[i],d);
if(!equal && previous_equal){origin=dx_source[i];if(*first==-999)*first=origin;}
previous_equal=equal;
}
return origin;
}
static DxDifference dx_difference(NativePropertyCache *p,NativePipeCache *pipes,Snapshot *b){
DxDifference d={0,-1,-1,0,0,0,0};
uint64_t bm[]={b->count,b->capacity,(uintptr_t)b->temperatures,(uintptr_t)b->jacobian};
uint64_t pm[]={p->count,p->capacity,(uintptr_t)p->temperatures,(uintptr_t)p->jacobian};
for(int i=0;i<4;i++)if(bm[i]!=pm[i]){d.kind=1;d.field=i;d.baseline=bm[i];d.trial=pm[i];return d;}
for(int kind=2;kind<=3;kind++){
size_t size=kind==2?sizeof(NativePropertyState):sizeof(NativePipeCache),count=kind==2?b->count:LP_NPC;
const unsigned char *a=(const unsigned char*)(kind==2?(void*)b->states:(void*)b->pipes),*c=(const unsigned char*)(kind==2?(void*)p->states:(void*)pipes);
const DxField *fields=kind==2?dx_state_fields:dx_pipe_fields;
int nf=kind==2?(int)(sizeof(dx_state_fields)/sizeof(*dx_state_fields)):(int)(sizeof(dx_pipe_fields)/sizeof(*dx_pipe_fields));
for(size_t i=0;i<count*size;i++)if(a[i]!=c[i]){
d.kind=kind;d.index=(int)(i/size);size_t off=i%size;d.offset=off;d.size=1;
for(int f=0;f<nf;f++)if(off>=fields[f].offset && off<fields[f].offset+fields[f].size){d.field=f;d.offset=fields[f].offset;d.size=fields[f].size;break;}
memcpy(&d.baseline,a+d.index*size+d.offset,d.size);memcpy(&d.trial,c+d.index*size+d.offset,d.size);return d;
}
}
return d;
}
static void dx_failure(int r,NativePropertyCache *p,NativePipeCache *pipes){
if(dx_mode==5 && !dx_trace)return;
DxDifference d=dx_difference(p,pipes,&saved->snapshots[lp_before[r]]);
if(!d.kind){fprintf(stderr,"failure without difference\n");abort();}
int first=-999,origin=dx_trace?dx_origin(d,dx_version[dx_start_pos[r]],&first):-999;
fprintf(dx_fail_file,"{\"jac\":%llu,\"t\":%.17g,\"group\":%d,\"region\":%d,\"kind\":%d,\"index\":%d,\"field\":%d,\"offset\":%llu,\"baseline\":\"%016llx\",\"trial\":\"%016llx\",\"firstOrigin\":%d,\"persistentOrigin\":%d,\"firstContextOrigin\":%d}\n",
(unsigned long long)(dx_jac-1),dx_time,lp_color,r,d.kind,d.index,d.field,(unsigned long long)d.offset,(unsigned long long)d.baseline,(unsigned long long)d.trial,first,origin,dx_first_context);
}
void dx_initialize(void){
const char *m=getenv("CONTEXT_DIAG_MODE"),*s=getenv("CONTEXT_DIAG_STRIDE"),*seed=getenv("CONTEXT_DIAG_SEED"),*mat=getenv("CONTEXT_DIAG_MATRICES");
dx_mode=m?atoi(m):1;dx_stride=s?(unsigned)atoi(s):16;dx_seed=seed?(unsigned)atoi(seed):1;
dx_q0=lp_tick();dx_tick0=dx_clock();
if(dx_mode==1 || dx_mode==5){dx_fail_file=fopen("failures.jsonl","wb");if(!dx_fail_file)abort();setvbuf(dx_fail_file,NULL,_IOFBF,1024*1024);}
if(dx_mode==5){dx_input_file=fopen("trace-inputs.jsonl","wb");if(!dx_input_file)abort();}
if(dx_mode==5){dx_create_file=fopen("trace-created.jsonl","wb");if(!dx_create_file)abort();}
if(mat && atoi(mat)){dx_matrix_file=fopen("jacobians.bin","wb");if(!dx_matrix_file)abort();setvbuf(dx_matrix_file,NULL,_IOFBF,1024*1024);}
}
void dx_jacobian(void){
unsigned pos=(unsigned)(dx_jac++%(dx_stride?dx_stride:1));
if(!pos){dx_seed^=dx_seed<<13;dx_seed^=dx_seed>>17;dx_seed^=dx_seed<<5;dx_pick=dx_seed%(dx_stride?dx_stride:1);}
dx_selected=dx_mode==1 || dx_mode==5 || (dx_stride && pos==dx_pick);
dx_trace=dx_mode==5;
dx_details=dx_mode==5 && (dx_jac==1 || dx_jac==201 || dx_jac==451 || dx_jac==701 || dx_jac==896);
if(dx_selected)dx_sampled++;
}
void dx_eval_begin(double t,const double *y){(void)y;dx_time=t;dx_region=-1;dx_position=-1;dx_first_context=-999;memset(dx_seen,0,sizeof(dx_seen));}
void dx_eval_end(void){if(dx_region>=0){fprintf(stderr,"unclosed fallback region\n");abort();}dx_position=-1;}
void dx_schedule(NativePropertyCache *p,NativePipeCache *pipes){dx_region=-1;dx_boundary(0,-2,p,pipes);}
int dx_reuse(int r,NativePropertyCache *p,NativePipeCache *pipes,double *pv,double *h,double *q,double *w,double *fb){
int ok=lp_reuse(r,p,pipes,pv,h,q,w,fb);
DxRegion *s=&dx_stats[lp_color][r];s->attempts++;if(dx_contextual[r])s->compares++;
if(!ok){
if(!dx_contextual[r])abort();
s->failures++;dx_region=r;
if(dx_fail_file)dx_failure(r,p,pipes);
if(dx_mode>=2 && dx_mode<=4)dx_region_tick=dx_clock();
}
else dx_boundary(lp_end[r],-1000-r,p,pipes);
return ok;
}
void dx_op_begin(int pos,const double *inputs){
dx_position=pos;
if(lp_color<0){if(dx_mode==1 || dx_mode==5)memcpy(dx_inputs+dx_in_offset[pos],inputs,(dx_in_offset[pos+1]-dx_in_offset[pos])*sizeof(double));return;}
if(dx_details){
int any=0;
for(int i=0;i<dx_in_offset[pos+1]-dx_in_offset[pos];i++)if(memcmp(&inputs[i],&dx_inputs[dx_in_offset[pos]+i],8)){
uint64_t a,b;memcpy(&a,&dx_inputs[dx_in_offset[pos]+i],8);memcpy(&b,&inputs[i],8);
if(!any)fprintf(dx_input_file,"{\"jac\":%llu,\"group\":%d,\"position\":%d,\"inputs\":[",(unsigned long long)(dx_jac-1),lp_color,pos);
fprintf(dx_input_file,"%s[%d,\"%016llx\",\"%016llx\"]",any++?",":"",i,(unsigned long long)a,(unsigned long long)b);
}
if(any)fprintf(dx_input_file,"]}\n");
}
if(dx_region<0)return;
dx_stats[lp_color][dx_region].ops++;dx_op_count[lp_color][pos]++;
if(dx_mode==1 || dx_mode==5){
if(memcmp(dx_inputs+dx_in_offset[pos],inputs,(dx_in_offset[pos+1]-dx_in_offset[pos])*sizeof(double)))dx_stats[lp_color][dx_region].input_diff_ops++;
}
if(dx_mode==3 || dx_mode==4)dx_op_tick=dx_clock();
}
void dx_op_end(int pos,const double *outputs,NativePropertyCache *p,NativePipeCache *pipes){
if(dx_mutates[pos])dx_boundary(pos+1,pos,p,pipes);
if(lp_color<0){if(dx_mode==1 || dx_mode==5)memcpy(dx_outputs+dx_out_offset[pos],outputs,(dx_out_offset[pos+1]-dx_out_offset[pos])*sizeof(double));return;}
if(dx_region<0)return;
if(dx_mode==3 || dx_mode==4){uint64_t elapsed=dx_clock()-dx_op_tick;dx_op_ticks[lp_color][pos]+=elapsed;dx_stats[lp_color][dx_region].op_ticks+=elapsed;}
if((dx_mode==1 || dx_mode==5) && memcmp(dx_outputs+dx_out_offset[pos],outputs,(dx_out_offset[pos+1]-dx_out_offset[pos])*sizeof(double)))dx_stats[lp_color][dx_region].output_diff_ops++;
}
void dx_region_end(double *p,double *h,double *q,double *w,double *fb,NativePropertyCache *properties,NativePipeCache *pipes){
if(dx_region<0)return;
int r=dx_region;
if(dx_mode>=2 && dx_mode<=4)dx_stats[lp_color][r].region_ticks+=dx_clock()-dx_region_tick;
if(dx_mode==1 || dx_mode==5){
double *dst[]={p,h,q,w,fb};double *src[]={saved->p,saved->h,saved->q,saved->w,saved->fb};int changed=0;
for(int i=lp_first_output[r];i<lp_last_output[r];i++)changed|=memcmp(&dst[lp_output_map[i][0]][lp_output_map[i][1]],&src[lp_output_map[i][0]][lp_output_map[i][1]],sizeof(double))!=0;
dx_stats[lp_color][r].exit_output_diff+=changed;
dx_stats[lp_color][r].exit_context_diff+=dx_difference(properties,pipes,&saved->snapshots[lp_after[r]]).kind!=0;
}
dx_region=-1;
}
uint64_t dx_kernel_begin(int kind){
if(dx_mode==5 && dx_details && !dx_shadow && dx_position>=0){
if(dx_region>=0 && lp_color>=0)dx_kernel_count[dx_region][kind]++;
if(dx_depth>=64)abort();
dx_stack[dx_depth].kind=kind;dx_depth++;return 1;
}
if(!dx_selected || dx_shadow || dx_region<0 || lp_color<0)return 0;
dx_kernel_count[dx_region][kind]++;
if(dx_mode!=4)return 0;
if(dx_depth>=64)abort();
uint64_t t=dx_clock();dx_stack[dx_depth].start=t;dx_stack[dx_depth].child=0;dx_stack[dx_depth].kind=kind;dx_stack[dx_depth].region=dx_region;dx_depth++;return t;
}
void dx_kernel_end(int kind,uint64_t start){
if(!start)return;
if(start==1 && dx_mode==5){if(--dx_depth<0 || dx_stack[dx_depth].kind!=kind)abort();return;}
uint64_t elapsed=dx_clock()-start;int i=--dx_depth;
if(i<0 || dx_stack[i].kind!=kind)abort();
int r=dx_stack[i].region;dx_kernel_inclusive[r][kind]+=elapsed;dx_kernel_exclusive[r][kind]+=elapsed-dx_stack[i].child;
if(i)dx_stack[i-1].child+=elapsed;
}
void dx_property_created(NativePropertyCache *p,NativePropertyState *s){
if(!dx_details || dx_position<0 || !p)return;
uintptr_t a=(uintptr_t)s,b=(uintptr_t)p->states;
int slot=a>=b && a<b+p->capacity*sizeof(*s)?(int)((a-b)/sizeof(*s)):-1;
fprintf(dx_create_file,"{\"jac\":%llu,\"group\":%d,\"position\":%d,\"slot\":%d,\"p\":%.17g,\"T\":%.17g,\"count\":%llu,\"path\":[",(unsigned long long)(dx_jac-1),lp_color,dx_position,slot,s->p,s->T,(unsigned long long)p->count);
for(int i=0;i<dx_depth;i++)fprintf(dx_create_file,"%s%d",i?",":"",dx_stack[i].kind);
fprintf(dx_create_file,"]}\n");
}
void dx_validate_matrix(double t,const double *y,const double *m){if(dx_matrix_file){fwrite(&t,8,1,dx_matrix_file);fwrite(y,8,NSTATES,dx_matrix_file);fwrite(m,8,NSTATES*NSTATES,dx_matrix_file);}}
void dx_finish(void){
double hz=(double)(dx_clock()-dx_tick0)/((double)(lp_tick()-dx_q0)/frequency);
if(dx_fail_file)fclose(dx_fail_file);
if(dx_matrix_file)fclose(dx_matrix_file);
if(dx_input_file)fclose(dx_input_file);
if(dx_create_file)fclose(dx_create_file);
FILE *f=fopen("context.json","wb");if(!f)abort();
fprintf(f,"{\"mode\":%d,\"frequency\":%.9f,\"jacobians\":%llu,\"sampled\":%llu,\"regions\":[",dx_mode,hz,(unsigned long long)dx_jac,(unsigned long long)dx_sampled);
int comma=0;
for(int g=0;g<LP_NC;g++)for(int r=0;r<LP_NR;r++){
DxRegion *s=&dx_stats[g][r];if(!s->attempts)continue;
fprintf(f,"%s{\"group\":%d,\"region\":%d,\"attempts\":%llu,\"compares\":%llu,\"failures\":%llu,\"regionTicks\":%llu,\"opTicks\":%llu,\"ops\":%llu,\"inputDiffOps\":%llu,\"outputDiffOps\":%llu,\"exitOutputDiff\":%llu,\"exitContextDiff\":%llu}",comma++?",":"",g,r,(unsigned long long)s->attempts,(unsigned long long)s->compares,(unsigned long long)s->failures,(unsigned long long)s->region_ticks,(unsigned long long)s->op_ticks,(unsigned long long)s->ops,(unsigned long long)s->input_diff_ops,(unsigned long long)s->output_diff_ops,(unsigned long long)s->exit_output_diff,(unsigned long long)s->exit_context_diff);
}
fprintf(f,"],\"operations\":[");comma=0;
for(int g=0;g<LP_NC;g++)for(int p=0;p<LP_NO;p++)if(dx_op_count[g][p])fprintf(f,"%s[%d,%d,%llu,%llu]",comma++?",":"",g,p,(unsigned long long)dx_op_count[g][p],(unsigned long long)dx_op_ticks[g][p]);
fprintf(f,"],\"kernels\":[");comma=0;
for(int r=0;r<LP_NR;r++)for(int k=0;k<DX_NK;k++)if(dx_kernel_count[r][k])fprintf(f,"%s[%d,%d,%llu,%llu,%llu]",comma++?",":"",r,k,(unsigned long long)dx_kernel_count[r][k],(unsigned long long)dx_kernel_inclusive[r][k],(unsigned long long)dx_kernel_exclusive[r][k]);
fprintf(f,"],\"fields\":{");
for(int kind=2;kind<=3;kind++){
const DxField *fields=kind==2?dx_state_fields:dx_pipe_fields;int n=kind==2?(int)(sizeof(dx_state_fields)/sizeof(*dx_state_fields)):(int)(sizeof(dx_pipe_fields)/sizeof(*dx_pipe_fields));
fprintf(f,"%s\"%d\":[",kind==2?"":",",kind);for(int i=0;i<n;i++)fprintf(f,"%s{\"name\":\"%s\",\"offset\":%llu,\"size\":%llu,\"floating\":%d}",i?",":"",fields[i].name,(unsigned long long)fields[i].offset,(unsigned long long)fields[i].size,fields[i].floating);fprintf(f,"]");
}
fprintf(f,"}}\n");fclose(f);
}
+24
View File
@@ -0,0 +1,24 @@
/* Independent diagnostic worker. Never linked by the production builder. */
#ifndef CONTEXT_FALLBACK_DIAG_H
#define CONTEXT_FALLBACK_DIAG_H
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0600
#endif
#include <windows.h>
#include <stdint.h>
#include "local_probe.h"
extern int dx_mode,dx_selected,dx_region,dx_position,dx_shadow;
extern const int dx_start_pos[LP_NR],dx_contextual[LP_NR],dx_version[LP_NO+1],dx_mutates[LP_NO];
extern const int dx_in_offset[LP_NO+1],dx_out_offset[LP_NO+1];
void dx_initialize(void);void dx_finish(void);void dx_jacobian(void);
int dx_reuse(int,NativePropertyCache*,NativePipeCache*,double*,double*,double*,double*,double*);
void dx_eval_begin(double,const double*);void dx_eval_end(void);
void dx_schedule(NativePropertyCache*,NativePipeCache*);
void dx_op_begin(int,const double*);void dx_op_end(int,const double*,NativePropertyCache*,NativePipeCache*);
void dx_region_end(double*,double*,double*,double*,double*,NativePropertyCache*,NativePipeCache*);
int dx_eval(double,const double*,double*,double*,ModelJacobianWorkspace*);
uint64_t dx_kernel_begin(int);void dx_kernel_end(int,uint64_t);
void dx_validate_matrix(double,const double*,const double*);
void dx_property_created(NativePropertyCache*,NativePropertyState*);
static inline uint64_t dx_clock(void){unsigned lo,hi;__asm__ __volatile__("lfence\n\trdtsc\n\tlfence":"=a"(lo),"=d"(hi)::"memory");return ((uint64_t)hi<<32)|lo;}
#endif
+578
View File
@@ -0,0 +1,578 @@
/* Diagnostic only. The semantic interpreter never calls a physics kernel. */
#include "context_access_diag.h"
#include "context_shadow_replay.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
#include <errno.h>
#include <fenv.h>
#if defined(__SSE__)
#include <xmmintrin.h>
#endif
typedef struct {fenv_t standard;int rounding;unsigned sse;} SREnvironment;
static unsigned sse_control(void){
#if defined(__SSE__)
return _mm_getcsr();
#else
return 0;
#endif
}
static void save_environment(SREnvironment *env){
fegetenv(&env->standard);env->rounding=fegetround();env->sse=0;
#if defined(__SSE__)
env->sse=_mm_getcsr();
#endif
}
static void restore_environment(const SREnvironment *env){
fesetenv(&env->standard);fesetround(env->rounding);
#if defined(__SSE__)
/* MinGW's fesetenv does not restore the complete SSE control register. */
_mm_setcsr(env->sse);
#endif
}
enum {OFF,RECORD,REFERENCE,CANDIDATE,TAIL};
enum {READ_STATE=1,WRITE_STATE,WRITE_OR,READ_PIPE,WRITE_PIPE,QUERY,ALLOCATE,VALID,SCALAR};
enum {OK,NO_RECORD,INPUTS,FIRST_MATCH,QUERY_PATH,CONSUMED,VALID_BITS,CAPACITY,OBSERVER,
EXISTING_UPDATE,PIPE_BRANCH,UNKNOWN_EFFECT,NONFINITE,MEMO_BINDING,MEMO_MISS,MEMO_VALUE,OVERFLOW,NREASONS};
static const char *reasons[]={"accepted","no_current_record","inputs","first_match_relation","query_hit_miss_path",
"consumed_field","valid_test","capacity_scratch","observer_nonnull","existing_entry_update","pipe_hit_branch",
"unknown_effect_or_schema","nonfinite_or_uncovered_branch","memo_lifetime_binding","memo_miss","memo_value","metadata_overflow"};
typedef struct {
int type,slot,offset,size,aux;
unsigned mask;
unsigned char data[sizeof(NativePropertyState)];
double result;
} Event;
typedef struct {
Event events[SR_EVENTS];
unsigned long long jac;
uintptr_t memo_binding;
double inputs[4],output;
size_t entry_count;
int position,n,ready,error,query_open,query_index,path;
int required_flags,baseline_errno,rounding;
unsigned required_sse_flags,sse_mode;
} Plan;
typedef struct {
unsigned long long total,accepted,rejected,mismatches,count_different,relocated,relocated_slots,appends;
unsigned long long paths[4],pt_hit,pt_miss,ph_hit,ph_miss,rejects[NREASONS];
unsigned long long live_contamination,rollbacks,tail_equal,live_tail_equal,memo_immutable,negative_passed;
unsigned long long untouched_equal,metadata_immutable;
size_t metadata_min,metadata_max,event_max;
} Statistics;
static Plan plan;
static Statistics stats;
static int phase,position,group,configured,pending_live,pending_status;
static unsigned long long jac;
static double sim_time,op_inputs[4];
static NativePropertyCache *bound;
static NativePipeCache *bound_pipes;
static SRContext entry,reference,candidate,overlay,before_transaction,tail_ref,tail_cand;
static SRFrame frame_ref,frame_cand;
static FILE *trials,*negative;
static unsigned long long forbidden_native_calls;
static void fatal(const char *message){fprintf(stderr,"shadow fatal: %s\n",message);abort();}
static void initialize(void){
if(configured)return;
const char *s=getenv("CONTEXT_SHADOW_POSITION");configured=s?atoi(s):16;
if(configured!=16 && configured!=52)fatal("invalid stage");
trials=fopen("shadow-trials.jsonl","wb");negative=fopen("shadow-negative.jsonl","wb");
if(!trials || !negative)fatal("open logs");
stats.metadata_min=(size_t)-1;
}
void sr_native_enter(const char *name){
if(phase==CANDIDATE){forbidden_native_calls++;fprintf(stderr,"Candidate called %s\n",name);fatal("physical call during replay");}
}
static int locate(const void *ptr,size_t width,int *domain,int *slot,int *offset){
uintptr_t p=(uintptr_t)ptr;
const void *bases[]={bound,bound?bound->states:NULL,bound_pipes};
size_t sizes[]={sizeof(NativePropertyCache),bound?bound->capacity*sizeof(NativePropertyState):0,SR_PIPES*sizeof(NativePipeCache)};
size_t units[]={sizeof(NativePropertyCache),sizeof(NativePropertyState),sizeof(NativePipeCache)};
for(int i=0;i<3;i++)if(bases[i] && p>=(uintptr_t)bases[i] && p-(uintptr_t)bases[i]+width<=sizes[i]){
size_t delta=p-(uintptr_t)bases[i];*domain=i;*slot=(int)(delta/units[i]);*offset=(int)(delta%units[i]);return 1;
}
return 0;
}
static Event *event(int type){
if(plan.n>=SR_EVENTS){plan.error=OVERFLOW;return NULL;}
Event *e=&plan.events[plan.n++];memset(e,0,sizeof(*e));e->type=type;return e;
}
static int state_pointer(int offset){
return offset==(int)offsetof(NativePropertyState,jacobian) || offset==(int)offsetof(NativePropertyState,temperatures);
}
static int floating_field(int domain,int offset,int size){
if(size!=8)return 0;
if(domain==1 && state_pointer(offset))return 0;
return offset>=(int)offsetof(NativeMedium,R) && offset<(domain==1?(int)offsetof(NativePropertyState,valid):(int)offsetof(NativePipeCache,kind));
}
void ax_access(const char *action,const void *ptr,size_t width,const char *field,const char *fn){
(void)field;
if(phase!=RECORD)return;
int domain,slot,offset;
if(!locate(ptr,width,&domain,&slot,&offset)){
/* The only recorded store to a stack local is scalar_get's output. */
if(!strcmp(action,"write") && strcmp(fn,"native_jacobian_scalar_get"))plan.error=UNKNOWN_EFFECT;
return;
}
if(domain==0){
if(!strcmp(action,"write") && offset!=(int)offsetof(NativePropertyCache,count))plan.error=UNKNOWN_EFFECT;
return; /* count/capacity and pointer bindings are semantic guards. */
}
if(plan.query_open && !strcmp(action,"read"))return; /* Re-run query, not baseline's scan slots. */
if(width>sizeof(NativePropertyState)){plan.error=UNKNOWN_EFFECT;return;}
int write=!strcmp(action,"write");
Event *e=event(domain==1?(write?WRITE_STATE:READ_STATE):(write?WRITE_PIPE:READ_PIPE));
if(!e)return;
e->slot=slot;e->offset=offset;e->size=(int)width;memcpy(e->data,ptr,width);
if(floating_field(domain,offset,(int)width)){double x;memcpy(&x,ptr,8);if(!isfinite(x))plan.error=NONFINITE;}
if(domain==1 && write && (size_t)slot<plan.entry_count)plan.error=EXISTING_UPDATE;
if(domain==2 && slot!=(configured==16?0:28))plan.error=UNKNOWN_EFFECT;
}
void sr_or(const void *ptr,unsigned mask,const char *field,const char *fn){
int previous=plan.n;ax_access("write",ptr,sizeof(unsigned),field,fn);
if(phase==RECORD && plan.n>previous){Event *e=&plan.events[plan.n-1];e->type=WRITE_OR;e->mask=mask;}
}
void ax_bind(NativePropertyCache *p,NativePipeCache *pipes){bound=p;bound_pipes=pipes;}
void ax_begin(unsigned long long j,int color,int pos,double t,const double *inputs,int n){
initialize();phase=OFF;jac=j;group=color;position=pos;sim_time=t;
if(pos!=configured)return;
if(n!=4 || !inputs)fatal("operation signature");
memcpy(op_inputs,inputs,sizeof(op_inputs));
if(color<0){
memset(&plan,0,sizeof(plan));plan.jac=j;plan.position=pos;plan.entry_count=bound->count;
plan.memo_binding=(uintptr_t)bound->jacobian;memcpy(plan.inputs,inputs,sizeof(plan.inputs));
plan.rounding=fegetround();
plan.sse_mode=sse_control()&~63u;
if(bound->temperatures)plan.error=OBSERVER;
phase=RECORD;
}
}
void ax_query(const char *kind,const NativeMedium *m,double p,double second){
if(phase!=RECORD)return;
if(plan.query_open){plan.error=UNKNOWN_EFFECT;return;}
Event *e=event(QUERY);if(!e)return;
e->aux=!strcmp(kind,"PH");e->offset=m->real_helium;e->slot=-2;
double key[]={p,second,m->R,m->cp,m->Tref,m->slope,m->mu,m->muT,m->S};
memcpy(e->data,key,sizeof(key));for(int i=0;i<9;i++)if(!isfinite(key[i]))plan.error=NONFINITE;
plan.query_open=1;plan.query_index=plan.n-1;
}
void ax_match(const char *kind,NativePropertyState *s){
if(phase!=RECORD)return;
if(!plan.query_open){plan.error=UNKNOWN_EFFECT;return;}
Event *e=&plan.events[plan.query_index];
if(e->aux!=(!strcmp(kind,"PH")))plan.error=UNKNOWN_EFFECT;
e->slot=s?(int)(s-bound->states):-1;plan.query_open=0;
}
void ax_new(NativePropertyCache *cache,NativePropertyState *s,int valid){
if(phase!=RECORD)return;
int domain,slot,offset;
if(!valid || !cache || !locate(s,sizeof(*s),&domain,&slot,&offset) || domain!=1 || offset || (size_t)slot+1!=cache->count){plan.error=CAPACITY;return;}
Event *e=event(ALLOCATE);if(e)e->slot=slot;
}
unsigned ax_test(NativePropertyState *s,unsigned mask,const char *fn){
(void)fn;unsigned result=s->valid&mask;
if(phase==RECORD && !plan.query_open){
int domain,slot,offset;
if(!locate(s,sizeof(*s),&domain,&slot,&offset) || domain!=1){plan.error=CAPACITY;return result;}
Event *e=event(VALID);if(e){e->slot=slot;e->mask=mask;memcpy(e->data,&result,sizeof(result));}
}
return result;
}
void ax_scalar(const char *action,NativeJacobianScalars *memo,int kind,int medium,const double *keys,size_t n,int hit,const double *value){
if(phase!=RECORD)return;
if(memo!=bound->jacobian || n>NATIVE_JACOBIAN_SCALAR_KEYS){plan.error=UNKNOWN_EFFECT;return;}
if(!strcmp(action,"get")){
Event *e=event(SCALAR);if(!e)return;e->slot=kind;e->offset=medium;e->size=(int)n;memcpy(e->data,keys,n*8);
if(hit && value){e->result=*value;e->aux=1;}
}else if(!strcmp(action,"put_attempt")){
int found=0;
for(int i=plan.n-1;i>=0;i--){Event *e=&plan.events[i];if(e->type==SCALAR && e->slot==kind && e->offset==medium && e->size==(int)n && !memcmp(e->data,keys,n*8)){
if(!value || !isfinite(*value)){plan.error=NONFINITE;break;}e->result=*value;e->aux=1;found=1;break;
}}
if(!found)plan.error=UNKNOWN_EFFECT;
}else plan.error=UNKNOWN_EFFECT;
}
static int path_of_plan(void){
int pattern[4],n=0;
for(int i=0;i<plan.n;i++)if(plan.events[i].type==QUERY){Event *e=&plan.events[i];if(n==4)return -1;pattern[n++]=e->aux*2+(e->slot>=0);}
if(n==0)return 0;
if(n==2 && pattern[0]==0 && pattern[1]==0)return 1;
if(n==4 && pattern[0]==2 && pattern[1]==0 && pattern[2]==1 && pattern[3]==0)return 2;
if(n==3 && pattern[0]==3 && pattern[1]==1 && pattern[2]==0)return 3;
return -1;
}
void ax_end(const double *outputs,int n){
if(phase!=RECORD)return;
if(n!=1 || !isfinite(outputs[0]))plan.error=NONFINITE;
plan.output=outputs[0];plan.path=path_of_plan();
plan.required_flags=fetestexcept(FE_ALL_EXCEPT);plan.baseline_errno=errno;
plan.required_sse_flags=sse_control()&63u;
if(plan.path<0 || plan.query_open)plan.error=NONFINITE;
for(int i=0;i<plan.n;i++)if(plan.events[i].type==SCALAR && !plan.events[i].aux)plan.error=UNKNOWN_EFFECT;
plan.ready=1;phase=OFF;
}
void ax_result(int result){(void)result;}
static void copy_context(SRContext *dst,const SRContext *src){
memcpy(dst,src,sizeof(*dst));dst->context.states=dst->states;dst->memo.entries=dst->entries;
if(src->context.jacobian==&src->memo)dst->context.jacobian=&dst->memo;
if(src->context.temperatures==&src->observer)dst->context.temperatures=&dst->observer;
for(size_t i=0;i<dst->context.count;i++){
if(src->states[i].jacobian==&src->memo)dst->states[i].jacobian=&dst->memo;
if(src->states[i].temperatures==&src->observer)dst->states[i].temperatures=&dst->observer;
}
}
static void from_live(SRContext *dst,NativePropertyCache *src,NativePipeCache *pipes){
if(src->count>SR_STATES || src->capacity>SR_STATES || !src->jacobian || src->jacobian->capacity>MODEL_JACOBIAN_SCALAR_COUNT)fatal("context bounds");
memset(dst,0,sizeof(*dst));memset(dst->states,0xa5,sizeof(dst->states));
dst->context=*src;memcpy(dst->states,src->states,src->count*sizeof(*src->states));memcpy(dst->pipes,pipes,sizeof(dst->pipes));
dst->memo=*src->jacobian;memcpy(dst->entries,src->jacobian->entries,src->jacobian->capacity*sizeof(*dst->entries));
dst->memo.entries=dst->entries;dst->context.states=dst->states;dst->context.jacobian=&dst->memo;
if(src->temperatures){dst->observer=*src->temperatures;dst->context.temperatures=&dst->observer;}
for(size_t i=0;i<src->count;i++){
if(dst->states[i].jacobian==src->jacobian)dst->states[i].jacobian=&dst->memo;
if(src->temperatures && dst->states[i].temperatures==src->temperatures)dst->states[i].temperatures=&dst->observer;
}
}
static int same_medium_key(const NativeMedium *m,int kind,const double *key){
return m->real_helium==kind && m->R==key[2] && m->cp==key[3] && m->Tref==key[4] && m->slope==key[5] && m->mu==key[6] && m->muT==key[7] && m->S==key[8];
}
static int first_match(SRContext *ctx,const Event *e){
double key[9];memcpy(key,e->data,sizeof(key));
for(size_t i=0;i<ctx->context.count;i++){
NativePropertyState *s=&ctx->states[i];unsigned mask=e->aux?NATIVE_PROPERTY_H:NATIVE_PROPERTY_PT;
if((s->valid&mask) && s->p==key[0] && (e->aux?s->h:s->T)==key[1] && same_medium_key(&s->medium,e->offset,key))return (int)i;
}
return -1;
}
static int map_slot(int *mapping,int logical,int actual){
if(logical<0 || logical>=SR_STATES || actual<0 || actual>=SR_STATES)return 0;
if(mapping[logical]>=0)return mapping[logical]==actual;
for(int i=0;i<SR_STATES;i++)if(i!=logical && mapping[i]==actual)return 0;
mapping[logical]=actual;return 1;
}
/* Same exact bit-key and bounded linear-probe semantics as the read-only memo.
* Only counters change. No physical fallback and no memo insertion is allowed. */
static int scalar_lookup(SRContext *ctx,const Event *e){
NativeJacobianScalars *memo=&ctx->memo;size_t capacity=memo->capacity;
if(!capacity || capacity>MODEL_JACOBIAN_SCALAR_COUNT || (capacity&(capacity-1)) || memo->recording)return MEMO_BINDING;
uint64_t hash=UINT64_C(14695981039346656037)^(unsigned)e->slot;
hash=(hash^(unsigned)e->offset)*UINT64_C(1099511628211);
for(int i=0;i<e->size;i++){uint64_t bits;memcpy(&bits,e->data+i*8,8);hash=(hash^bits)*UINT64_C(1099511628211);hash^=hash>>32;}
if(!hash)hash=1;
size_t limit=capacity<32?capacity:32;
for(size_t i=0;i<limit;i++){
NativeJacobianScalarEntry *v=&ctx->entries[(hash+i)&(capacity-1)];
if(!v->hash)return MEMO_MISS;
if(v->hash==hash && v->kind==e->slot && v->medium_kind==e->offset && v->input_count==(size_t)e->size && !memcmp(v->inputs,e->data,e->size*8)){
if(!isfinite(v->value) || memcmp(&v->value,&e->result,8))return MEMO_VALUE;
memo->reuses[e->slot]++;return OK;
}
}
return MEMO_MISS;
}
static int state_read_equal(SRContext *ctx,int slot,const Event *e){
NativePropertyState *s=&ctx->states[slot];
if(e->offset==(int)offsetof(NativePropertyState,jacobian))return s->jacobian==&ctx->memo;
if(e->offset==(int)offsetof(NativePropertyState,temperatures))return s->temperatures==NULL;
return !memcmp((unsigned char*)s+e->offset,e->data,e->size);
}
static int replay_overlay(SRContext *ctx,const double *inputs,int *mapping,int *appends){
if(!plan.ready || plan.jac!=jac || plan.position!=position)return NO_RECORD;
if(plan.error)return plan.error;
if(fegetround()!=plan.rounding || (fetestexcept(FE_ALL_EXCEPT)&plan.required_flags)!=plan.required_flags ||
(plan.baseline_errno && errno!=plan.baseline_errno) || (sse_control()&~63u)!=plan.sse_mode ||
(sse_control()&plan.required_sse_flags)!=plan.required_sse_flags)return UNKNOWN_EFFECT;
if(memcmp(inputs,plan.inputs,sizeof(plan.inputs)))return INPUTS;
for(int i=0;i<4;i++)if(!isfinite(inputs[i]))return NONFINITE;
if(ctx->context.temperatures)return OBSERVER;
if(ctx->context.capacity>SR_STATES || ctx->context.count>ctx->context.capacity)return CAPACITY;
if(ctx->context.jacobian!=&ctx->memo || ctx->memo.entries!=ctx->entries || ctx->memo.recording || (uintptr_t)bound->jacobian!=plan.memo_binding)return MEMO_BINDING;
for(size_t i=0;i<ctx->context.count;i++)if(ctx->states[i].temperatures || ctx->states[i].jacobian!=&ctx->memo)return MEMO_BINDING;
if(ctx->pipes[position==16?0:28].valid)return PIPE_BRANCH;
for(int i=0;i<SR_STATES;i++)mapping[i]=-1;
*appends=0;int pending_miss=0;
for(int i=0;i<plan.n;i++){
const Event *e=&plan.events[i];int slot=-1;
if(e->type<READ_STATE || e->type>SCALAR)return UNKNOWN_EFFECT;
if(e->type==QUERY){
int actual=first_match(ctx,e);
if((e->slot<0)!=(actual<0))return QUERY_PATH;
if(e->slot>=0 && !map_slot(mapping,e->slot,actual))return FIRST_MATCH;
pending_miss=(actual<0 && !e->aux);continue;
}
if(e->type==ALLOCATE){
if(!pending_miss)return UNKNOWN_EFFECT;
if(ctx->context.count>=ctx->context.capacity)return CAPACITY;
if(!map_slot(mapping,e->slot,(int)ctx->context.count))return FIRST_MATCH;
ctx->context.count++;(*appends)++;pending_miss=0;continue;
}
if(e->type==SCALAR){int reason=scalar_lookup(ctx,e);if(reason)return reason;continue;}
if(e->type==READ_PIPE || e->type==WRITE_PIPE){
if(e->slot!=(position==16?0:28) || e->offset<0 || e->offset+e->size>(int)sizeof(NativePipeCache))return UNKNOWN_EFFECT;
unsigned char *ptr=(unsigned char*)&ctx->pipes[e->slot]+e->offset;
if(e->type==READ_PIPE){if(memcmp(ptr,e->data,e->size))return PIPE_BRANCH;}
else memcpy(ptr,e->data,e->size);
continue;
}
if(e->slot<0 || e->slot>=SR_STATES || (slot=mapping[e->slot])<0 || (size_t)slot>=ctx->context.count)return FIRST_MATCH;
NativePropertyState *s=&ctx->states[slot];
if(e->type==VALID){unsigned expected;memcpy(&expected,e->data,sizeof(expected));if((s->valid&e->mask)!=expected)return VALID_BITS;continue;}
if(e->offset<0 || e->size<0 || e->offset+e->size>(int)sizeof(*s))return UNKNOWN_EFFECT;
if(e->type==READ_STATE){if(!state_read_equal(ctx,slot,e))return CONSUMED;continue;}
if(e->type!=WRITE_STATE && e->type!=WRITE_OR)return UNKNOWN_EFFECT;
if((size_t)e->slot<plan.entry_count || (size_t)slot<entry.context.count)return EXISTING_UPDATE;
if(e->type==WRITE_OR){if(e->offset!=(int)offsetof(NativePropertyState,valid))return UNKNOWN_EFFECT;s->valid|=e->mask;}
else if(e->offset==(int)offsetof(NativePropertyState,jacobian))s->jacobian=&ctx->memo;
else if(e->offset==(int)offsetof(NativePropertyState,temperatures))s->temperatures=NULL;
else memcpy((unsigned char*)s+e->offset,e->data,e->size);
}
if(pending_miss || !isfinite(plan.output))return NONFINITE;
return OK;
}
static void commit_patch(SRContext *dst,double *output,const int *mapping){
/* Apply only validated stores, in recorded order. Everything not addressed
* by a store stays in the original Candidate allocation, even at commit. */
for(int i=0;i<plan.n;i++){
const Event *e=&plan.events[i];
if(e->type==ALLOCATE)dst->context.count++;
else if(e->type==SCALAR)dst->memo.reuses[e->slot]++;
else if(e->type==WRITE_PIPE)memcpy((unsigned char*)&dst->pipes[e->slot]+e->offset,e->data,e->size);
else if(e->type==WRITE_STATE || e->type==WRITE_OR){
NativePropertyState *s=&dst->states[mapping[e->slot]];
if(e->type==WRITE_OR)s->valid|=e->mask;
else if(e->offset==(int)offsetof(NativePropertyState,jacobian))s->jacobian=&dst->memo;
else if(e->offset==(int)offsetof(NativePropertyState,temperatures))s->temperatures=NULL;
else memcpy((unsigned char*)s+e->offset,e->data,e->size);
}
}
*output=plan.output;
}
static int transaction(SRContext *dst,const double *inputs,double *output,int *mapping,int *appends){
copy_context(&overlay,dst);phase=CANDIDATE;
int reason=replay_overlay(&overlay,inputs,mapping,appends);
/* No stores to dst or output occur before this commit point. */
if(!reason)commit_patch(dst,output,mapping);
phase=OFF;return reason;
}
typedef struct {const char *name;size_t offset,size;} Field;
#define FIELD(T,n) {#n,offsetof(T,n),sizeof(((T*)0)->n)}
#define MEDIUM(T,n) {"medium." #n,offsetof(T,medium)+offsetof(NativeMedium,n),sizeof(((NativeMedium*)0)->n)}
#define MEDIUM_FIELDS(T) MEDIUM(T,real_helium),MEDIUM(T,R),MEDIUM(T,cp),MEDIUM(T,Tref),MEDIUM(T,slope),MEDIUM(T,mu),MEDIUM(T,muT),MEDIUM(T,S)
static const Field state_fields[]={MEDIUM_FIELDS(NativePropertyState),FIELD(NativePropertyState,p),FIELD(NativePropertyState,T),FIELD(NativePropertyState,h),FIELD(NativePropertyState,rho),FIELD(NativePropertyState,mu),FIELD(NativePropertyState,isentropic_factor),FIELD(NativePropertyState,isentropic_exponent),FIELD(NativePropertyState,valid)};
static const Field pipe_fields[]={MEDIUM_FIELDS(NativePipeCache),FIELD(NativePipeCache,p1),FIELD(NativePipeCache,p2),FIELD(NativePipeCache,T),FIELD(NativePipeCache,diameter),FIELD(NativePipeCache,length),FIELD(NativePipeCache,roughness),FIELD(NativePipeCache,flow),FIELD(NativePipeCache,kind),FIELD(NativePipeCache,valid)};
static char difference[160];
static int differ(const char *domain,int slot,const char *field){snprintf(difference,sizeof(difference),"%s[%d].%s",domain,slot,field);return 1;}
static int compare_frame(const SRFrame *a,const SRFrame *b){
const double *left[]={a->dy,a->w,a->p,a->h,a->q,a->fb,(const double*)a->g};
const double *right[]={b->dy,b->w,b->p,b->h,b->q,b->fb,(const double*)b->g};
const char *names[]={"dy","w","p","h","q","fb","gas_scalar"};
const int counts[]={NSTATES,NOUTPUTS,48,232,232,152,56*5};
for(int array=0;array<7;array++)for(int i=0;i<counts[array];i++)if(memcmp(&left[array][i],&right[array][i],8))return differ(names[array],i,"value");
return 0;
}
static int compare_contexts(const SRContext *a,const SRContext *b){
if(a->context.count!=b->context.count)return differ("property",-1,"count");
if(a->context.capacity!=b->context.capacity)return differ("property",-1,"capacity");
if(a->context.states!=a->states || b->context.states!=b->states)return differ("property",-1,"states_binding");
if(a->context.jacobian!=&a->memo || b->context.jacobian!=&b->memo)return differ("property",-1,"memo_binding");
if(!!a->context.temperatures!=!!b->context.temperatures)return differ("property",-1,"observer_binding");
for(size_t i=0;i<a->context.count;i++){
const NativePropertyState *x=&a->states[i],*y=&b->states[i];
for(size_t f=0;f<sizeof(state_fields)/sizeof(*state_fields);f++)if(memcmp((const char*)x+state_fields[f].offset,(const char*)y+state_fields[f].offset,state_fields[f].size))return differ("states",(int)i,state_fields[f].name);
if(x->jacobian!=&a->memo || y->jacobian!=&b->memo)return differ("states",(int)i,"memo_binding");
if(!!x->temperatures!=!!y->temperatures)return differ("states",(int)i,"observer_binding");
}
for(int i=0;i<SR_PIPES;i++)for(size_t f=0;f<sizeof(pipe_fields)/sizeof(*pipe_fields);f++)if(memcmp((const char*)&a->pipes[i]+pipe_fields[f].offset,(const char*)&b->pipes[i]+pipe_fields[f].offset,pipe_fields[f].size))return differ("pipes",i,pipe_fields[f].name);
if(a->memo.entries!=a->entries || b->memo.entries!=b->entries || a->memo.capacity!=b->memo.capacity || a->memo.recording!=b->memo.recording)return differ("memo",-1,"lifecycle");
if(memcmp(a->memo.evaluations,b->memo.evaluations,sizeof(a->memo.evaluations)))return differ("memo",-1,"evaluations");
if(memcmp(a->memo.reuses,b->memo.reuses,sizeof(a->memo.reuses)))return differ("memo",-1,"reuses");
for(size_t i=0;i<a->memo.capacity;i++)if(memcmp(&a->entries[i],&b->entries[i],sizeof(*a->entries))){
const Field fields[]={FIELD(NativeJacobianScalarEntry,hash),FIELD(NativeJacobianScalarEntry,inputs),FIELD(NativeJacobianScalarEntry,value),FIELD(NativeJacobianScalarEntry,kind),FIELD(NativeJacobianScalarEntry,medium_kind),FIELD(NativeJacobianScalarEntry,input_count)};
for(size_t k=0;k<sizeof(fields)/sizeof(*fields);k++)if(memcmp((const char*)&a->entries[i]+fields[k].offset,(const char*)&b->entries[i]+fields[k].offset,fields[k].size))return differ("memo_entries",(int)i,fields[k].name);
return differ("memo_entries",(int)i,"padding_byte");
}
if(memcmp(&a->observer,&b->observer,sizeof(a->observer)))return differ("warning",-1,"observer");
return 0;
}
static void dump_blob(FILE *f,const char *name,const void *ptr,size_t n){
fprintf(f,"\"%s\":\"",name);const unsigned char *b=ptr;for(size_t i=0;i<n;i++)fprintf(f,"%02x",b[i]);fputc('"',f);
}
static void mismatch(const char *where,double ref_output,double cand_output){
stats.mismatches++;
if(stats.mismatches==1){
FILE *f=fopen("first-mismatch.json","wb");if(!f)fatal("mismatch log");
fprintf(f,"{\"jac\":%llu,\"position\":%d,\"t\":%.17g,\"where\":\"%s\",\"field\":\"%s\",",jac,configured,sim_time,where,difference);
dump_blob(f,"probe_entry",&entry,sizeof(entry));fputc(',',f);dump_blob(f,"reference",&reference,sizeof(reference));fputc(',',f);dump_blob(f,"candidate",&candidate,sizeof(candidate));fputc(',',f);
dump_blob(f,"metadata",&plan,sizeof(plan));fputc(',',f);dump_blob(f,"reference_output",&ref_output,8);fputc(',',f);dump_blob(f,"candidate_output",&cand_output,8);fputc(',',f);
dump_blob(f,"reference_frame",&frame_ref,sizeof(frame_ref));fputc(',',f);dump_blob(f,"candidate_frame",&frame_cand,sizeof(frame_cand));fputc(',',f);
dump_blob(f,"reference_tail_context",&tail_ref,sizeof(tail_ref));fputc(',',f);dump_blob(f,"candidate_tail_context",&tail_cand,sizeof(tail_cand));fputs("}\n",f);fclose(f);
}
}
static void fill_frame(SRFrame *f,const double *p,const double *h,const double *q,const double *fb,const NativeGas *g,const double *w){
memset(f,0,sizeof(*f));memcpy(f->p,p,sizeof(f->p));memcpy(f->h,h,sizeof(f->h));memcpy(f->q,q,sizeof(f->q));memcpy(f->fb,fb,sizeof(f->fb));memcpy(f->g,g,sizeof(f->g));memcpy(f->w,w,sizeof(f->w));
}
static int live_equal(NativePropertyCache *p,NativePipeCache *pipes){
if(p->count!=entry.context.count || p->capacity!=entry.context.capacity || p->temperatures)return 0;
for(size_t i=0;i<p->count;i++){
NativePropertyState s=entry.states[i];s.jacobian=p->jacobian;
if(memcmp(&s,&p->states[i],sizeof(s)))return 0;
}
return !memcmp(pipes,entry.pipes,sizeof(entry.pipes)) && !memcmp(p->jacobian->entries,entry.entries,p->jacobian->capacity*sizeof(*entry.entries)) &&
!memcmp(p->jacobian->evaluations,entry.memo.evaluations,sizeof(entry.memo.evaluations)) && !memcmp(p->jacobian->reuses,entry.memo.reuses,sizeof(entry.memo.reuses));
}
static int untouched_equal(const SRContext *ctx){
for(size_t i=0;i<entry.context.count;i++){
NativePropertyState expected=entry.states[i];expected.jacobian=(NativeJacobianScalars*)&ctx->memo;
if(memcmp(&ctx->states[i],&expected,sizeof(expected)))return 0;
}
for(int i=0;i<SR_PIPES;i++)if(i!=(position==16?0:28) && memcmp(&ctx->pipes[i],&entry.pipes[i],sizeof(NativePipeCache)))return 0;
/* Unallocated tail is a poison-filled diagnostic canary. */
if(memcmp(ctx->states+ctx->context.count,entry.states+ctx->context.count,(SR_STATES-ctx->context.count)*sizeof(NativePropertyState)))return 0;
return 1;
}
static uint64_t metadata_hash(void){
const unsigned char *bytes=(const unsigned char*)&plan;uint64_t h=UINT64_C(14695981039346656037);
for(size_t i=0;i<sizeof(plan);i++)h=(h^bytes[i])*UINT64_C(1099511628211);
return h;
}
static void negative_tests(const double *inputs);
void sr_shadow(int pos,double t,const double *y,NativePropertyCache *properties,NativePipeCache *pipes,
const double *p,const double *h,const double *q,const double *fb,const NativeGas *g,const double *w,SROperation operation,SRTail tail){
initialize();
if(pos!=configured || group!=(configured==16?6:18))return;
uint64_t metadata_before=metadata_hash();
stats.total++;stats.count_different+=(plan.entry_count!=properties->count);
int saved_errno=errno;SREnvironment saved_env;save_environment(&saved_env);int entry_flags=fetestexcept(FE_ALL_EXCEPT);
from_live(&entry,properties,pipes);copy_context(&reference,&entry);copy_context(&candidate,&entry);
if(compare_contexts(&reference,&candidate))fatal("initial clones differ");
if(reference.states==candidate.states || reference.entries==candidate.entries)fatal("aliased clones");
phase=REFERENCE;double ref_output=operation(&reference.context,reference.pipes,op_inputs);phase=OFF;
int ref_errno=errno,ref_flags=fetestexcept(FE_ALL_EXCEPT);unsigned ref_sse=sse_control();
errno=saved_errno;restore_environment(&saved_env);
memcpy(&before_transaction,&candidate,sizeof(candidate));double cand_output=NAN;int mapping[SR_STATES],appends=0;
int reason=transaction(&candidate,op_inputs,&cand_output,mapping,&appends);
int cand_errno=errno,cand_flags=fetestexcept(FE_ALL_EXCEPT);unsigned cand_sse=sse_control();
if(reason){
stats.rejected++;stats.rejects[reason]++;
if(memcmp(&before_transaction,&candidate,sizeof(candidate)) || !isnan(cand_output))fatal("reject modified Candidate");
stats.rollbacks++;
}else{
stats.accepted++;stats.paths[plan.path]++;stats.appends+=appends;
int relocated=0;
for(int i=0;i<SR_STATES;i++)if(mapping[i]>=0 && mapping[i]!=i){relocated++;stats.relocated_slots++;}
stats.relocated+=(relocated>0);
for(int i=0;i<plan.n;i++)if(plan.events[i].type==QUERY){Event *e=&plan.events[i];if(e->aux){if(e->slot>=0)stats.ph_hit++;else stats.ph_miss++;}else{if(e->slot>=0)stats.pt_hit++;else stats.pt_miss++;}}
size_t bytes=sizeof(Plan)-sizeof(plan.events)+(size_t)plan.n*sizeof(Event);
if(bytes<stats.metadata_min)stats.metadata_min=bytes;
if(bytes>stats.metadata_max)stats.metadata_max=bytes;
if((size_t)plan.n>stats.event_max)stats.event_max=(size_t)plan.n;
if(memcmp(&ref_output,&cand_output,8)){differ("operation",pos,"output");mismatch("operation",ref_output,cand_output);}
else if(compare_contexts(&reference,&candidate))mismatch("operation_context",ref_output,cand_output);
if(!untouched_equal(&reference) || !untouched_equal(&candidate)){differ("probe",-1,"unwritten_data");mismatch("untouched",ref_output,cand_output);}else stats.untouched_equal++;
if(ref_errno!=cand_errno || ref_flags!=cand_flags || ref_sse!=cand_sse){differ("warning",-1,"errno_or_fenv_sse");mismatch("side_effect",ref_output,cand_output);}
if(memcmp(entry.entries,reference.entries,entry.memo.capacity*sizeof(*entry.entries)) || memcmp(entry.entries,candidate.entries,entry.memo.capacity*sizeof(*entry.entries))){differ("memo",-1,"readonly_entries");mismatch("memo",ref_output,cand_output);}else stats.memo_immutable++;
/* Both independent continuations begin at these operation exits. */
copy_context(&tail_ref,&reference);copy_context(&tail_cand,&candidate);
fill_frame(&frame_ref,p,h,q,fb,g,w);memcpy(&frame_cand,&frame_ref,sizeof(frame_ref));
int out_index=pos==16?45:152;frame_ref.q[out_index]=ref_output;frame_cand.q[out_index]=cand_output;
phase=TAIL;errno=saved_errno;restore_environment(&saved_env);
int ref_status=tail(t,y,&tail_ref,&frame_ref);
errno=saved_errno;restore_environment(&saved_env);
int cand_status=tail(t,y,&tail_cand,&frame_cand);phase=OFF;
if(ref_status!=cand_status){differ("evaluator",-1,"return");mismatch("continuation",ref_output,cand_output);}
else if(compare_frame(&frame_ref,&frame_cand))mismatch("continuation",ref_output,cand_output);
else if(compare_contexts(&tail_ref,&tail_cand))mismatch("continuation_context",ref_output,cand_output);
else stats.tail_equal++;
pending_live=1;pending_status=ref_status;
}
fprintf(trials,"{\"jac\":%llu,\"position\":%d,\"t\":%.17g,\"reason\":\"%s\",\"baselineCount\":%llu,\"probeCount\":%llu,\"referenceCount\":%llu,\"candidateCount\":%llu,\"appends\":%d,\"path\":%d,\"events\":%d,\"entryFlags\":%d,\"referenceFlags\":%d,\"candidateFlags\":%d,\"mapping\":[",jac,pos,t,reasons[reason],(unsigned long long)plan.entry_count,(unsigned long long)entry.context.count,(unsigned long long)reference.context.count,(unsigned long long)candidate.context.count,appends,plan.path,plan.n,entry_flags,ref_flags,cand_flags);
int comma=0;if(!reason)for(int i=0;i<SR_STATES;i++)if(mapping[i]>=0)fprintf(trials,"%s[%d,%d]",comma++?",":"",i,mapping[i]);
fputs("]}\n",trials);
if(!reason && (jac==200 || (configured==52 && jac==1)))negative_tests(op_inputs);
if(metadata_hash()!=metadata_before)fatal("Reference or Candidate modified metadata");
stats.metadata_immutable++;
if(!live_equal(properties,pipes))stats.live_contamination++;
phase=OFF;restore_environment(&saved_env);errno=saved_errno;
#if defined(__SSE__)
if(_mm_getcsr()!=saved_env.sse)stats.live_contamination++;
#endif
if(fegetround()!=saved_env.rounding)stats.live_contamination++;
}
void sr_eval_result(int result,const double *dy,const double *w){
if(!pending_live){return;}pending_live=0;
int different=result!=pending_status;
if(different)differ("evaluator",-1,"return");
for(int i=0;!different && i<NSTATES;i++)if(memcmp(&dy[i],&frame_ref.dy[i],8))different=differ("live_dy",i,"value");
for(int i=0;!different && i<NOUTPUTS;i++)if(memcmp(&w[i],&frame_ref.w[i],8))different=differ("live_w",i,"value");
if(different)mismatch("live_tail",0,0);
else stats.live_tail_equal++;
}
/* Adversarial rejection tests run on copies. Every failed transaction must
* leave the entire destination and explicit output sentinel byte-identical. */
static void negative_case(const char *name,int expected,const double *inputs){
SRContext before;memcpy(&before,&candidate,sizeof(before));double output=123.25;int mapping[SR_STATES],appends;
int reason=transaction(&candidate,inputs,&output,mapping,&appends);
int unchanged=!memcmp(&before,&candidate,sizeof(before)) && output==123.25;
fprintf(negative,"{\"jac\":%llu,\"test\":\"%s\",\"reason\":\"%s\",\"expected\":\"%s\",\"unchanged\":%s}\n",jac,name,reasons[reason],reasons[expected],unchanged?"true":"false");
if(reason!=expected || !unchanged)fatal("negative transaction test");
stats.negative_passed++;
}
static void negative_tests(const double *inputs){
Plan saved;memcpy(&saved,&plan,sizeof(saved));
copy_context(&candidate,&entry);candidate.context.temperatures=&candidate.observer;negative_case("observer",OBSERVER,inputs);
copy_context(&candidate,&entry);candidate.context.capacity=candidate.context.count;negative_case("capacity",CAPACITY,inputs);
int allocations=0;for(int i=0;i<plan.n;i++)allocations+=plan.events[i].type==ALLOCATE;
if(allocations>1){copy_context(&candidate,&entry);candidate.context.capacity=candidate.context.count+1;negative_case("capacity_after_first_append",CAPACITY,inputs);}
copy_context(&candidate,&entry);candidate.pipes[position==16?0:28].valid=1;negative_case("pipe_hit",PIPE_BRANCH,inputs);
copy_context(&candidate,&entry);candidate.memo.recording=1;negative_case("memo_recording",MEMO_BINDING,inputs);
copy_context(&candidate,&entry);memset(candidate.entries,0,sizeof(candidate.entries));negative_case("memo_miss",MEMO_MISS,inputs);
copy_context(&candidate,&entry);
for(size_t i=0;i<candidate.memo.capacity;i++)if(candidate.entries[i].hash)candidate.entries[i].value=nextafter(candidate.entries[i].value,INFINITY);
negative_case("memo_value",MEMO_VALUE,inputs);
copy_context(&candidate,&entry);double changed[4];memcpy(changed,inputs,sizeof(changed));changed[0]=nextafter(changed[0],INFINITY);negative_case("changed_input",INPUTS,changed);
int qi=-1,ri=-1,vi=-1,wi=-1;
for(int i=0;i<plan.n;i++){
if(qi<0 && plan.events[i].type==QUERY)qi=i;
if(ri<0 && plan.events[i].type==READ_STATE && plan.events[i].offset==(int)offsetof(NativePropertyState,rho))ri=i;
if(vi<0 && plan.events[i].type==VALID)vi=i;
if(wi<0 && plan.events[i].type==WRITE_STATE)wi=i;
}
if(qi>=0){
copy_context(&candidate,&entry);Event *e=&plan.events[qi];e->slot=e->slot<0?0:-1;negative_case("query_path",QUERY_PATH,inputs);memcpy(&plan,&saved,sizeof(plan));
}
if(ri>=0){copy_context(&candidate,&entry);plan.events[ri].data[0]^=1;negative_case("consumed_rho",CONSUMED,inputs);memcpy(&plan,&saved,sizeof(plan));}
if(vi>=0){copy_context(&candidate,&entry);plan.events[vi].data[0]^=(unsigned char)plan.events[vi].mask;negative_case("valid_test",VALID_BITS,inputs);memcpy(&plan,&saved,sizeof(plan));}
if(wi>=0){copy_context(&candidate,&entry);plan.entry_count=SR_STATES;negative_case("existing_entry_store",EXISTING_UPDATE,inputs);memcpy(&plan,&saved,sizeof(plan));}
if(allocations>1){
int logical=-1;
for(int i=0;i<plan.n;i++)if(plan.events[i].type==ALLOCATE){if(logical<0)logical=plan.events[i].slot;else{plan.events[i].slot=logical;break;}}
copy_context(&candidate,&entry);negative_case("conflicting_append_identity",FIRST_MATCH,inputs);memcpy(&plan,&saved,sizeof(plan));
}
if(qi>=0 && plan.events[qi].slot>=0){
int hit=plan.events[qi].slot;
copy_context(&candidate,&entry);candidate.states[hit].rho=nextafter(candidate.states[hit].rho,INFINITY);negative_case("probe_consumed_rho",CONSUMED,inputs);
copy_context(&candidate,&entry);candidate.states[hit].valid^=NATIVE_PROPERTY_MU;negative_case("probe_valid_bit",VALID_BITS,inputs);
for(int i=qi+1;i<plan.n;i++)if(plan.events[i].type==QUERY && plan.events[i].slot==hit){plan.events[i].slot=hit+1;break;}
copy_context(&candidate,&entry);negative_case("incompatible_first_hit_identity",FIRST_MATCH,inputs);memcpy(&plan,&saved,sizeof(plan));
}
copy_context(&candidate,&entry);plan.events[plan.n-1].type=999;negative_case("late_unknown_event",UNKNOWN_EFFECT,inputs);memcpy(&plan,&saved,sizeof(plan));
copy_context(&candidate,&entry);plan.output=NAN;negative_case("late_nonfinite_output",NONFINITE,inputs);memcpy(&plan,&saved,sizeof(plan));
copy_context(&candidate,&entry);plan.jac++;negative_case("expired_record",NO_RECORD,inputs);memcpy(&plan,&saved,sizeof(plan));
SREnvironment env;save_environment(&env);
if(plan.required_flags){copy_context(&candidate,&entry);feclearexcept(FE_ALL_EXCEPT);negative_case("uncovered_fenv",UNKNOWN_EFFECT,inputs);restore_environment(&env);}
copy_context(&candidate,&entry);fesetround(plan.rounding==FE_DOWNWARD?FE_UPWARD:FE_DOWNWARD);negative_case("rounding_mode",UNKNOWN_EFFECT,inputs);restore_environment(&env);
#if defined(__SSE__)
copy_context(&candidate,&entry);_mm_setcsr(env.sse^0x2000u);negative_case("sse_only_rounding_mode",UNKNOWN_EFFECT,inputs);restore_environment(&env);
#endif
}
void ax_finish(void){
initialize();FILE *f=fopen("shadow-summary.json","wb");if(!f)fatal("summary log");
fprintf(f,"{\"position\":%d,\"total\":%llu,\"accepted\":%llu,\"rejected\":%llu,\"mismatches\":%llu,\"countDifferent\":%llu,\"slotRelocationTrials\":%llu,\"relocatedSlots\":%llu,\"appends\":%llu,\"ptHit\":%llu,\"ptMiss\":%llu,\"phHit\":%llu,\"phMiss\":%llu,\"paths\":[%llu,%llu,%llu,%llu],\"liveContamination\":%llu,\"rejectRollbackChecks\":%llu,\"tailEqual\":%llu,\"liveTailEqual\":%llu,\"memoImmutable\":%llu,\"negativePassed\":%llu,\"forbiddenNativeCalls\":%llu,\"eventBytes\":%llu,\"eventMax\":%llu,\"metadataMin\":%llu,\"metadataMax\":%llu,\"metadataReserved\":%llu,\"contextCopyBytes\":%llu,\"frameCopyBytes\":%llu,\"rejectReasons\":{",configured,stats.total,stats.accepted,stats.rejected,stats.mismatches,stats.count_different,stats.relocated,stats.relocated_slots,stats.appends,stats.pt_hit,stats.pt_miss,stats.ph_hit,stats.ph_miss,stats.paths[0],stats.paths[1],stats.paths[2],stats.paths[3],stats.live_contamination,stats.rollbacks,stats.tail_equal,stats.live_tail_equal,stats.memo_immutable,stats.negative_passed,forbidden_native_calls,(unsigned long long)sizeof(Event),(unsigned long long)stats.event_max,(unsigned long long)stats.metadata_min,(unsigned long long)stats.metadata_max,(unsigned long long)sizeof(Plan),(unsigned long long)sizeof(SRContext),(unsigned long long)sizeof(SRFrame));
for(int i=1;i<NREASONS;i++)fprintf(f,"%s\"%s\":%llu",i>1?",":"",reasons[i],stats.rejects[i]);
fprintf(f,"},\"untouchedProbeEqual\":%llu,\"metadataImmutable\":%llu}\n",stats.untouched_equal,stats.metadata_immutable);fclose(f);fclose(trials);fclose(negative);
}
+26
View File
@@ -0,0 +1,26 @@
#ifndef CONTEXT_SHADOW_REPLAY_H
#define CONTEXT_SHADOW_REPLAY_H
#include "model.h"
#define SR_STATES 256
#define SR_PIPES 40
#define SR_EVENTS 512
typedef struct {
NativePropertyCache context;
NativePropertyState states[SR_STATES];
NativePipeCache pipes[SR_PIPES];
NativeJacobianScalars memo;
NativeJacobianScalarEntry entries[MODEL_JACOBIAN_SCALAR_COUNT];
NativePropertyTemperatures observer;
} SRContext;
typedef struct {
double p[48],h[232],q[232],fb[152],w[NOUTPUTS],dy[NSTATES];
NativeGas g[56];
} SRFrame;
typedef int (*SRTail)(double,const double*,SRContext*,SRFrame*);
typedef double (*SROperation)(NativePropertyCache*,NativePipeCache*,const double*);
void sr_shadow(int,double,const double*,NativePropertyCache*,NativePipeCache*,
const double*,const double*,const double*,const double*,const NativeGas*,const double*,SROperation,SRTail);
void sr_eval_result(int,const double*,const double*);
void sr_native_enter(const char*);
void sr_or(const void*,unsigned,const char*,const char*);
#endif
+145
View File
@@ -0,0 +1,145 @@
# 最小 context shadow replay 验证结果
## 1. Candidate 能否逐位复现 Reference
**可以,在本轮两个 operation 的已覆盖路径和严格前置条件下,1,792 次 shadow replay 全部通过,出口不一致为 0。** R288 先完成全部 896 次;同一 worker 二进制通过该阶段后,才运行 position 52 的 896 次。
参考答案始终是**同一个当前 probe 入口的独立深拷贝,真实执行原 operation 后的出口**。没有把 baseline 出口当参考。baseline 只提供本 Jacobian 内的局部语义记录,不提供用于恢复的完整 context。
| 统计 | group 6 / R288 / position 16 | group 18 / R475 / position 52 |
|---|---:|---:|
| shadow 总次数 | 896 | 896 |
| 可重放 | 896 | 896 |
| 自然轨迹 reject | 0 | 0 |
| Reference/Candidate 不一致 | 0 | 0 |
| 入口 count 不同 | 638 | 24 |
| 发生 slot relocation 的 probe | 571 | 23 |
| 重定位逻辑 slot 数量 | 1142 | 45 |
| 追加条目总数 | 1658 | 1789 |
| PT hit | 0 | 168 |
| PT miss | 1658 | 1789 |
| PH hit | 0 | 1 |
| PH miss | 0 | 167 |
| 独立后续 evaluator 返回值/dy/w/context 一致 | 896 | 896 |
| 后续 evaluator 与主仿真返回值/dy/w 一致 | 896 | 896 |
| memo 条目只读验证 | 896 | 896 |
| 未写入的 probe 数据保持不变 | 896 | 896 |
| 局部 metadata 未被 Reference/Candidate 改写 | 896 | 896 |
| Candidate 物理 native 调用 | 0 | 0 |
| 主 context/浮点环境污染 | 0 | 0 |
### 如何保证比较有意义
1. 在目标 probe 的 operation 前,深拷贝 property states、全部 pipe 槽、scalar memo 的全部 entries 和计数,分别绑定到 Reference/Candidate 私有存储。两路没有共享可写 context。
2. Reference 调用该位置的原始表达式。Candidate 只解释 baseline 捕获的 query/read/valid/allocate/write/OR/scalar-get 语义;物理 native 入口有运行时禁入检查。Reference 执行期间不能补写 Candidate 的记录。
3. Candidate 从自己的 probe 副本建立 overlay。重新扫描当前有序 entries,采用原 PT/PH 的 exact `==` 和 medium 比较语义找 first-match;用逻辑 ID 映射结果,追加使用当前 count。
4. 所有条件成功后进入无失败分支的 commit,仅按记录顺序更新实际写入字段、valid OR、count、指定 pipe 槽、memo 命中计数及显式输出;不复制 baseline context,也不覆盖 probe 未写字段。reject 前没有向 Candidate 或输出提交任何存储。
5. 逐位比较输出、所有 active property 字段、valid bits、全部 pipe 字段、memo entries/计数、warning observer、errno 和 x87/SSE 环境。私有指针按“绑定到各自当前 owner”检查;不要求两个独立 allocation 的地址相等。数值字段没有容差或近似比较。
6. 两个独立出口继续执行同一份原 evaluator 后续代码,实测返回状态、完整 dy/w、context 和 memo 计数一致;再与主仿真的真实 evaluator 返回值/dy/w 核对。主仿真仍然执行原 operation,没有采用 Candidate 输出,没有进入真实 skip 路径。
7. 两轮主仿真的状态、输出、事件及全部 896 个 132×132 Jacobian 仍与未插桩基线逐字节一致。whole-context guard 源码哈希保持一致。
operation 本身返回一个 double,没有单独的 int 成功码;报告中的 evaluator 状态来自两条后续执行路径,不是用 `isfinite(output)` 代替。
### 两个具体入口例子(Jacobian 200)
| 项目 | R288 | position 52 |
|---|---|---|
| baseline count | `12` | `75` |
| 当前 probe count | `13` | `75` |
| Reference 出口 count | `15` | `77` |
| Candidate 出口 count | `15` | `77` |
| 逻辑 slot → 当前 slot | `[[12, 13], [13, 14]]` | `[[75, 75], [76, 76]]` |
R288 保留 probe 原 slot 12,在 13、14 追加;position 52 保留 probe slot 74 的压力/温度差异,查询重新扫描后仍 miss,再在 75、76 追加。
## 2. 哪些路径已经可以 replay
| 查询路径 | R288 次数 | position 52 次数 |
|---|---:|---:|
| 近零流量,无物性查询 | 67 | 1 |
| PT miss → PT miss | 829 | 727 |
| PH miss → PT miss → PT hit → PT miss | 0 | 167 |
| PH hit → PT hit → PT miss | 0 | 1 |
以上“可 replay”指本轮相应 operation 的完整 0–10 s 轨迹,且所有 runtime guard 同时成立。PH miss 的 h 登记与 valid OR、后续对刚追加逻辑条目的 PT hit 都保留。近零流量不追加物性条目,但仍执行 pipe 的有序写入,包括 valid=0 的同值写入。
R288 的 638 次 count 差异中,67 次属于无查询/无追加路径,因此只有 571 次发生实际 slot relocation。position 52 同理,24 次 count 差异中有一次无追加。不能把 count 差异次数当作重定位次数。
## 3. 哪些情况仍必须 reject / fallback
自然轨迹中各 reject 分类均为 0。为防止“全成功但拒绝机制无效”,另在私有副本中执行下列负例,要求 reject 且整个 Candidate 与输出 sentinel 逐字节不变;这些不计入自然轨迹的 896 次。
| reject 分类 | R288 负例通过次数 | position 52 负例通过次数 |
|---|---:|---:|
| `capacity_scratch` | 2 | 3 |
| `consumed_field` | 1 | 3 |
| `existing_entry_update` | 1 | 2 |
| `first_match_relation` | 1 | 2 |
| `inputs` | 1 | 2 |
| `memo_lifetime_binding` | 1 | 2 |
| `memo_miss` | 1 | 2 |
| `memo_value` | 1 | 2 |
| `no_current_record` | 1 | 2 |
| `nonfinite_or_uncovered_branch` | 1 | 2 |
| `observer_nonnull` | 1 | 2 |
| `pipe_hit_branch` | 1 | 2 |
| `query_hit_miss_path` | 1 | 2 |
| `unknown_effect_or_schema` | 4 | 8 |
| `valid_test` | 1 | 3 |
严格拒绝边界:
- 记录不属于当前 Jacobian/operation、源代码不再匹配已验证 worker、记录溢出或未知语义事件。
- 输入改变、first-match 逻辑映射冲突、hit/miss 路径改变、已消费字段或 valid 掩码结果不一致。
- 容量不足或 scratch、非空 observer、未覆盖的已有 entry 原地更新、pipe 命中分支变化、非有限结果或未覆盖路径。
- memo owner/lifetime/recording 不符合只读约束、所需 scalar key 未命中或值不匹配。Candidate 不调用物理 fallback 来弥补 memo miss。
- 舍入模式、SSE 控制模式或所需异常状态不满足已验证条件;已记录的非零 errno 前置条件不成立。
负例包括第一次追加完成后第二次容量检查失败、末尾未知事件、末尾非有限输出,因此覆盖了 overlay 已发生大量修改后的回滚,不只是入口早退。position 52 的 PH-hit 样本还直接改变 probe 已有条目的 rho 和 MU 位,确认消费值/valid guard 生效。
Jacobian memo 的 entries 始终只读,但 Reference 的 scalar get 会增加 reuses 计数。Candidate 在 overlay 中验证同一 bit-key 查找结果,并在 commit 中重放对应计数增量。没有将 baseline 的 recording/put 副作用照搬到 probe。
验证期间发现并修复了 Windows 诊断隔离问题:该工具链的 `fesetenv` 不完整恢复 SSE 控制寄存器。负例现在保存/恢复完整 x87/SSE 环境,并验证 SSE 单独改变时会拒绝。修复前失败证据保存在 `test/context-shadow-20260917/env-restore-investigation/`;本报告仅使用修复后的同一构建全量重跑结果。
## 4. Runtime metadata 需要多少
| 当前实现 | R288 | position 52 |
|---|---:|---:|
| 单条语义事件 | 176 B | 176 B |
| 最大事件数 | 132 | 138 |
| 每条 operation 记录实际使用范围 | 2224–23344 B | 2224–24400 B |
| 每阶段为 512 个事件预留 | 90224 B | 90224 B |
记录包括:Jacobian/operation 身份、四个显式输入、出口值、query 完整 key/逻辑命中关系、实际消费的字节值与 valid 掩码、创建顺序、字段更新/OR、scalar key/value、memo 绑定及环境前置条件。**不保存 baseline 完整 property/pipe context 用于 replay。** 只保留当前 Jacobian 的一条目标 operation 记录。
事务 scratch 另需一个 156816 B 的 overlay;本诊断为入口、Reference、Candidate、回滚检查及双路后续比较共保留 7 个 context 副本、2 个 22880 B frame。它们是 shadow 验证工作内存,不能算作未来 skip 每条记录都必须长期保存的 metadata。slot 映射临时表为 256 个 int(当前 ABI 1,024 B)。
这是当前保守事件表示的实测大小,未做去重或压缩,也没有据此评价性能。
## 5. 是否具备最小真实 skip 实验的条件
**已具备针对这两个单独 operation、上述四种已验证路径的下一阶段实验条件。** 下一步必须继续保持严格 guard、事务 commit、reject 后原执行以及独立 Reference 抽查/全量对比;先对 R288 单点实验,再单独考虑 position 52。
本轮没有实现真实 skip,也没有移除或放宽 whole-context guard。结论不适用于整个 R475、全部 reuse interval、新模型、其他 native kernel、容量耗尽/observer 非空/已有 entry 原地更新等未验证分支;Linux 尚未运行本实验。双路执行和详细检查的时长不作性能证据。
## 复现与证据
依赖上一轮保留的 access worker 和未插桩 baseline 文件。准备阶段核对 access worker 源码哈希;第二阶段核对第一阶段通过的 worker 二进制 SHA-256。
```powershell
.venv-win\Scripts\python.exe tests/manual/diagnose_context_shadow.py prepare
.venv-win\Scripts\python.exe tests/manual/diagnose_context_shadow.py run --position 16
.venv-win\Scripts\python.exe tests/manual/diagnose_context_shadow.py run --position 52
.venv-win\Scripts\python.exe tests/manual/analyze_context_shadow.py
```
生成文件位于 `test/context-shadow-20260917/`:`worker/build.json`、各阶段 `shadow-trials.jsonl`、`shadow-summary.json`、`shadow-negative.jsonl`、`measurement.json`,以及汇总的 `validation.json`。若出现不一致,会输出首个不同字段和完整入口/两路 context、frame、metadata 到 `first-mismatch.json`。最终两阶段均未生成该文件。
最终 worker SHA-256:`0491872485d3d97d6b79da556ea4de53b77e6daed9100112f1e1179a6dd3d18f`。
全量主轨迹哈希(两阶段相同):
- states: `100864d208ce5cd86d4728aa9c1a343b6252b057266c8ccb7010c7fb0bc14317`
- outputs: `46fdbd10844d72e4fd23debe3439fd8eb736567f48713950b1bb5faadbdb1ff8`
- events: `4a068d192278eb91bb78782cdcc7d635deaef8dabd074ddbde3edc1d5d080b7d`
- jacobians: `74d559c39973a609884c6bc9274ce1185d23e163b33d60443108c4519fedb448`
+134
View File
@@ -0,0 +1,134 @@
"""Independent, opt-in access recorder for positions 16 and 52 only.
Uses the existing fallback diagnostic worker as its source; never edits the
production kernels or changes the local-probe guard/replay policy.
"""
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
import argparse, hashlib, json, re, shutil, time
import diagnose_context_fallback as dx
ROOT = dx.ROOT
HERE = Path(__file__).parent
BASE = ROOT / 'test/context-fallback-20260917/worker'
OUT = ROOT / 'test/context-access-20260917'
replace = dx.replace
def instrument(body):
"""Wrap reviewed lvalues first, then actual rvalues; keep short circuits."""
saved = []
def hold(text):
saved.append(text)
return f'AXHOLD{len(saved)-1}ZZ'
field = r'(?:s|up|down|cache|m|a|b)->\w+'
def reads(text):
# An address expression is not a value read (e.g. &s->rho).
text = re.sub(r'(?<![\w&])' + field, lambda m: 'AX_R(' + m[0] + ')', text)
# Taking &states[i] still consumes the states base pointer.
text = text.replace('&cache->states[', '&AX_R(cache->states)[')
return 'AX_R(*m)' if text.strip() == '*m' else text
body = re.sub(r'([su]\w*->valid)&(NATIVE_PROPERTY_\w+)',
lambda m: hold(f'ax_test({m[1].split("->")[0]},{m[2]},__func__)'), body)
body = body.replace('cache->count++', hold('AX_INC(cache->count)'))
# Struct reset is an explicit write, including equal-valued fields.
body = body.replace('*s=(NativePropertyState){0};', hold('AX_W(*s,((NativePropertyState){0}));'))
def write(m):
lhs, operator, rhs = m.groups()
return hold(('AX_OR' if operator == '|=' else 'AX_W') + f'({lhs},{reads(rhs)});')
body = re.sub(r'(' + field + r')\s*(\|=|=(?!=))\s*([^;]+);', write, body)
body = reads(body)
for i, value in enumerate(saved):
body = body.replace(f'AXHOLD{i}ZZ', value)
# Fail closed if a field write evaded the transformation.
assert not re.search(field + r'\s*(?:\|=|=(?!=)|\+\+)', body), body
return body
def change_function(source, name, transform):
a, b, e = dx.ex.function_span(source, name)
return source[:b+1] + transform(source[b+1:e-1]) + source[e-1:]
def prepare():
OUT.mkdir(exist_ok=True)
work = OUT / 'worker'
work.mkdir(exist_ok=True)
sources = {p.name: p.read_text(encoding='utf-8') for p in BASE.glob('*.c')}
assert sources, 'First prepare the existing context-fallback diagnostic worker.'
before = {k: hashlib.sha256(v.encode()).hexdigest() for k, v in sources.items()}
s = sources['properties.c']
def pt(body):
body = 'ax_query("PT",m,p,T);\n' + body
body = replace(body, 'same_medium(&s->medium,m))return s;', 'same_medium(&s->medium,m)){ax_match("PT",s);return s;}')
body = replace(body, 'return property_new(cache,m,p,T,scratch);', 'ax_match("PT",NULL);return property_new(cache,m,p,T,scratch);')
return body
s = change_function(s, 'property_pt', pt)
def ph(body):
body = 'ax_query("PH",m,p,h);\n' + body
body = replace(body, 'observe_temperature(cache->temperatures,m,s->T,3);return s->T;', 'ax_match("PH",s);observe_temperature(cache->temperatures,m,s->T,3);return s->T;')
return replace(body, 'double T;', 'ax_match("PH",NULL);double T;')
s = change_function(s, 'native_temperature_ph_context', ph)
s = change_function(s, 'property_new', lambda body: replace(body, '*s=(NativePropertyState){0};', 'ax_new(cache,s,valid);*s=(NativePropertyState){0};'))
functions = ['same_medium', 'property_new', 'property_pt', 'property_density', 'property_viscosity',
'native_temperature_ph_context', 'local_isentropic', 'isentropic', 'state_valve',
'native_density', 'native_temperature_ph', 'native_viscosity']
for name in functions:
s = change_function(s, name, instrument)
# Scalar get can write directly into s->rho through an output pointer.
s = change_function(s, 'native_jacobian_scalar_get', lambda body: replace(
replace(body, '*value=entry->value;', 'AX_W(*value,entry->value);ax_scalar("get",cache,kind,medium_kind,inputs,count,1,value);'),
'return 0;', 'ax_scalar("get",cache,kind,medium_kind,inputs,count,0,NULL);return 0;'))
s = change_function(s, 'native_jacobian_scalar_put', lambda body:
'ax_scalar("put_attempt",cache,kind,medium_kind,inputs,count,-1,&value);\n' + body)
sources['properties.c'] = s
for name in ['native_pipe_flow_context', 'native_pipe_flow_cached_context']:
sources['pipe.c'] = change_function(sources['pipe.c'], name, instrument)
s = sources['local_probe_support.c']
s = replace(s, 'dx_region=-1;dx_boundary(0,-2,p,pipes);', 'ax_bind(p,pipes);dx_region=-1;dx_boundary(0,-2,p,pipes);')
s = replace(s, 'void dx_op_begin(int pos,const double *inputs){',
'void dx_op_begin(int pos,const double *inputs){ax_begin(dx_jac-1,lp_color,pos,dx_time,inputs,dx_in_offset[pos+1]-dx_in_offset[pos]);')
s = replace(s, 'void dx_op_end(int pos,const double *outputs,NativePropertyCache *p,NativePipeCache *pipes){',
'void dx_op_end(int pos,const double *outputs,NativePropertyCache *p,NativePipeCache *pipes){ax_end(outputs,dx_out_offset[pos+1]-dx_out_offset[pos]);')
s = replace(s, 'void dx_finish(void){', 'void dx_finish(void){ax_finish();')
sources['local_probe_support.c'] = s
sources['model.c'] = replace(sources['model.c'], 'lp_active=-1;dx_eval_end();return result;', 'lp_active=-1;ax_result(result);dx_eval_end();return result;')
sources['context_access_diag.c'] = (HERE/'context_access_diag.c').read_text(encoding='utf-8')
for path in BASE.glob('*.h'):
shutil.copyfile(path, work/path.name)
shutil.copyfile(HERE/'context_access_diag.h', work/'context_access_diag.h')
cc, sun, _ = dx.ex.builder.toolchain()
flags, libs, dlls, exe = dx.ex.builder.platform_build_inputs(sun)
flags += ['-DLP_OBSERVE=0']
started = time.perf_counter()
def compile_one(item):
name, code = item
path = work/name
if '#define _WIN32_WINNT 0x0600' in code:
code = code.replace('#define _WIN32_WINNT 0x0600', '#define _WIN32_WINNT 0x0600\n#include "context_access_diag.h"', 1)
else:
code = '#include "context_access_diag.h"\n' + code
path.write_text(code, encoding='utf-8', newline='\n')
obj = path.with_suffix('.o'); log = []
dx.ex.builder._command([cc, *flags, '-I', str(work), '-I', str(dx.ex.builder.NATIVE/'include'), '-I', str(sun/'include'), '-c', str(path), '-o', str(obj)], log=log, timeout=240)
return obj, log
with ThreadPoolExecutor(max_workers=4) as pool:
objects = list(pool.map(compile_one, sources.items()))
log = []
dx.ex.builder._command([cc, *flags, *[str(o) for o, _ in objects], *dx.ex.builder.link_library_arguments(libs), '-lm', '-o', str(work/exe)], log=log)
for dll in dlls:
shutil.copyfile(dll, work/dll.name)
(work/'build.log').write_text('\n'.join(sum([v for _, v in objects], [])+log), encoding='utf-8')
dx.ex.write(work/'build.json', dict(sourceHashes=before, instrumentedHashes={p.name:hashlib.sha256(p.read_bytes()).hexdigest() for p in work.glob('*.c')}, seconds=time.perf_counter()-started, functions=functions))
print('BUILT access worker', flush=True)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('action', choices=['prepare', 'run'])
args = parser.parse_args()
if args.action == 'prepare':
prepare()
else:
dx.OUT = OUT
dx.run('audit', mode=1, matrices=True)
+126
View File
@@ -0,0 +1,126 @@
"""Build isolated context-fallback diagnostic workers; no implementation edits."""
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor
import argparse,hashlib,json,os,re,shutil,subprocess,time
import local_probe_experiment as ex
ROOT=ex.ROOT;SOURCE=ROOT/'test/local-probe-20260917/worker';OUT=ROOT/'test/context-fallback-20260917';HERE=Path(__file__).parent
KERNELS=[('properties','property_pt'),('properties','property_density'),('properties','property_viscosity'),
('properties','native_temperature_ph_context'),('properties','local_isentropic'),('properties','state_valve'),
('properties','native_jacobian_scalar_get'),('properties','native_temperature_ph'),('properties','native_density'),('properties','native_viscosity'),
('pipe','native_pipe_flow_cached_context'),('pipe','native_pipe_flow_context'),('pipe','native_pipe_resistance'),
('orifice','native_medium_orifice_context')]
replace=ex.replace
def function(s,name):a,b,e=ex.function_span(s,name);return s[a:e]
def vec(refs):return '(double[]){'+(','.join(refs) or '0')+'}'
def generate_model(source,ops,meta):
original=function(source,'model_eval_local_internal');start=original.index('if(lp_capture){');end=original.index('double node_energy[')
checkpoints=meta['contextCheckpointSlots'];capture=[]
for pos,op in enumerate(ops):
if str(pos) in checkpoints:capture.append(f'lp_snapshot({checkpoints[str(pos)]},properties,pipe_cache);')
capture += [f'dx_op_begin({pos},{vec(sorted(op.inputs))});',*op.code,f'dx_op_end({pos},{vec(op.outputs)},properties,pipe_cache);']
capture += [f'lp_snapshot({checkpoints[str(len(ops))]},properties,pipe_cache);','lp_save(p,h,q,w,fb);']
# Use the original capture branch in time-only runs; it is not profiled.
orig_capture=original[start+len('if(lp_capture){'):original.index('}else{',start)]
schedule=['dx_schedule(properties,pipe_cache);','if(lp_capture){','if(dx_mode==1 || dx_mode==5){',*capture,'}else{',orig_capture,'}','}else{','for(int pos=0;pos<LP_NO;){',
'int region=lp_plan[lp_color][pos];',
'if(region>=0 && dx_reuse(region,properties,pipe_cache,p,h,q,w,fb)){pos=lp_end[region];continue;}',
'switch(pos){']
for pos,op in enumerate(ops):
schedule += [f'case {pos}:{{',f'dx_op_begin({pos},(dx_mode==1 || dx_mode==5)?{vec(sorted(op.inputs))}:NULL);',*op.code,
f'dx_op_end({pos},(dx_mode==1 || dx_mode==5)?{vec(op.outputs)}:NULL,properties,pipe_cache);','break;}']
schedule += ['default:return 0;}','pos++;','if(dx_region>=0 && pos==lp_end[dx_region])dx_region_end(p,h,q,w,fb,properties,pipe_cache);','}}']
clone=(original[:start]+'\n'.join(schedule)+'\ndx_position=-1;\n'+original[end:]).replace('model_eval_local_internal(', 'dx_model_eval_local_internal(',1)
wrapper=function(source,'lp_eval').replace('int lp_eval(', 'int dx_eval(',1).replace('model_eval_local_internal(', 'dx_model_eval_local_internal(')
a=wrapper.index('{')+1;wrapper=wrapper[:a]+'\nif(!dx_selected)return lp_eval(t,y,dy,w,workspace);\ndx_eval_begin(t,y);\n'+wrapper[a:]
wrapper=replace(wrapper,'lp_active=-1;return result;','lp_active=-1;dx_eval_end();return result;')
return source+'\n'+clone+'\n'+wrapper
def kernel_wrapper(source,name,index):
a,b,e=ex.function_span(source,name);sig=source[a:b].strip();body=source[a:e]
args=sig[sig.index('(')+1:sig.rindex(')')]
params=[re.search(r'([A-Za-z_]\w*)\s*(?:\[[^]]*\])?$',x.strip())[1] for x in args.split(',')]
prefix=sig[:sig.index(name)].strip();typ=re.sub(r'^(?:static|NATIVE_COMPONENT_INTERNAL)\s+','',prefix).strip()
impl=body.replace(name+'(', 'dx_impl_'+name+'(',1)
call='dx_impl_'+name+'('+','.join(params)+')'
action=(call+';dx_kernel_end('+str(index)+',ticket);') if typ=='void' else (typ+' result='+call+';dx_kernel_end('+str(index)+',ticket);return result;')
wrapper=sig+'{uint64_t ticket=dx_kernel_begin('+str(index)+');'+action+'}'
# Forward declaration preserves recursive calls and cross-calls.
return source[:a]+sig+';\n'+impl+'\n'+wrapper+source[e:]
def prepare(kernels=False,trace=False):
OUT.mkdir(exist_ok=True);work=OUT/('trace-worker' if trace else 'kernels' if kernels else 'worker');work.mkdir(exist_ok=True)
program,saved=ex.capture(ROOT/'tests/data/test-mql-8-corrected.json')
assert program.source==(SOURCE.parent/'original-model.c').read_text(encoding='utf-8')
schedule=saved['schedule'];ops=[schedule.computations[b.members[0]] for b in schedule.blocks]
meta=json.loads((SOURCE.parent/'plan.json').read_text(encoding='utf-8'))
versions=[0];pure={'if','for','sizeof','fmax','fmin','fabs','sqrt','copysign','pow'}
for op in ops:versions.append(versions[-1]+int(bool(set(re.findall(r'\b([A-Za-z_]\w*)\s*\(', '\n'.join(op.code)))-pure)))
assert all(versions[int(pos)]==slot for pos,slot in meta['contextCheckpointSlots'].items())
ins=[0];outs=[0]
for op in ops:ins.append(ins[-1]+len(op.inputs));outs.append(outs[-1]+len(op.outputs))
arrays={'dx_start_pos':[a for a,b in meta['regions']],'dx_contextual':[int(versions[a]!=versions[b]) for a,b in meta['regions']],
'dx_version':versions,'dx_mutates':[versions[i+1]!=versions[i] for i in range(len(ops))],'dx_in_offset':ins,'dx_out_offset':outs}
tables='\n'.join('const int '+name+'[]={'+','.join(str(int(v)) for v in values)+'};' for name,values in arrays.items())
tables+=f'\n#define DX_NIN {ins[-1]}\n#define DX_NOUT {outs[-1]}\n'
sources={p.name:p.read_text(encoding='utf-8') for p in SOURCE.glob('*.c')};before={n:hashlib.sha256(s.encode()).hexdigest() for n,s in sources.items()}
sources['model.c']=generate_model(sources['model.c'],ops,meta)
sources['local_probe_support.c']+='\n'+(HERE/'context_fallback_diag.c').read_text(encoding='utf-8').replace('/* DIAG_TABLES */',tables)
sources['common.c']=replace(sources['common.c'],'lp_start();','lp_start();dx_initialize();')
sources['common.c']=replace(sources['common.c'],'lp_finish();','lp_finish();dx_finish();')
sources['cvode_solver.c']=replace(sources['cvode_solver.c'],'lp_eval(t,N_VGetArrayPointer(y),N_VGetArrayPointer(f),outputs,workspace)','dx_eval(t,N_VGetArrayPointer(y),N_VGetArrayPointer(f),outputs,workspace)')
sources['cvode_solver.c']=replace(sources['cvode_solver.c'],'{lp_color=-1;uint64_t start=lp_tick();','{lp_color=-1;dx_jacobian();uint64_t start=lp_tick();')
sources['cvode_solver.c']=replace(sources['cvode_solver.c'],'if(!result)lp_matrix(t,N_VGetArrayPointer(y),SUNDenseMatrix_Data(matrix));',
'if(!result){lp_matrix(t,N_VGetArrayPointer(y),SUNDenseMatrix_Data(matrix));dx_validate_matrix(t,N_VGetArrayPointer(y),SUNDenseMatrix_Data(matrix));}')
if kernels:
for i,(module,name) in enumerate(KERNELS):sources[module+'.c']=kernel_wrapper(sources[module+'.c'],name,i)
if trace:
a,b,e=ex.function_span(sources['properties.c'],'property_new')
fn=sources['properties.c'][a:e];fn=replace(fn,'return s;','dx_property_created(cache,s);return s;')
sources['properties.c']=sources['properties.c'][:a]+fn+sources['properties.c'][e:]
for name in ('model.h','local_probe.h'):shutil.copyfile(SOURCE/name,work/name)
(work/'context_fallback_diag.h').write_text((HERE/'context_fallback_diag.h').read_text(encoding='utf-8').replace('#include "local_probe.h"','#include "local_probe.h"\n#define DX_NK '+str(len(KERNELS))),encoding='utf-8')
cc,sun,_=ex.builder.toolchain();flags,libs,dlls,exe=ex.builder.platform_build_inputs(sun);flags+=['-DLP_OBSERVE=0'];started=time.perf_counter()
def compile_one(item):
i,(name,s)=item;path=work/name;path.write_text('#include "context_fallback_diag.h"\n'+s,encoding='utf-8',newline='\n');obj=work/f'diag-{i}.o';log=[]
ex.builder._command([cc,*flags,'-I',str(work),'-I',str(ex.builder.NATIVE/'include'),'-I',str(sun/'include'),'-c',str(path),'-o',str(obj)],log=log,timeout=240)
return obj,log
with ThreadPoolExecutor(max_workers=4) as pool:objs=list(pool.map(compile_one,enumerate(sources.items())))
log=[];ex.builder._command([cc,*flags,*[str(o) for o,_ in objs],*ex.builder.link_library_arguments(libs),'-lm','-o',str(work/exe)],log=log)
for dll in dlls:shutil.copyfile(dll,work/dll.name)
(work/'build.log').write_text('\n'.join(sum([v for _,v in objs],[])+log),encoding='utf-8')
ex.write(work/'build.json',dict(sourceHashes=before,seconds=time.perf_counter()-started,kernels=kernels))
ex.write(OUT/'plan.json',dict(**meta,versions=versions,kernels=KERNELS,code=[list(o.code) for o in ops],inputs=[sorted(o.inputs) for o in ops]))
print('BUILT',work.name,time.perf_counter()-started,flush=True)
def run(label,mode=1,stride=16,seed=1,kernels=False,matrices=False,control=False,trace=False):
work=OUT/label;work.mkdir(exist_ok=True);exe=(SOURCE if control else OUT/('trace-worker' if trace else 'kernels' if kernels else 'worker'))/'model.exe'
env=os.environ.copy();env.update(LOCAL_PROBE_MASK='0x7ffffff',CONTEXT_DIAG_MODE=str(mode),CONTEXT_DIAG_STRIDE=str(stride),CONTEXT_DIAG_SEED=str(seed),CONTEXT_DIAG_MATRICES=str(int(matrices)))
args=[str(exe),'--method','BDF','--start','0','--stop','10','--sample-step','.01','--max-step','1e30','--rtol','1e-8','--timeout','300',
'--sample-file',str(work/'states.bin'),'--output-block-file',str(work/'outputs.bin'),'--output',str(work/'result.json')]
start=time.perf_counter()
with (work/'stderr.log').open('wb') as f:p=subprocess.run(args,cwd=work,env=env,stdout=subprocess.PIPE,stderr=f,timeout=330,creationflags=subprocess.CREATE_NO_WINDOW)
elapsed=time.perf_counter()-start
if p.returncode:raise RuntimeError((label,p.returncode,(work/'stderr.log').read_text()[-5000:]))
result=json.loads((work/'result.json').read_text());diag=json.loads((work/'probe.json').read_text());ref=json.loads((SOURCE.parent/'all-run-0/measurement.json').read_text(encoding='utf-8'))
keys=['success','finalState','final','propertyWarnings','acceptedSteps','rejectedSteps','stateTransitions','solverStarts','nfev','njev','nlu']
assert all(result[k]==ref[k] for k in keys),(label,'result differs')
assert all(diag[k]==ref['diagnostic'][k] for k in ['newtonIterations','newtonConvergenceFailures','modelCalls','groups','contextCopiedBytes','contextComparedBytes']),(label,'counters differ')
hashes={}
for name in ('states','outputs','events','jacobians'):
p=work/(name+'.bin')
if p.exists():
with p.open('rb') as f:hashes[name]=hashlib.file_digest(f,'sha256').hexdigest()
if name=='jacobians':
with (SOURCE.parent/'all-audit/jacobians.bin').open('rb') as f:assert hashes[name]==hashlib.file_digest(f,'sha256').hexdigest()
else:assert hashes[name]==ref[name+'Sha256'],(label,name)
record=dict(label=label,mode=mode,stride=stride,seed=seed,kernels=kernels,control=control,processSeconds=elapsed,
solveSeconds=result['solveSeconds'],solveCpuSeconds=result['solveCpuSeconds'],jacobianSeconds=diag['jacobianSeconds'],hashes=hashes)
ex.write(work/'measurement.json',record);print('RUN',label,'Jac',diag['jacobianSeconds'],'solve',result['solveSeconds'],'exact OK',flush=True)
return record
if __name__=='__main__':
p=argparse.ArgumentParser();p.add_argument('action',choices=['prepare','run']);p.add_argument('--kernels',action='store_true');p.add_argument('--matrices',action='store_true');p.add_argument('--control',action='store_true');p.add_argument('--trace',action='store_true');p.add_argument('--label',default='census');p.add_argument('--mode',type=int,default=1);p.add_argument('--stride',type=int,default=16);p.add_argument('--seed',type=int,default=1);a=p.parse_args()
if a.action=='prepare':prepare(a.kernels,a.trace)
else:run(a.label,a.mode,a.stride,a.seed,a.kernels,a.matrices,a.control,a.trace)
+107
View File
@@ -0,0 +1,107 @@
"""Build/run a shadow-only replay experiment, R288 before position 52.
No Candidate result is used by the live evaluator. Baseline metadata is captured
online; Reference executes on a deep copy of the actual current probe entry.
"""
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
import argparse,hashlib,json,os,shutil,time
import diagnose_context_access as access
ROOT=access.ROOT
HERE=Path(__file__).parent
BASE=ROOT/'test/context-access-20260917/worker'
OUT=ROOT/'test/context-shadow-20260917'
replace=access.replace
def generated_model(source,plan):
a,b,e=access.dx.ex.function_span(source,'dx_model_eval_local_internal')
fn=source[a:e]
tail=fn[fn.index('double node_energy['):]
generated=[]
for pos in (16,52):
operation=plan['code'][pos][0]
expected=(45,0,3,4,64) if pos==16 else (152,28,44,40,178)
qi,pipe,pi,gi,hi=expected
expr=operation.split('=',1)[1].rstrip(';')
for old,new in [(f'p[{pi}]','x[3]'),(f'g[{gi}].p','x[1]'),(f'g[{gi}].T','x[0]'),(f'h[{hi}]','x[2]')]:expr=expr.replace(old,new)
generated.append(f'static double sr_original_{pos}(NativePropertyCache *properties,NativePipeCache *pipe_cache,const double *x){{return {expr};}}')
header=f'''static int sr_tail_{pos}(double t,const double *y,SRContext *ctx,SRFrame *f){{
NativePropertyCache *properties=&ctx->context;NativePipeCache *pipe_cache=ctx->pipes;
double *p=f->p,*h=f->h,*q=f->q,*fb=f->fb,*w=f->w,*dy=f->dy;NativeGas *g=f->g;
(void)t;(void)y;(void)p;(void)h;(void)q;(void)fb;(void)w;(void)dy;(void)g;(void)properties;(void)pipe_cache;
'''
generated.append(header+'\n'.join('\n'.join(c) for c in plan['code'][pos+1:])+'\n'+tail)
hook=f'sr_shadow({pos},t,y,properties,pipe_cache,p,h,q,fb,g,w,sr_original_{pos},sr_tail_{pos});\n'
# Both baseline capture and the live probe switch still execute the
# original assignment. The hook returns void and cannot supply q.
assert fn.count(operation)==3 # detailed baseline, timed baseline, probe
fn=fn.replace(operation,hook+operation)
return source[:a]+'\n'.join(generated)+'\n'+fn+source[e:]
def prepare():
OUT.mkdir(exist_ok=True);work=OUT/'worker';work.mkdir(exist_ok=True)
sources={p.name:p.read_text(encoding='utf-8') for p in BASE.glob('*.c')}
assert sources
before={k:hashlib.sha256(v.encode()).hexdigest() for k,v in sources.items()}
certified=json.loads((BASE/'build.json').read_text(encoding='utf-8'))['instrumentedHashes']
assert before==certified, 'Access-validated source changed; revalidate it before extending the allowlist.'
plan=json.loads((ROOT/'test/context-fallback-20260917/plan.json').read_text(encoding='utf-8'))
sources['model.c']=generated_model(sources['model.c'],plan)
sources['model.c']=replace(sources['model.c'],'ax_result(result);dx_eval_end();','sr_eval_result(result,dy,w);dx_eval_end();')
# Every native entry reachable by either target is guarded during replay.
# Candidate's semantic interpreter has no calls to these physics functions.
native_functions={
'properties.c':['property_pt','property_density','property_viscosity','native_temperature_ph_context','local_isentropic','isentropic','state_valve','native_density','native_temperature_ph','native_viscosity'],
'pipe.c':['native_pipe_flow_cached_context','native_pipe_flow_context','native_pipe_resistance'],
}
for module,names in native_functions.items():
for name in names:sources[module]=access.change_function(sources[module],name,lambda body:'sr_native_enter(__func__);\n'+body)
del sources['context_access_diag.c']
sources['context_shadow_replay.c']=(HERE/'context_shadow_replay.c').read_text(encoding='utf-8')
for p in BASE.glob('*.h'):shutil.copyfile(p,work/p.name)
shutil.copyfile(HERE/'context_shadow_replay.h',work/'context_shadow_replay.h')
header=(work/'context_access_diag.h').read_text(encoding='utf-8')
header=header.replace('#include "kernels.h"','#include "kernels.h"\n#include "context_shadow_replay.h"')
header=replace(header,'(x)|=(v); ax_access("write",&(x),sizeof(x),#x,__func__);','(x)|=(v); sr_or(&(x),(v),#x,__func__);')
(work/'context_access_diag.h').write_text(header,encoding='utf-8')
cc,sun,_=access.dx.ex.builder.toolchain();flags,libs,dlls,exe=access.dx.ex.builder.platform_build_inputs(sun)
flags+=['-DLP_OBSERVE=0'];started=time.perf_counter()
def compile_one(item):
name,code=item;path=work/name;path.write_text(code,encoding='utf-8',newline='\n');obj=path.with_suffix('.o');log=[]
access.dx.ex.builder._command([cc,*flags,'-I',str(work),'-I',str(access.dx.ex.builder.NATIVE/'include'),'-I',str(sun/'include'),'-c',str(path),'-o',str(obj)],log=log,timeout=240)
return obj,log
with ThreadPoolExecutor(max_workers=4) as pool:objects=list(pool.map(compile_one,sources.items()))
log=[];access.dx.ex.builder._command([cc,*flags,*[str(o) for o,_ in objects],*access.dx.ex.builder.link_library_arguments(libs),'-lm','-o',str(work/exe)],log=log)
for dll in dlls:shutil.copyfile(dll,work/dll.name)
(work/'build.log').write_text('\n'.join(sum([v for _,v in objects],[])+log),encoding='utf-8')
guard_before=access.dx.function(sources['local_probe_support.c'],'lp_reuse')
guard_certified=access.dx.function((BASE/'local_probe_support.c').read_text(encoding='utf-8'),'lp_reuse')
assert guard_before==guard_certified
access.dx.ex.write(work/'build.json',dict(sourceHashes=before,instrumentedHashes={p.name:hashlib.sha256(p.read_bytes()).hexdigest() for p in work.glob('*.c')},seconds=time.perf_counter()-started,guardedNativeFunctions=native_functions,wholeContextGuardUnchanged=True))
print('BUILT independent shadow worker',flush=True)
def run(stage):
worker_hash=hashlib.sha256((OUT/'worker/model.exe').read_bytes()).hexdigest()
if stage==52:
gate=json.loads((OUT/'r288/shadow-summary.json').read_text(encoding='utf-8'))
assert gate['total']==896 and gate['accepted']==896 and gate['mismatches']==0 and gate['liveContamination']==0, 'R288 must pass first.'
assert gate['validatedWorkerSha256']==worker_hash, 'R288 must validate this exact worker build first.'
os.environ['CONTEXT_SHADOW_POSITION']=str(stage)
access.dx.OUT=OUT
label='r288' if stage==16 else 'position52'
access.dx.run(label,mode=1,matrices=True)
summary=json.loads((OUT/label/'shadow-summary.json').read_text(encoding='utf-8'))
assert summary['total']==896 and summary['mismatches']==0 and summary['liveContamination']==0
summary['validatedWorkerSha256']=worker_hash
access.dx.ex.write(OUT/label/'shadow-summary.json',summary)
print(json.dumps(summary,indent=2),flush=True)
if __name__=='__main__':
p=argparse.ArgumentParser();p.add_argument('action',choices=['prepare','run']);p.add_argument('--position',type=int,choices=[16,52],default=16);a=p.parse_args()
if a.action=='prepare':prepare()
else:run(a.position)
+328
View File
@@ -0,0 +1,328 @@
"""Verify production LSTP integration and probe force at real Amesim timestamps.
Dense trace sampling operates on an isolated runtime copy, without changing
the step schedule or time-event treatment. It never interpolates across events.
"""
from concurrent.futures import ThreadPoolExecutor
from dataclasses import replace
import argparse
import hashlib
import json
import math
import os
from pathlib import Path
import re
import shutil
import subprocess
import sys
from unittest.mock import patch
import numpy as np
ROOT=Path(__file__).resolve().parents[2]
sys.path.insert(0,str(ROOT))
from app.main import compile_system_xml_network
from app.simulation.backends import simulation_config
from app.simulation.native_codegen import build as builder,result_storage
from app.simulation.native_codegen.compiler import compile_native_program
from app.simulation.native_codegen.input import load_input
from app.simulation.native_codegen.runner import execute_native
from tests.manual import evaluate_mql8_correctness as evaluation,mql8_comparison as curves
PREVIOUS=ROOT/'test/mechanical-events-20260917/baselines'
DEFAULT=ROOT/'test/lstp-mainline-20260917'
save=evaluation.save
def model(profile):
project=ROOT/'test/output-semantics-20260917/after'/profile/'platform.json'
xml,doc=load_input(project)
net=compile_system_xml_network(doc)
return project,xml,doc,net,compile_native_program(net)
def build(program,out,native=None):
with patch.object(builder,'CACHE',out/'cache'),patch.object(builder,'NATIVE',native or ROOT/'native'), \
patch.object(builder,'ThreadPoolExecutor',lambda **kw:ThreadPoolExecutor(max_workers=1)):
return builder.build_native(program)
def baseline(out):
results={}
for profile in ('full','noncyclic'):
directory=out/'baseline'/profile
if (directory/'summary.json').exists():
results[profile]=json.loads((directory/'summary.json').read_bytes());continue
directory.mkdir(parents=True,exist_ok=True)
project,xml,doc,net,program=model(profile)
old=json.loads((PREVIOUS/profile/'lstp/event-descriptors.json').read_bytes())
# The sole generated-source addition is a constant descriptor table.
original=re.sub(r'^const NativeContact model_contacts\[[^\n]+\n','',program.source,flags=re.M)
assert hashlib.sha256(original.encode()).hexdigest()==old['originalModelSourceSha256']
assert program.jacobian_structure==old['jacobianStructure']
(directory/'platform.json').write_bytes(project.read_bytes());(directory/'platform.xml').write_bytes(xml)
reference=directory/'amesim';reference.mkdir(exist_ok=True)
for filename in ('test_mql_.var','test_mql_.results'):
if not (reference/filename).exists():os.link(PREVIOUS/profile/'reference'/filename,reference/filename)
audit=json.loads((PREVIOUS/profile/'audit/audit.json').read_bytes())
print(profile,'production build',flush=True)
compiled=build(program,out)
try:
with patch.object(result_storage,'RESULT_ROOT',out/'results'):
r=execute_native(compiled,replace(simulation_config(doc.simulation),rtol=1e-8),.01,
run_dir=directory/'native',timeout=180)
assert r['success'],r['message']
summary=evaluation.compare(directory,json.loads(project.read_bytes()),net,audit,evaluation.PROFILES[profile])
summary['nativeRun']={k:v for k,v in r.items() if k not in ('series','final','finalState')}
with np.load(directory/'curves.npz') as current,np.load(PREVIOUS/profile/'lstp/curves.npz') as expected:
summary['allComparedArraysEqualIsolatedLstp']=all(np.array_equal(current[k],expected[k],equal_nan=True) for k in current.files)
summary['generatedRhsUnchanged']=True
save(directory/'summary.json',summary);results[profile]=summary
print(profile,'done',summary['allComparedArraysEqualIsolatedLstp'],r['contactEvents'],flush=True)
finally:compiled.close()
save(out/'baseline-summary.json',results)
def trace(out):
project,xml,doc,net,program=model('full')
audit=json.loads((PREVIOUS/'full/audit/audit.json').read_bytes());curves.configure(audit,net)
ame=curves.read_ame(PREVIOUS/'full/reference')
at=np.array(ame.times)
# Query BEFORE and AFTER every forcing boundary, including the exact saved
# Amesim row. These queries never become solver stop times.
queries=set();points=[]
boundaries=evaluation.event_times(json.loads(project.read_bytes()),50)
for boundary in boundaries:
if boundary<=0:continue
ai=int(np.argmin(abs(at-boundary)))
points.append(dict(boundary=boundary,ameIndex=ai,ameTime=float(at[ai])))
queries.update((math.nextafter(boundary,-math.inf),boundary,float(at[ai])))
for delta in (-1e-6,-1e-9,-1e-12,1e-14,1e-13,5e-13,1e-12,2e-12,3e-12,1e-11,1e-10,1e-9,1e-8,1e-7,1e-6,1e-5,1e-4,.001):
queries.add(boundary+delta)
queries=sorted(t for t in queries if 0<t<50)
directory=out/'trace';directory.mkdir(exist_ok=True)
save(directory/'queries.json',dict(points=points,times=queries,stateKeys=program.state_keys))
runtime=directory/'native-source';shutil.copytree(ROOT/'native',runtime,dirs_exist_ok=True)
source=runtime/'runtime/common.c';text=source.read_text(encoding='utf-8')
hook='''
static void force_trace(double t,double end,int event,NativeDense dense,void *context) {
static const double times[]={TIMES};
static size_t cursor=0;
while(cursor<sizeof(times)/sizeof(times[0]) && (event?times[cursor]<end:times[cursor]<=end)) {
double at=times[cursor++],y[NSTATES];
if(at<t || !dense(context,at,y)) continue;
fprintf(stderr,"{\\"phase\\":\\"force-dense-trace\\",\\"time\\":%.17g,\\"state\\":[",at);
for(int i=0;i<NSTATES;i++)fprintf(stderr,"%s%.17g",i?",":"",y[i]);
fprintf(stderr,"]}\\n");
}
}
'''.replace('TIMES',','.join(repr(t) for t in queries))
text=text.replace('int native_accept(',hook+'\nint native_accept(',1)
needle=' for (int i=0;i<count;i++) stop=fmin(stop,when[i]);'
assert text.count(needle)==1
text=text.replace(needle,needle+'\n force_trace(t,stop,count,dense,context);',1)
source.write_text(text,encoding='utf-8')
print('dense trace build',flush=True);compiled=build(program,out,runtime)
try:
r=execute_native(compiled,replace(simulation_config(doc.simulation),rtol=1e-8),.01,
record_samples=False,run_dir=directory/'run',timeout=180)
assert r['success'],r['message']
base=json.loads((out/'baseline/full/summary.json').read_bytes())['nativeRun']
fields=('nfev','acceptedSteps','rejectedSteps','stateTransitions','solverStarts','njev','nlu','contactEvents')
assert all(r[k]==base[k] for k in fields),'Trace changed integration work'
states=[]
for line in (directory/'run/worker.log').read_text(encoding='utf-8').splitlines():
try:e=json.loads(line)
except ValueError:continue
if e.get('phase')=='force-dense-trace':states.append(e)
assert [s['time'] for s in states]==queries
payload='\n'.join(' '.join(format(v,'.17g') for v in (s['time'],*s['state'])) for s in states)+'\n'
probes=subprocess.run([str(compiled.executable),'--probe'],input=payload,capture_output=True,text=True,check=True,timeout=45)
output=[json.loads(line) for line in probes.stdout.splitlines()]
keys=[v.key for v in program.variables]
focused=[k for k in keys if any(word in k for word in ('amesim_mecmas21','amesim_lstp00a','amesim_ud00','amesim_forc'))]
rows=[]
for s,e in zip(states,output):
assert e['success'];outputs=dict(zip(keys,e['outputs']))
rows.append(dict(time=s['time'],values={k:outputs[k] for k in focused}))
save(directory/'dense-outputs.json',rows)
save(directory/'summary.json',dict(countersUnchanged=True,queries=len(rows),nativeRun={k:v for k,v in r.items() if k not in ('series','final','finalState')}))
print('trace done',len(rows),flush=True)
finally:compiled.close()
def rebase(out):
"""Short diagnostic continuation in a local clock, with the SAME saved
boundary state and physics. This is not a production time-handling patch.
"""
_,_,doc,net,program=model('full')
points=json.loads((out/'trace/queries.json').read_bytes())['points']
point=next(p for p in points if abs(p['boundary']-32.4)<1e-6)
origin=point['boundary'];duration=point['ameTime']-origin
summary=json.loads((out/'baseline/full/summary.json').read_bytes())
state_file=out/'results'/summary['nativeRun']['resultStorage']['id']/'states.bin'
assert not result_storage.scan_blocks(state_file)['corrupt']
initial=None
with state_file.open('rb') as stream:
while header:=stream.read(result_storage._HEADER.size):
magic,sequence,nrow,ncol,crc=result_storage._HEADER.unpack(header)
assert magic==b'SIMBLK01' and 0<nrow<=1024 and ncol==len(program.state_keys)+1
block=np.frombuffer(stream.read(nrow*ncol*8),dtype='<f8').reshape(ncol,nrow).T
assert stream.read(8)==b'COMMIT01'
matches=np.flatnonzero(block[:,0]==origin)
if len(matches):initial=block[matches[-1],1:].copy()
assert initial is not None
original=program.source
names=('model_init','model_eval','model_eval_jacobian','model_eval_jacobian_reuse',
'model_friction_drives','model_property_temperatures','model_next_break')
source=re.sub(r'\b('+ '|'.join(names)+r')\s*\(',lambda m:'absolute_'+m[1]+'(',original)
source+='\nint model_init(double *y) {const double initial[NSTATES]={'+','.join(repr(float(v)) for v in initial)+'};memcpy(y,initial,sizeof(initial));return 1;}\n'
for name,tail,args in [
('model_eval','double *dy,double *w','dy,w'),
('model_eval_jacobian','double *dy,double *w','dy,w'),
('model_eval_jacobian_reuse','double *dy,double *w,ModelJacobianWorkspace *workspace','dy,w,workspace'),
('model_friction_drives','double *drives','drives'),
('model_property_temperatures','NativePropertyTemperatures *temperatures','temperatures')]:
source+=f'int {name}(double t,const double *y,{tail}) {{return absolute_{name}(t+{origin!r},y,{args});}}\n'
# All STEP/UD00 signals are constant in this 2.2 ps continuation.
source+='double model_next_break(double t,double end) {(void)t;return end;}\n'
directory=out/'rebase';directory.mkdir(exist_ok=True)
save(directory/'initial-state.json',dict(time=origin,stateKeys=program.state_keys,state=initial.tolist()))
compiled=build(replace(program,source=source),out)
try:
records=[]
for rtol in (1e-8,1e-10):
config=replace(simulation_config(doc.simulation),t_start=0,t_stop=duration,max_step=duration/20,rtol=rtol)
with patch.object(result_storage,'RESULT_ROOT',out/'results'):
r=execute_native(compiled,config,duration/40,run_dir=directory/str(rtol),timeout=60)
assert r['success'],r['message']
record={k:v for k,v in r.items() if k not in ('series',)}
record.update(origin=origin,duration=duration,rtol=rtol)
records.append(record)
print('rebase',rtol,r['final']['amesim_lstp00a_2.force'],r['solverControl'],flush=True)
save(directory/'summary.json',records)
finally:compiled.close()
def analyze(out):
project,_,_,net,program=model('full')
info=json.loads((out/'trace/queries.json').read_bytes())
dense={row['time']:row['values'] for row in json.loads((out/'trace/dense-outputs.json').read_bytes())}
descriptors=json.loads((PREVIOUS/'full/lstp/event-descriptors.json').read_bytes())['contacts']
grid=np.load(out/'baseline/full/curves.npz')
rows=[]
for point in info['points']:
boundary=point['boundary'];at=point['ameTime'];i=int(np.argmin(abs(grid['time']-boundary)))
if not any(abs(boundary-t)<1e-6 for t in (21.6,32.4,43.2)):continue
for contact in descriptors:
name=contact['name'];_,v1,v2,gap,k,d,pdis,option=contact['values']
keys=[program.state_keys[v1],program.state_keys[v2]]
values={}
for side in ('platform','amesim'):
p=-float(grid[side+'|'+name+'.gap'][i])
velocity=float(grid[side+'|'+keys[0]][i]-grid[side+'|'+keys[1]][i])
elastic=k*p;damping=-math.expm1(-p/pdis)*d*velocity
force=float(grid[side+'|'+name+'.force'][i])
values[side]=dict(penetration=p,relativeVelocity=velocity,elastic=elastic,damping=damping,force=force,
reconstructedForce=elastic+damping,residual=force-(elastic+damping))
error=values['platform']['force']-values['amesim']['force']
same=dense[at][name+'.force'];same_error=same-values['amesim']['force']
row=dict(component=name,nominalTime=float(grid['time'][i]),boundary=boundary,ameTime=at,
elapsedAfterBoundary=at-boundary,**values,originalForceError=error,
elasticError=values['platform']['elastic']-values['amesim']['elastic'],
dampingError=values['platform']['damping']-values['amesim']['damping'],
sameTimeForce=same,sameTimeForceError=same_error,
errorReductionPercent=100*(1-abs(same_error)/abs(error)),
timeUlp=math.ulp(boundary),forceChangePerTimeUlp=d*abs(dense[at]['amesim_mecmas21_10.a'])*math.ulp(boundary))
rows.append(row)
save(out/'force-error-analysis.json',rows)
for row in rows:
if row['component']=='amesim_lstp00a_2':print(json.dumps(row,ensure_ascii=False))
# Report the absolute raw event peak separately from reference error.
raw=json.loads((out/'baseline/full/native/result.json').read_bytes())['series']
name='amesim_lstp00a_2';i=int(np.argmax(np.abs(raw[name+'.force'])))
peak=dict(time=raw['time'][i],force=raw[name+'.force'][i],gap=raw[name+'.gap'][i],
relativeVelocity=raw[name+'.port_1.v'][i]-raw[name+'.port_2.v'][i])
peak['elastic']=-peak['gap']*1e11;peak['damping']=peak['force']-peak['elastic']
keys=(name+'.force',name+'.gap',name+'.port_1.v',name+'.port_2.v',
'amesim_mecmas21_10.x','amesim_mecmas21_10.v','amesim_ud00_2.out.signal')
peak['adjacentSamples']=[dict(time=raw['time'][j],**{k:raw[k][j] for k in keys}) for j in (i-1,i,i+1)]
save(out/'raw-force-peak.json',peak);print('raw peak',peak)
grid.close()
def amesim_events(out):
"""Enable only Amesim's documented discontinuities printout on a copy."""
directory=out/'amesim-event-output'
evaluation.prepare_ame(ROOT/'tests/data/test_mql.ame',directory,50,.01,1e-8)
sim=directory/'test_mql_.sim';lines=sim.read_text(encoding='ascii').splitlines()
before=lines[:];options=lines[1].split()
# Amesim 2404 scripting/python/amesim.py: ameputsimopt maps printDiscont
# to simOptions[2]; every other solver and model setting stays unchanged.
options[2]='1';lines[1]=' '.join(options);sim.write_text('\n'.join(lines)+'\n',encoding='ascii')
save(directory/'print-option-change.json',dict(before=before,after=lines,source='Amesim 2404 ameputsimopt: simOptions[2]'))
run=evaluation.run_ame(directory,Path('F:/AMESim2404/Amesim'))
reference=curves.read_ame(directory)
audit=json.loads((PREVIOUS/'full/audit/audit.json').read_bytes())
aliases={x['component']:x['ameAlias'] for x in audit['mapping']}
keys={'force':'f1@'+aliases['amesim_lstp00a_2'],
'gap':'gap@'+aliases['amesim_lstp00a_2'],
'massVelocity':'v1@'+aliases['amesim_mecmas21_10'],
'branchVelocity':'v1@'+aliases['amesim_mecmas21_2'],
'massPosition':'x1@'+aliases['amesim_mecmas21_10'],
'drive':'output@'+aliases['amesim_ud00_2']}
times=np.array(reference.times);data={k:np.array(reference.series(v)) for k,v in keys.items()}
data['gap']*=.001
selected=np.flatnonzero((times>32.39999)&(times<32.4004))
rows=[dict(time=float(times[i]),**{k:float(v[i]) for k,v in data.items()}) for i in selected]
j=int(np.argmax(abs(data['force'])))
peak=dict(index=j,time=float(times[j]),**{k:float(v[j]) for k,v in data.items()})
save(directory/'mechanical-events.json',dict(run=run,samples=len(times),around324=rows,maximumAbsoluteForce=peak))
print('Amesim event output',len(times),'rows',rows,'peak',peak,flush=True)
def event_comparison(out):
directory=out/'event-output-comparison';directory.mkdir(exist_ok=True)
(directory/'native').mkdir(exist_ok=True);(directory/'amesim').mkdir(exist_ok=True)
for source,target in [(out/'baseline/full/native/result.json',directory/'native/result.json'),
*[(out/'amesim-event-output'/name,directory/'amesim'/name) for name in ('test_mql_.var','test_mql_.results')]]:
if not target.exists():os.link(source,target)
project,_,_,net,_=model('full')
audit=json.loads((PREVIOUS/'full/audit/audit.json').read_bytes())
original_reference=curves.read_ame(directory/'amesim')
times=np.array(original_reference.times);order=np.argsort(times,kind='stable')
# Amesim writes its .8 s discontinuity after an already-written grid row
# at .8000000000000009. Preserve every row/value and stable-sort by its
# actual timestamp for this diagnostic matcher; retain the permutation.
sorted_reference=replace(original_reference,times=tuple(times[order]),
series_by_data_path={k:tuple(np.asarray(v)[order]) for k,v in original_reference.series_by_data_path.items()})
save(directory/'ame-row-order.json',dict(originalTimeInversions=np.flatnonzero(np.diff(times)<0).tolist(),sortedToOriginal=order.tolist()))
with patch.object(curves,'read_ame',return_value=sorted_reference):
summary=evaluation.compare(directory,json.loads(project.read_bytes()),net,audit,evaluation.PROFILES['full'])
# Adding event outputs must preserve every original Amesim saved sample.
old=curves.read_ame(PREVIOUS/'full/reference');new=sorted_reference
oldtimes=np.array(old.times);newtimes=np.array(new.times)
indices=np.searchsorted(newtimes,oldtimes)
assert np.array_equal(newtimes[indices],oldtimes)
unchanged=all(np.array_equal(np.array(new.series(key))[indices],values) for key,values in old.series_by_data_path.items())
assert unchanged,'Amesim event printout changed original results'
summary['allOriginalAmesimRowsUnchanged']=unchanged
save(directory/'summary.json',summary)
print('event-output comparison',summary['groups']['force']['worstAbsolute'],
'above5',summary['above5PercentCount'],'unpaired',summary['phaseUnpairedGridCount'],flush=True)
def main():
parser=argparse.ArgumentParser(description=__doc__)
parser.add_argument('--output',type=Path,default=DEFAULT)
parser.add_argument('--stage',choices=('baseline','trace','rebase','analyze','amesim-events','event-comparison','all'),default='all')
args=parser.parse_args();out=args.output.resolve();out.mkdir(parents=True,exist_ok=True)
if args.stage in ('baseline','all'):baseline(out)
if args.stage in ('trace','all'):trace(out)
if args.stage in ('rebase','all'):rebase(out)
if args.stage in ('analyze','all'):analyze(out)
if args.stage in ('amesim-events','all'):amesim_events(out)
if args.stage in ('event-comparison','all'):event_comparison(out)
if __name__=='__main__':main()
+90
View File
@@ -0,0 +1,90 @@
"""position379 only: diagnose the actual numerical tail before any real skip."""
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
import argparse,hashlib,json,os,shutil,subprocess,time
import local_probe_experiment as ex
ROOT=ex.ROOT
HERE=Path(__file__).parent
BASE=ROOT/'test/local-probe-20260917/worker'
OUT=ROOT/'test/position379-tail-20260917'
def replace(s,a,b):
assert s.count(a)==1,(a,s.count(a))
return s.replace(a,b)
def prepare(trace=True,tail_only=False):
name='tail-only-trace' if tail_only else 'diag-trace' if trace else 'diag-perf'
work=OUT/name;work.mkdir(parents=True,exist_ok=True)
src={p.name:p.read_text(encoding='utf-8') for p in BASE.glob('*.c')}
hashes={k:hashlib.sha256(v.encode()).hexdigest() for k,v in src.items()}
plan=json.loads((ROOT/'test/context-fallback-20260917/plan.json').read_text(encoding='utf-8'))
s=src['model.c'];a,b,e=ex.function_span(s,'model_eval_local_internal');body=s[a:e];op=plan['code'][379][0]
assert body.count(op)==2
body=body.replace(op,'kd_scope=1;'+op+'kd_scope=0;')
body=replace(body,'return 1;}','kd_eval_exit(t,y,dy,w,1,properties,pipe_cache,jacobian);return 1;}')
src['model.c']=s[:a]+body+s[e:]
s=src['properties.c'];a,b,e=ex.function_span(s,'state_valve');body=s[a:e]
boundary=' double r=fmax(pd/p,0),critical=pow(2*g/(g+1),1/(1-g)),eff;'
body=replace(body,boundary,''' KdRecord kd_record;uint64_t kd_t=0;
if(kd_scope){uint64_t kd_lookup_start=kd_clock();kd_before(&kd_record,p,T,pd,g,rho);kd_record.lookup=kd_clock()-kd_lookup_start;
kd_t=kd_clock();
}
'''+boundary)
end=body.rfind('}')
body=body[:end]+' if(kd_scope){uint64_t kd_end=kd_clock();kd_after(&kd_record,*cm,*vel,kd_end-kd_t,1);}\n'+body[end:]
if tail_only:
# Cross-check K without key construction, lookup, or environment reads
# immediately before the timed numerical work. Post-tail records in this
# mode MUST NOT be used for memo/FP eligibility or G/H estimates.
before='uint64_t kd_lookup_start=kd_clock();kd_before(&kd_record,p,T,pd,g,rho);kd_record.lookup=kd_clock()-kd_lookup_start;'
body=replace(body,before,'')
body=replace(body,'uint64_t kd_end=kd_clock();kd_after(', 'uint64_t kd_end=kd_clock();kd_before(&kd_record,p,T,pd,g,rho);kd_record.lookup=0;kd_after(')
src['properties.c']=s[:a]+body+s[e:]
src['common.c']=replace(replace(src['common.c'],'lp_start();','lp_start();kd_start();'),'lp_finish();','lp_finish();kd_finish();')
src['cvode_solver.c']=replace(src['cvode_solver.c'],'{lp_color=-1;uint64_t start=lp_tick();','{lp_color=-1;kd_new_jac();uint64_t start=lp_tick();')
src['cvode_solver.c']=replace(src['cvode_solver.c'],'if(!result)lp_matrix(t,N_VGetArrayPointer(y),SUNDenseMatrix_Data(matrix));','if(!result){lp_matrix(t,N_VGetArrayPointer(y),SUNDenseMatrix_Data(matrix));kd_matrix(t,N_VGetArrayPointer(y),SUNDenseMatrix_Data(matrix));}')
src['valve_tail_diag.c']=(HERE/'valve_tail_diag.c').read_text(encoding='utf-8')
for name in ('model.h','local_probe.h'):shutil.copyfile(BASE/name,work/name)
shutil.copyfile(HERE/'valve_tail_diag.h',work/'valve_tail_diag.h')
cc,sun,_=ex.builder.toolchain();flags,libs,dlls,exe=ex.builder.platform_build_inputs(sun)
flags+=['-DLP_OBSERVE=0',f'-DKD_TRACE={int(trace)}']
def compile_one(item):
name,code=item;path=work/name;path.write_text('#include "valve_tail_diag.h"\n'+code,encoding='utf-8',newline='\n');obj=path.with_suffix('.o');log=[]
ex.builder._command([cc,*flags,'-I',str(work),'-I',str(ex.builder.NATIVE/'include'),'-I',str(sun/'include'),'-c',str(path),'-o',str(obj)],log=log,timeout=240)
return obj,log
with ThreadPoolExecutor(max_workers=4) as pool:objects=list(pool.map(compile_one,src.items()))
log=[];ex.builder._command([cc,*flags,*[str(o) for o,_ in objects],*ex.builder.link_library_arguments(libs),'-lm','-o',str(work/exe)],log=log)
for dll in dlls:shutil.copyfile(dll,work/dll.name)
(work/'build.log').write_text('\n'.join(sum([l for _,l in objects],[])+log),encoding='utf-8')
assert src['local_probe_support.c']==(BASE/'local_probe_support.c').read_text(encoding='utf-8')
ex.write(work/'build.json',dict(originalHashes=hashes,skip=False,trace=trace,tailOnlyTiming=tail_only,wholeContextGuardUnchanged=True,sourceHashes={p.name:hashlib.sha256(p.read_bytes()).hexdigest() for p in work.glob('*.c')}))
print('BUILT',work.name,flush=True)
def run(label,worker='diag-trace',matrices=False,disabled=False):
folder=OUT/label;folder.mkdir(parents=True,exist_ok=True);env=os.environ.copy();env['LOCAL_PROBE_MASK']='0x7ffffff'
if matrices:env['KD_MATRICES']='1'
if disabled:env['KD_DISABLED']='1'
args=[str(OUT/worker/'model.exe'),'--method','BDF','--start','0','--stop','10','--sample-step','.01','--max-step','1e30','--rtol','1e-8','--timeout','300','--sample-file',str(folder/'states.bin'),'--output-block-file',str(folder/'outputs.bin'),'--output',str(folder/'result.json')]
start=time.perf_counter()
with (folder/'stderr.log').open('wb') as f:r=subprocess.run(args,cwd=folder,env=env,stdout=subprocess.PIPE,stderr=f,timeout=330,creationflags=subprocess.CREATE_NO_WINDOW)
assert r.returncode==0,(label,r.returncode,(folder/'stderr.log').read_text(encoding='utf-8')[-4000:])
result=json.loads((folder/'result.json').read_text(encoding='utf-8'));d=json.loads((folder/'probe.json').read_text(encoding='utf-8'))
reference=json.loads((BASE.parent/'all-run-0/measurement.json').read_text(encoding='utf-8'))
keys=['success','finalState','final','propertyWarnings','acceptedSteps','rejectedSteps','stateTransitions','solverStarts','nfev','njev','nlu']
assert all(result[k]==reference[k] for k in keys),[(k,result[k],reference[k]) for k in keys if result[k]!=reference[k]]
for k in ['newtonIterations','newtonConvergenceFailures','modelCalls','groups','contextCopiedBytes','contextComparedBytes']:assert d[k]==reference['diagnostic'][k],k
hashes={}
for name in ['states','outputs','events']+(['jacobians'] if matrices else []):
hashes[name]=hashlib.sha256((folder/(name+'.bin')).read_bytes()).hexdigest()
expected=hashlib.sha256((BASE.parent/'all-audit/jacobians.bin').read_bytes()).hexdigest() if name=='jacobians' else reference[name+'Sha256']
assert hashes[name]==expected,name
data=json.loads((folder/'tail.json').read_text());assert data['jacobians']==896
record=dict(label=label,worker=worker,exact=True,hashes=hashes,counters={k:result[k] for k in keys},newtonIterations=d['newtonIterations'],newtonConvergenceFailures=d['newtonConvergenceFailures'],tail=data,wall=result['solveSeconds'],cpu=result['solveCpuSeconds'],jacobian=d.get('jacobianSeconds'),processSeconds=time.perf_counter()-start)
ex.write(folder/'validation.json',record);print(json.dumps(dict(label=label,exact=True,tail=data,wall=record['wall'],cpu=record['cpu']),ensure_ascii=True),flush=True)
return record
if __name__=='__main__':
p=argparse.ArgumentParser();p.add_argument('action',choices=['prepare','run']);p.add_argument('--label',default='diagnostic-0');p.add_argument('--worker',default='diag-trace');p.add_argument('--no-trace',action='store_true');p.add_argument('--tail-only',action='store_true');p.add_argument('--matrices',action='store_true');p.add_argument('--disabled',action='store_true');a=p.parse_args()
if a.action=='prepare':prepare(not a.no_trace,a.tail_only)
else:run(a.label,a.worker,a.matrices,a.disabled)
+140
View File
@@ -0,0 +1,140 @@
"""Run isolated off/MASS/LSTP/all experiments without altering production.
One fresh Amesim execution per profile supplies the common reference. Accuracy
runs retain full output. Repeated solve-only runs rotate variant order and
exclude compilation/output serialization from integration timing.
"""
from dataclasses import replace
from concurrent.futures import ThreadPoolExecutor
from contextlib import nullcontext
import argparse
import hashlib
import json
import os
from pathlib import Path
import statistics
import sys
import time
from unittest.mock import patch
ROOT=Path(__file__).resolve().parents[2]
sys.path.insert(0,str(ROOT))
from app.main import compile_system_xml_network
from app.simulation.backends import simulation_config
from app.simulation.native_codegen import build as builder, result_storage
from app.simulation.native_codegen.compiler import compile_native_program
from app.simulation.native_codegen.input import load_input
from app.simulation.native_codegen.runner import execute_native
from tests.manual import evaluate_mql8_correctness as evaluation
from tests.manual.mechanical_event_variant import event_program,prepare_runtime,MODES
def tree_hashes(root):
return {str(p.relative_to(root)):hashlib.sha256(p.read_bytes()).hexdigest()
for folder in ('native','app/simulation/native_codegen') for p in (root/folder).rglob('*')
if p.is_file() and '__pycache__' not in p.parts}
def main():
parser=argparse.ArgumentParser(description=__doc__)
parser.add_argument('--output',type=Path,required=True)
parser.add_argument('--profiles',nargs='+',choices=('full','noncyclic'),default=['full','noncyclic'])
parser.add_argument('--repeats',type=int,default=5)
parser.add_argument('--resume',action='store_true',help='Resume completed accuracy/timing runs after a transient toolchain failure.')
parser.add_argument('--serial-build',action='store_true',help='Serialize experiment compilation to recover Windows compiler-launch failures; solve settings are unchanged.')
args=parser.parse_args();out=args.output.resolve();out.mkdir(parents=True,exist_ok=args.resume)
if args.repeats<3:raise ValueError('Use at least three repeated integration timings')
control=ROOT/'test/mechanical-events-20260917/source-control/native'
runtime=out/'experimental-native';prepare_runtime(control,runtime)
production=tree_hashes(ROOT)
evaluation.save(out/'production-before.json',production)
results=json.loads((out/'summary.json').read_bytes()) if args.resume and (out/'summary.json').exists() else {}
for profile in args.profiles:
if profile in results and all('timing' in r for r in results[profile].values()):continue
base=out/profile;base.mkdir(exist_ok=args.resume)
ame=ROOT/('tests/data/test_mql.ame' if profile=='full' else 'test/node-fixes-amesim-20260914/test_mql.ame')
project=ROOT/'test/output-semantics-20260917/after'/profile/'platform.json'
audit=(json.loads((base/'audit/audit.json').read_bytes()) if (base/'audit/audit.json').exists()
else evaluation.audit_input(ame,project,base/'audit'))
settings=evaluation.PROFILES[profile];stop,step,rtol=settings
if not (base/'reference').exists():evaluation.prepare_ame(ame,base/'reference',stop,step,rtol)
if args.resume and (base/'reference/run-summary.json').exists():
ame_run=json.loads((base/'reference/run-summary.json').read_bytes())
assert ame_run['normalTermination'] and ame_run['returncode']==0
else:
print(profile,'fresh Amesim reference',flush=True)
ame_run=evaluation.run_ame(base/'reference',Path('F:/AMESim2404/Amesim'))
xml,document=load_input(project);network=compile_system_xml_network(document)
raw_project=json.loads(project.read_bytes())
original=compile_native_program(network)
builds={};records=json.loads((base/'summary.json').read_bytes()) if args.resume and (base/'summary.json').exists() else {}
config=replace(simulation_config(document.simulation),rtol=rtol)
try:
for mode in MODES:
directory=base/mode;directory.mkdir(exist_ok=args.resume);(directory/'platform.json').write_bytes(project.read_bytes())
(directory/'platform.xml').write_bytes(xml)
reference=directory/'amesim';reference.mkdir(exist_ok=args.resume)
for name in ('test_mql_.results','test_mql_.var'):
if not (reference/name).exists():os.link(base/'reference'/name,reference/name)
program,metadata=event_program(original,network,mode)
evaluation.save(directory/'event-descriptors.json',metadata)
assert program.state_keys==original.state_keys
assert program.jacobian_structure==original.jacobian_structure
assert program.evaluation_schedule==original.evaluation_schedule
assert program.source.startswith(original.source)
for attempt in range(3):
cache=out/('build-cache' if not attempt else f'build-cache-retry-{profile}-{mode}-{time.time_ns()}')
try:
build_workers=(patch.object(builder,'ThreadPoolExecutor',lambda **kwargs: ThreadPoolExecutor(max_workers=1))
if args.serial_build else nullcontext())
with patch.object(builder,'NATIVE',control if mode=='off' else runtime),patch.object(builder,'CACHE',cache),build_workers:
print(profile,mode,'build',attempt+1,flush=True)
builds[mode]=builder.build_native(program)
break
except PermissionError as exc:
if getattr(exc,'winerror',None)!=5 or attempt==2:raise
print('Windows cache rename denied; retaining artifacts and using a fresh isolated cache.',flush=True)
if mode in records and not records[mode].get('failed'):
print(profile,mode,'retaining completed accuracy result',flush=True)
continue
print(profile,mode,'accuracy run',flush=True)
with patch.object(result_storage,'RESULT_ROOT',out/'result-storage'):
r=execute_native(builds[mode],config,step,run_dir=directory/'native',timeout=180)
native={k:v for k,v in r.items() if k not in ('series','final','finalState')}
evaluation.save(directory/'native-summary.json',native)
if not r['success']:
records[mode]=dict(nativeRun=native,failed=True)
evaluation.save(base/'summary.json',records)
raise RuntimeError(f'{profile}/{mode} failed: '+r['message'])
summary=evaluation.compare(directory,raw_project,network,audit,settings)
summary.update(nativeRun=native,amesimRun=ame_run,settings=dict(stop=stop,sampleStep=step,rtol=rtol))
records[mode]=summary;evaluation.save(base/'summary.json',records)
print(profile,mode,'accuracy done; force=',summary['groups']['force']['worstAbsolute']['maxAbsoluteError'],
'events=',native.get('experimentalEvents',{}),flush=True)
timings=json.loads((base/'timings.json').read_bytes()) if args.resume and (base/'timings.json').exists() else {mode:[] for mode in MODES}
for repeat in range(args.repeats):
order=MODES[repeat%4:]+MODES[:repeat%4]
for mode in order:
if len(timings[mode])>repeat:continue
print(profile,'timing',repeat+1,mode,flush=True)
r=execute_native(builds[mode],config,step,record_samples=False,
run_dir=base/mode/f'timing-{repeat+1}',timeout=180)
if not r['success']:raise RuntimeError(r['message'])
selected={k:v for k,v in r.items() if k in ('solveSeconds','solveCpuSeconds','processWallSeconds',
'nfev','acceptedSteps','rejectedSteps','solverStarts','stateTransitions','njev','nlu','experimentalEvents')}
timings[mode].append(selected)
evaluation.save(base/'timings.json',timings)
for mode in MODES:
values=[r['solveSeconds'] for r in timings[mode]]
records[mode]['timing']=dict(repeats=len(values),medianSeconds=statistics.median(values),
minimumSeconds=min(values),maximumSeconds=max(values),runs=timings[mode])
results[profile]=records;evaluation.save(out/'summary.json',results)
finally:
for build in builds.values():build.close()
after=tree_hashes(ROOT)
evaluation.save(out/'production-verification.json',dict(unchanged=production==after,before=production,after=after))
assert production==after,'Production code changed during the isolated experiment'
print('Completed; production source and existing numerical optimizations unchanged.',flush=True)
if __name__=='__main__':main()
+412
View File
@@ -0,0 +1,412 @@
"""Re-run audited eight-branch inputs against the AME archive's executable.
Use project Python 3.12; --output must be a new directory. Only copied time
settings change unless --align-cyclic-from-ame is explicitly supplied, which
permits only the audited UD00 cyclic flags to change in a new JSON copy.
Numeric differences are evidence, not a claim of physical validation.
"""
from __future__ import annotations
import argparse
from contextlib import redirect_stdout
from dataclasses import replace
import hashlib
import json
import os
from pathlib import Path
import re
import subprocess
import sys
import tarfile
import time
from unittest.mock import patch
import numpy as np
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from app.main import compile_system_xml_network
from app.simulation.backends import simulation_config
from app.simulation.native_codegen import build as builder, result_storage
from app.simulation.native_codegen.compiler import compile_native_program
from app.simulation.native_codegen.input import load_input
from app.simulation.native_codegen.runner import execute_native
from tests.manual import mql8_comparison as curves
from tests.manual.event_phase_comparison import pair_saved_phases
from tools import audit_test_mql8_model as auditor
# Report boundaries, not accepted engineering tolerances. Preserve absolute
# errors when the reference is small, and report the sensitivity to this choice.
EPS = dict(pressure=1., temperature=1e-6, enthalpy_flow=1., mass_flow=1e-6,
displacement=1e-9, velocity=1e-6, gap=1e-9, mass=1e-12,
volume=1e-12, piston_volume=1e-12, volume_rate=1e-12,
chamber_volume_rate=1e-12, volume_work=1., force=1e-6, signal=1e-12)
PROFILES = {'default': (10., .01, 1e-8), 'full': (50., .01, 1e-8),
'startup': (.15, .0001, 1e-8), 'startup-refined': (.15, .0001, 1e-10),
'volume-startup': (2e-6, 2e-9, 1e-10), 'noncyclic': (50., .01, 1e-8)}
def digest(path):
return hashlib.sha256(path.read_bytes()).hexdigest()
def save(path, data):
path.write_text(json.dumps(data, ensure_ascii=False, indent=2, allow_nan=False) + '\n', encoding='utf-8')
def audit_input(ame, project, out, allow_cyclic_mismatch=False):
ame, project = ame.resolve(), project.resolve()
out.mkdir(parents=True)
with patch.object(auditor, 'HERE', out), patch.object(auditor, 'AME', ame), \
patch.object(auditor, 'INPUT', project), patch.object(sys, 'argv', ['audit', '--check']), \
(out / 'audit.log').open('w', encoding='utf-8') as log, redirect_stdout(log):
try:
auditor.main()
except AssertionError:
if not allow_cyclic_mismatch or not (out / 'audit.json').exists():
raise
evidence = json.loads((out / 'audit.json').read_bytes())
changes = evidence['parameterChanges']
if evidence['connectionChanges'] or not changes or not all(
row['parameter'] == 'iscyclic' and row['component'].startswith('amesim_ud00_')
for row in changes):
raise
return json.loads((out / 'audit.json').read_bytes())
def prepare_ame(archive_path, target, stop, step, rtol):
target.mkdir()
hashes = {}
with tarfile.open(archive_path) as archive:
for member in archive:
if not member.isfile() or member.name.endswith(('.results', '.ameperf')):
continue
path = (target / member.name).resolve()
if not path.is_relative_to(target.resolve()):
raise ValueError('Archive path outside target: ' + member.name)
path.parent.mkdir(parents=True, exist_ok=True)
data = archive.extractfile(member).read()
path.write_bytes(data)
hashes[member.name] = hashlib.sha256(data).hexdigest()
sim = target / 'test_mql_.sim'
lines = sim.read_text(encoding='ascii').splitlines()
original = lines[0].split()
fields = original[:]
fields[:5] = ['0', str(stop), str(step), '1e30', str(rtol)]
lines[0] = ' '.join(fields)
sim.write_text('\n'.join(lines) + '\n', encoding='ascii')
assert all(digest(target / name) == value for name, value in hashes.items() if name != sim.name)
save(target / 'source-verification.json', dict(archiveSha256=digest(archive_path),
originalSim=original, actualSim=fields, unchangedArchiveFiles=hashes))
def run_ame(target, ame_home):
env = dict(os.environ, AME=str(ame_home))
env['PATH'] = str(ame_home / 'win64') + os.pathsep + str(ame_home) + os.pathsep + env['PATH']
start = time.perf_counter()
with (target / 'run.log').open('wb') as log:
proc = subprocess.run([str(target / 'test_mql_.exe')], cwd=target, env=env,
stdout=log, stderr=subprocess.STDOUT, timeout=240,
creationflags=subprocess.CREATE_NO_WINDOW if os.name == 'nt' else 0)
elapsed = time.perf_counter() - start
log = (target / 'run.log').read_text(encoding='utf-8', errors='replace')
cpu = re.search(r'Total CPU time:\s*([\d.eE+-]+)', log)
result = dict(returncode=proc.returncode, processWallSeconds=elapsed,
cpuSeconds=float(cpu.group(1)) if cpu else None,
normalTermination='terminated normally' in log)
save(target / 'run-summary.json', result)
if proc.returncode or not result['normalTermination']:
raise RuntimeError(log[-3000:])
result['resultsSha256'] = digest(target / 'test_mql_.results')
return result
def event_times(project, stop):
result = set()
for node in project['nodes']:
kind, p = node['data']['modelType'], node['data']['parameters']
if kind == 'amesim_step0':
result.add(float(p['time']))
elif kind == 'amesim_ud00':
durations = [float(p['t' + str(i)]) for i in range(1, int(p['nstages']) + 1)]
period = sum(durations)
cycles = range(int(stop / period) + 1) if int(p['iscyclic']) else range(1)
for cycle in cycles:
at = float(p['tstart']) + cycle * period
result.add(at)
for duration in durations:
at += duration
result.add(at)
return sorted(t for t in result if 0 < t <= stop)
def metric(actual, expected, grid, quantity, exact_events, quiet):
error = actual - expected
absolute = np.abs(error)
nonzero = np.abs(expected) > EPS[quantity]
relative = np.zeros_like(error)
relative[nonzero] = 100 * error[nonzero] / expected[nonzero]
bad = nonzero & (np.abs(relative) > 5)
near = ~nonzero
index = int(np.argmax(absolute))
def maximum(values, mask):
return float(np.max(values[mask])) if np.any(mask) else None
examples = []
for j in np.flatnonzero(bad)[np.argsort(absolute[bad])[-5:][::-1]]:
examples.append(dict(time=float(grid[j]), platform=float(actual[j]), amesim=float(expected[j]),
absoluteError=float(absolute[j]), relativePercent=float(relative[j]),
atSignalEvent=bool(exact_events[j])))
return dict(maxAbsoluteError=float(absolute[index]), worstTime=float(grid[index]),
platformAtWorst=float(actual[index]), amesimAtWorst=float(expected[index]),
rmse=float(np.sqrt(np.mean(error**2))), finalError=float(error[-1]),
referencePeak=float(np.max(np.abs(expected))),
maxRelativePercent=maximum(np.abs(relative), nonzero),
maxRelativeOutsideEvents=maximum(np.abs(relative), nonzero & ~exact_events),
maxAbsoluteOutsideEvents=maximum(absolute, ~exact_events),
quietMaxAbsolute=maximum(absolute, quiet),
nonzeroCount=int(nonzero.sum()), above5PercentCount=int(bad.sum()),
above5PercentOutsideEvents=int((bad & ~exact_events).sum()),
nearZeroCount=int(near.sum()), nearZeroMaxAbsolute=maximum(absolute, near),
nearZeroBeyondEpsilon=int((near & (absolute > EPS[quantity])).sum()),
epsilon=EPS[quantity], examples=examples,
relativeScreenSensitivity={str(factor): int(((np.abs(expected) > EPS[quantity] * factor)
& (absolute > .05 * np.abs(expected))).sum()) for factor in (.1, 1., 10.)})
def sample_grid(times, values, grid, step):
"""Use the actual saved value at the same nominal output-grid position.
Repeated floating-point additions move an Amesim output timestamp slightly
off its nominal grid. Interpolating 1e17 -> 49000 immediately before an
almost-equal endpoint invents a plateau error through cancellation. Match
only within 1e-7 of one output interval; retain that record's original side
of an event. Do not move event times, average duplicates, or smooth spikes.
"""
times = np.asarray(times)
right = np.clip(np.searchsorted(times, grid), 0, len(times) - 1)
left = np.maximum(right - 1, 0)
closest = np.where(np.abs(times[left] - grid) < np.abs(times[right] - grid), left, right)
same_output = np.abs(times[closest] - grid) <= step * 1e-7
result = np.interp(grid, times, values)
result[same_output] = np.asarray(values)[closest[same_output]]
return result
def compare(directory, project, network, audit, settings):
stop, step, rtol = settings
for node in project['nodes']:
if node['data']['modelType'] == 'amesim_ud00':
p = node['data']['parameters']
if any(float(p['start'+str(i)]) != float(p['end'+str(i)]) for i in range(1, int(p['nstages'])+1)):
raise ValueError('This baseline phase matcher requires piecewise-constant UD00 stages; ramps need explicit stage metadata.')
curves.configure(audit, network)
mapping = curves.curve_mapping()
save(directory / 'curve-mapping.json', mapping)
raw = json.loads((directory / 'native/result.json').read_bytes())
native = curves.native_curves(raw['series'])
ame = curves.read_ame(directory / 'amesim')
nt, at = native['time'], np.array(ame.times)
assert raw['success'] and raw['simulatedUntil'] == stop
assert nt[0] == at[0] == 0 and abs(at[-1] - stop) < 1e-9 and nt[-1] == stop
assert np.all(np.diff(nt) > 0) and np.all(np.diff(at) >= 0)
assert all(np.isfinite(v).all() for v in native.values())
assert all(np.isfinite(v).all() for v in ame.series_by_data_path.values())
grid = np.arange(round(stop / step) + 1) * step
events = event_times(project, stop)
exact_events = np.zeros(grid.shape, dtype=bool)
quiet = grid >= .1
for event in events:
exact_events |= np.abs(grid - event) <= 1e-9
quiet &= np.abs(grid - event) > .0200001
signal_mapping = [m for m in mapping if m['quantity'] == 'signal']
ni, ai, pairing = pair_saved_phases(nt, at,
np.column_stack([native[m['key']] for m in signal_mapping]),
np.column_stack([curves.ame_curve(ame, m) for m in signal_mapping]), grid, step)
valid = (ni >= 0) & (ai >= 0)
save(directory / 'phase-pairing.json', dict(
policy='Reference saved forcing phase; one shared row pair for all curves; no cross-event interpolation',
scope='Observed STEP/UD00 forcing phases; unregistered contact mode is not certified',
signalKeys=[m['key'] for m in signal_mapping], toleranceSeconds=step*1e-7,
gridCount=len(grid), pairedCount=int(valid.sum()), unpairedCount=int((~valid).sum()),
adjustedCount=sum(p['status'] == 'matched-other-event-side' for p in pairing), records=pairing))
if not np.any(valid):
raise ValueError('No matching saved physical phases; see phase-pairing.json')
rows, raw_rows, arrays = [], [], {'time': grid, 'phaseMatched': valid}
for m in mapping:
raw_actual = sample_grid(nt, native[m['key']], grid, step)
reference = curves.ame_curve(ame, m)
raw_expected = sample_grid(at, reference, grid, step)
raw_rows.append(m | metric(raw_actual, raw_expected, grid, m['quantity'], exact_events, quiet))
actual, expected = np.full(len(grid), np.nan), np.full(len(grid), np.nan)
actual[valid], expected[valid] = native[m['key']][ni[valid]], reference[ai[valid]]
rows.append(m | metric(actual[valid], expected[valid], grid[valid], m['quantity'], exact_events[valid], quiet[valid]))
arrays['platform|' + m['key']] = actual
arrays['amesim|' + m['key']] = expected
np.savez_compressed(directory / 'curves.npz', **arrays)
save(directory / 'raw-time-comparison.json', dict(curves=raw_rows,
above5PercentCount=sum(r['above5PercentCount'] for r in raw_rows)))
groups = {}
for quantity in sorted({r['quantity'] for r in rows}):
selected = [r for r in rows if r['quantity'] == quantity]
groups[quantity] = dict(curveCount=len(selected),
worstAbsolute=max(selected, key=lambda r: r['maxAbsoluteError']),
worstRelative=max(selected, key=lambda r: r['maxRelativePercent'] or 0),
nonzeroCount=sum(r['nonzeroCount'] for r in selected),
above5PercentCount=sum(r['above5PercentCount'] for r in selected),
above5PercentOutsideEvents=sum(r['above5PercentOutsideEvents'] for r in selected),
nearZeroBeyondEpsilon=sum(r['nearZeroBeyondEpsilon'] for r in selected))
mass_keys = [k for k in raw['series'] if k.rsplit('.', 1)[-1] in ('m', 'm1', 'm2')]
mass = sum(native[k] for k in mass_keys)
pressures = [v for k, v in native.items() if k.rsplit('.', 1)[-1] in ('p', 'p1', 'p2')]
temperatures = [v for k, v in native.items() if k.rsplit('.', 1)[-1] in ('T', 'T1', 'T2')]
physical = dict(massKeys=mass_keys, initialMassKg=float(mass[0]),
maxTotalMassDriftKg=float(np.max(np.abs(mass - mass[0]))),
relativeMassDrift=float(np.max(np.abs(mass - mass[0])) / mass[0]),
minimumGasMassKg=float(min(np.min(native[k]) for k in mass_keys)),
minimumAbsolutePressurePa=float(min(np.min(v) for v in pressures)),
minimumTemperatureK=float(min(np.min(v) for v in temperatures)),
maximumTemperatureK=float(max(np.max(v) for v in temperatures)))
event_samples = []
for event in events:
for m in mapping:
if m['quantity'] not in ('signal', 'force'):
continue
entry = dict(eventTime=event, key=m['key'])
for label, times, values in [('platform', nt, native[m['key']]),
('amesim', at, curves.ame_curve(ame, m))]:
idx = int(np.searchsorted(times, event))
entry[label] = [dict(time=float(times[i]), timeHex=float(times[i]).hex(), value=float(values[i]))
for i in range(max(0, idx-2), min(len(times), idx+3))]
event_samples.append(entry)
save(directory / 'event-samples.json', event_samples)
summary = dict(curveCount=len(rows), gridCount=len(grid), nativeSampleCount=len(nt),
phaseMatchedGridCount=int(valid.sum()), phaseUnpairedGridCount=int((~valid).sum()),
phaseAdjustedGridCount=sum(p['status'] == 'matched-other-event-side' for p in pairing),
rawTimeAbove5PercentCount=sum(r['above5PercentCount'] for r in raw_rows),
amesimSampleCount=len(at), platformOutputCount=len(raw['series']) - 1, allFinite=True,
signalEvents=events, physical=physical, groups=groups, curves=rows,
extraEventPointMaxContactForce=max(float(np.max(np.abs(native[m['key']])))
for m in mapping if m['quantity'] == 'force'),
above5PercentCurveCount=sum(r['above5PercentCount'] > 0 for r in rows),
above5PercentCount=sum(r['above5PercentCount'] for r in rows),
above5PercentOutsideEvents=sum(r['above5PercentOutsideEvents'] for r in rows))
save(directory / 'comparison.json', summary)
return {k: v for k, v in summary.items() if k != 'curves'}
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--output', type=Path, required=True)
parser.add_argument('--ame', type=Path, default=ROOT / 'tests/data/test_mql.ame')
parser.add_argument('--project', type=Path, default=ROOT / 'tests/data/test-mql-8-corrected.json')
parser.add_argument('--ame-home', type=Path, required=True)
parser.add_argument('--noncyclic-ame', type=Path)
parser.add_argument('--noncyclic-project', type=Path)
parser.add_argument('--align-cyclic-from-ame', action='store_true')
parser.add_argument('--analyze-only', action='store_true', help='Reanalyze existing raw results without solving.')
parser.add_argument('--profiles', nargs='+', choices=tuple(PROFILES), default=list(PROFILES))
parser.add_argument('--native-source', type=Path, help='Frozen native source tree for a controlled before/after run.')
args = parser.parse_args()
if args.native_source:
builder.NATIVE = args.native_source.resolve()
out = args.output.resolve()
if args.analyze_only:
summaries = json.loads((out / 'summary.json').read_bytes())
for name, old in list(summaries.items()):
directory = out / name
project = json.loads((directory / 'platform.json').read_bytes())
_, document = load_input(directory / 'platform.json')
audit_name = 'audit-noncyclic' if name == 'noncyclic' else 'audit-aligned'
audit = json.loads((out / audit_name / 'audit.json').read_bytes())
summary = compare(directory, project, compile_system_xml_network(document), audit, PROFILES[name])
summary.update({key: old[key] for key in ('settings', 'amesimRun', 'nativeRun')})
summaries[name] = summary
print(name, 'reanalyzed', summary['above5PercentCount'], flush=True)
save(out / 'summary.json', summaries)
manifest = json.loads((out / 'manifest.json').read_bytes())
manifest['interpolation'] = 'Same saved forcing phase within 1e-7 output intervals. Unpaired points explicitly reported; raw time-only metrics retained.'
save(out / 'manifest.json', manifest)
return
out.mkdir(parents=True, exist_ok=False)
if bool(args.noncyclic_ame) != bool(args.noncyclic_project):
parser.error('Both noncyclic input paths are required together.')
inputs = [args.ame, args.project] + ([args.noncyclic_ame, args.noncyclic_project] if args.noncyclic_ame else [])
tracked = subprocess.check_output(['git', 'ls-files', 'app', 'native', 'frontend/src'], cwd=ROOT, text=True).splitlines()
sources = {str(p.resolve()): digest(p) for p in inputs}
code = {p: digest(ROOT / p) for p in tracked if (ROOT / p).is_file()}
manifest = dict(gitCommit=subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=ROOT, text=True).strip(),
python=sys.version, sources=sources, productionSources=code, epsilons=EPS,
criterion='5% is a diagnostic screen, not an approved engineering tolerance',
freshAmesimExecution=True, amesimExecutable='Extracted from source AME; not recompiled',
nativeSource=str(builder.NATIVE),
interpolation='Same saved forcing phase within 1e-7 output intervals. Unpaired points explicitly reported; raw time-only metrics retained.')
save(out / 'manifest.json', manifest)
original_audit = audit_input(args.ame, args.project, out / 'audit-original', args.align_cyclic_from_ame)
if original_audit['parameterChanges']:
aligned = json.loads(args.project.read_bytes())
nodes = {node['id']: node for node in aligned['nodes']}
for row in original_audit['parameterChanges']:
nodes[row['component']]['data']['parameters'][row['parameter']] = row['expected']
aligned_path = out / 'aligned-source.json'
save(aligned_path, aligned)
save(out / 'alignment-changes.json', original_audit['parameterChanges'])
args.project = aligned_path
audits = {'standard': audit_input(args.ame, args.project, out / 'audit-aligned')}
if args.noncyclic_ame:
audits['noncyclic'] = audit_input(args.noncyclic_ame, args.noncyclic_project, out / 'audit-noncyclic')
summaries = {}
for name, settings in PROFILES.items():
if name not in args.profiles:
continue
if name == 'noncyclic' and not args.noncyclic_ame:
continue
stop, step, rtol = settings
ame_path = args.noncyclic_ame if name == 'noncyclic' else args.ame
project_path = args.noncyclic_project if name == 'noncyclic' else args.project
audit = audits['noncyclic' if name == 'noncyclic' else 'standard']
directory = out / name
directory.mkdir()
project = json.loads(project_path.read_bytes())
project['simulation'].update(t_start=0., t_stop=stop, step=step, max_step=1e30, method='BDF')
save(directory / 'platform.json', project)
prepare_ame(ame_path, directory / 'amesim', stop, step, rtol)
print(name, 'Amesim running', flush=True)
ame_summary = run_ame(directory / 'amesim', args.ame_home)
xml, document = load_input(directory / 'platform.json')
(directory / 'platform.xml').write_bytes(xml)
network = compile_system_xml_network(document)
program = compile_native_program(network)
print(name, 'current platform running', flush=True)
started = time.perf_counter()
# Isolate cache and result quotas from the user's live application.
with patch.object(builder, 'CACHE', out / 'build-cache'), \
patch.object(result_storage, 'RESULT_ROOT', out / 'result-storage'):
build = builder.build_native(program)
try:
result = execute_native(build, replace(simulation_config(document.simulation), rtol=rtol),
step, run_dir=directory / 'native', timeout=240)
native_summary = {k: v for k, v in result.items() if k not in ('series', 'final', 'finalState')}
native_summary.update(buildSeconds=build.seconds, buildCacheHit=build.cache_hit,
buildKey=build.manifest['buildKey'], pipelineWallSeconds=time.perf_counter()-started,
stateKeys=program.state_keys)
save(directory / 'native-summary.json', native_summary)
finally:
build.close()
summary = compare(directory, project, network, audit, settings)
summary.update(settings=dict(stop=stop, sampleStep=step, rtol=rtol),
amesimRun=ame_summary, nativeRun=native_summary)
summaries[name] = summary
save(out / 'summary.json', summaries)
print(name, 'completed', 'pressure=', summary['groups']['pressure']['worstAbsolute']['maxAbsoluteError'],
'temperature=', summary['groups']['temperature']['worstAbsolute']['maxAbsoluteError'],
'above5%=', summary['above5PercentCount'], flush=True)
unchanged = all(digest(Path(p)) == h for p, h in sources.items())
unchanged_code = all(digest(ROOT / p) == h for p, h in code.items())
save(out / 'source-verification.json', dict(inputsUnchanged=unchanged, productionUnchanged=unchanged_code,
checkedProductionFiles=len(code)))
assert unchanged and unchanged_code
print('All profiles completed; original inputs and production sources unchanged.', flush=True)
if __name__ == '__main__':
main()
+49
View File
@@ -0,0 +1,49 @@
"""Pair saved samples by forcing phase, never by the size of output errors.
All curves share ONE pair of row indices. No interpolation across jumps, no
time shifting, and no use of force/pressure agreement to select a sample.
The reference's nearest saved grid row is authoritative; a different native
row is allowed only in the tiny output-timestamp roundoff window.
"""
from __future__ import annotations
import numpy as np
def pair_saved_phases(native_times, reference_times, native_signals, reference_signals, grid, step):
nt, rt, grid = map(np.asarray, (native_times, reference_times, grid))
ns, rs = map(np.asarray, (native_signals, reference_signals))
if ns.shape != (len(nt), rs.shape[1]) or len(rs) != len(rt):
raise ValueError('Phase signatures must be rows by the same signal columns.')
tolerance = step * 1e-7
ni = np.full(len(grid), -1, dtype=int)
ri = np.full(len(grid), -1, dtype=int)
records = []
for j, t in enumerate(grid):
candidates = np.arange(np.searchsorted(nt, t-tolerance, side='left'),
np.searchsorted(nt, t+tolerance, side='right'))
references = np.arange(np.searchsorted(rt, t-tolerance, side='left'),
np.searchsorted(rt, t+tolerance, side='right'))
if not len(candidates) or not len(references):
records.append(dict(gridIndex=j, time=float(t), status='missing-saved-sample'))
continue
# Prefer the later row on an exact tie / duplicate timestamp, matching
# the comparison's existing right-side duplicate policy.
ref = min(references, key=lambda i: (abs(rt[i]-t), -int(i)))
closest = min(candidates, key=lambda i: (abs(nt[i]-t), -int(i)))
matches = candidates[np.all(np.isclose(ns[candidates], rs[ref], rtol=1e-12, atol=1e-12), axis=1)]
ri[j] = ref
if not len(matches):
records.append(dict(gridIndex=j, time=float(t), status='unmatched-forcing-phase',
referenceTime=float(rt[ref]), referenceSignals=rs[ref].tolist(),
nativeCandidateTimes=nt[candidates].tolist(),
nativeCandidateSignals=ns[candidates].tolist()))
continue
chosen = min(matches, key=lambda i: (abs(nt[i]-t), -int(i)))
ni[j] = chosen
if chosen != closest:
records.append(dict(gridIndex=j, time=float(t), status='matched-other-event-side',
platformTime=float(nt[chosen]), referenceTime=float(rt[ref]),
originalPlatformTime=float(nt[closest]),
signals=ns[chosen].tolist(), referenceSignals=rs[ref].tolist()))
return ni, ri, records
@@ -0,0 +1,618 @@
# 全部 fallback 区间及预算
由 `analyze_fallback_profitability.py` 从历史数据生成。ID、group、position 为0基,范围为[start,end)。ms为完整896个Jacobian折算累计;µs为每次fallback。按累计原计算时间排序。
P=probe总开销,H=每个Jacobian新增baseline捕获,m=结构上最多共享的失败组数。盈利要求 P+H/m<C;Hmax=m(C-P)。所有预算是严格上限,不代表实际可达到;负数表示不可能。m不是已验证成功次数,若只能组内独立捕获则m=1。
原区间时间、op时间和function时间来自不同抽样运行,不相加;纯代数是完全不含native调用的operation计时,native内部代数耗时未知。完整JSON另含全部5157条group/position映射、三轮原始折算值及函数表。
## 115个失败区间:成本和组成
| R / 范围 | group | 次数 | 累计ms | 均值µs / 三轮min–max | ops/native | op累计ms / 纯代数ms | 主要native | 主要function(exclusive排序) | m / 共享 | 选择 |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| R490 [49,184) | 24,25 | 1792 | 33.8066 | 18.865 / 18.618–19.236 | 135/15 | 34.5129 / 4.1388 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,property_pt,local_isentropic | 2 / 条件式 | C/E预算筛选;native默认A;D仅融合区间的待证假设 |
| R485 [50,181) | 22,23 | 1792 | 31.6665 | 17.671 / 17.360–18.206 | 131/14 | 32.1117 / 4.0718 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,property_pt,local_isentropic | 2 / 条件式 | C/E预算筛选;native默认A;D仅融合区间的待证假设 |
| R480 [51,178) | 20,21 | 1792 | 30.2693 | 16.891 / 16.279–17.565 | 127/13 | 30.2065 / 3.9538 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,property_pt,local_isentropic | 2 / 条件式 | C/E预算筛选;native默认A;D仅融合区间的待证假设 |
| R475 [52,175) | 18,19 | 1792 | 27.2575 | 15.211 / 15.121–15.350 | 123/12 | 28.1366 / 3.8413 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,property_pt,local_isentropic | 2 / 条件式 | C/E预算筛选;native默认A |
| R470 [53,172) | 16,17 | 1792 | 25.3815 | 14.164 / 14.078–14.304 | 119/11 | 25.6504 / 3.7122 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,property_pt,local_isentropic | 2 / 条件式 | C/E预算筛选;native默认A |
| R465 [54,169) | 14,15 | 1792 | 22.9364 | 12.799 / 12.606–13.072 | 115/10 | 23.6220 / 3.6083 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,property_pt,local_isentropic | 2 / 条件式 | C/E预算筛选;native默认A |
| R460 [55,166) | 12,13 | 1792 | 22.1173 | 12.342 / 11.636–13.553 | 111/9 | 21.8736 / 3.5983 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,property_pt,local_isentropic | 2 / 条件式 | C/E预算筛选;native默认A |
| R3 [30,48) | 0 | 896 | 21.3933 | 23.876 / 23.438–24.421 | 18/17 | 18.3418 / 0.0327 | native_medium_orifice_context,native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,property_pt,local_isentropic | 1 / 条件式 | C/E预算筛选;native默认A;D仅融合区间的待证假设 |
| R455 [56,161) | 10,11 | 1792 | 20.7353 | 11.571 / 11.279–12.104 | 105/8 | 20.2934 / 3.6539 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,property_pt,local_isentropic | 2 / 条件式 | C/E预算筛选;native默认A |
| R457 [297,444) | 10,11 | 1792 | 15.8804 | 8.862 / 8.421–9.626 | 147/4 | 14.8738 / 5.6530 | native_medium_orifice_context | state_valve,property_pt,local_isentropic | 2 / 条件式 | C/E预算筛选;native默认A |
| R49 [373,444) | 0 | 896 | 15.7818 | 17.614 / 16.941–18.203 | 71/4 | 6.5255 / 1.8142 | native_medium_orifice_context | state_valve,property_pt,native_temperature_ph_context | 1 / 条件式 | C/E预算筛选;native默认A |
| R5 [86,111) | 0,1 | 1792 | 14.7128 | 8.210 / 7.906–8.560 | 25/5 | 11.3355 / 0.8709 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,property_pt,local_isentropic | 2 / 条件式 | C/E预算筛选;native默认A |
| R58 [46,80) | 1 | 896 | 13.5515 | 15.124 / 14.891–15.414 | 34/10 | 10.9583 / 0.5253 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,property_pt,local_isentropic | 1 / 条件式 | C/E预算筛选;native默认A |
| R487 [321,456) | 22,23 | 1792 | 13.2545 | 7.396 / 6.833–8.073 | 135/4 | 13.2774 / 4.6048 | native_medium_orifice_context | state_valve,property_pt,local_isentropic | 2 / 条件式 | C/E预算筛选;native默认A |
| R477 [313,452) | 18,19 | 1792 | 13.1702 | 7.349 / 7.103–7.826 | 139/4 | 13.5595 / 4.7114 | native_medium_orifice_context | state_valve,property_pt,local_isentropic | 2 / 条件式 | C/E预算筛选;native默认A |
| R462 [301,446) | 12,13 | 1792 | 13.0502 | 7.282 / 7.100–7.603 | 145/4 | 13.7101 / 4.9268 | native_medium_orifice_context | state_valve,property_pt,local_isentropic | 2 / 条件式 | C/E预算筛选;native默认A |
| R492 [325,458) | 24,25 | 1792 | 12.9209 | 7.210 / 7.078–7.329 | 133/4 | 13.3904 / 4.6050 | native_medium_orifice_context | state_valve,property_pt,local_isentropic | 2 / 条件式 | C/E预算筛选;native默认A |
| R467 [305,448) | 14,15 | 1792 | 12.8537 | 7.173 / 6.967–7.424 | 143/4 | 13.8231 / 4.9066 | native_medium_orifice_context | state_valve,property_pt,local_isentropic | 2 / 条件式 | C/E预算筛选;native默认A |
| R472 [309,450) | 16,17 | 1792 | 12.7963 | 7.141 / 7.027–7.212 | 141/4 | 13.6274 / 4.7731 | native_medium_orifice_context | state_valve,property_pt,local_isentropic | 2 / 条件式 | C/E预算筛选;native默认A |
| R482 [317,454) | 20,21 | 1792 | 12.7099 | 7.093 / 6.945–7.229 | 137/4 | 13.5151 / 4.7408 | native_medium_orifice_context | state_valve,property_pt,local_isentropic | 2 / 条件式 | C/E预算筛选;native默认A |
| R2 [20,26) | 0,1 | 1792 | 11.0505 | 6.167 / 6.118–6.228 | 6/5 | 9.9409 / 0.0493 | native_medium_orifice_context,native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,local_isentropic,native_jacobian_scalar_get | 2 / 条件式 | C/E预算筛选;native默认A |
| R424 [43,53) | 9 | 896 | 10.5664 | 11.793 / 11.673–11.977 | 10/10 | 10.8827 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,property_pt,local_isentropic | 1 / 条件式 | C/E预算筛选;native默认A |
| R385 [56,88) | 8,9 | 1792 | 9.5786 | 5.345 / 5.320–5.379 | 32/4 | 9.5279 / 0.9872 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,property_pt,local_isentropic | 2 / 条件式 | C/E预算筛选;native默认A |
| R96 [88,108) | 2,3 | 1792 | 9.2071 | 5.138 / 4.932–5.448 | 20/4 | 8.6807 / 0.5910 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,property_pt,local_isentropic | 2 / 条件式 | C/E预算筛选;native默认A |
| R423 [32,41) | 9 | 896 | 9.2056 | 10.274 / 10.058–10.521 | 9/9 | 9.2194 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,local_isentropic,property_pt | 1 / 条件式 | C/E预算筛选;native默认A |
| R296 [54,89) | 6 | 896 | 8.0238 | 8.955 / 8.928–8.994 | 35/7 | 8.0117 / 0.5007 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,property_pt,local_isentropic | 1 / 条件式 | C/E预算筛选;native默认A |
| R139 [24,35) | 3 | 896 | 7.0459 | 7.864 / 7.746–8.008 | 11/7 | 7.2081 / 0.0871 | native_medium_orifice_context,native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,local_isentropic,property_pt | 1 / 条件式 | C/E预算筛选;native默认A |
| R94 [50,80) | 2 | 896 | 6.7602 | 7.545 / 7.516–7.592 | 30/6 | 6.6132 / 0.4611 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,property_pt,local_isentropic | 1 / 条件式 | C/E预算筛选;native默认A |
| R192 [47,50) | 4,8 | 1792 | 6.5404 | 3.650 / 3.604–3.731 | 3/3 | 6.4293 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,property_pt,local_isentropic | 2 / 条件式 | C/E预算筛选;native默认A |
| R144 [51,80) | 3 | 896 | 6.0869 | 6.793 / 6.430–7.421 | 29/5 | 5.5535 / 0.4702 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,property_pt,local_isentropic | 1 / 条件式 | C/E预算筛选;native默认A |
| R342 [55,88) | 7 | 896 | 5.8420 | 6.520 / 6.371–6.627 | 33/5 | 5.7814 / 0.4929 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,property_pt,local_isentropic | 1 / 条件式 | C/E预算筛选;native默认A |
| R89 [8,18) | 2 | 896 | 5.6644 | 6.322 / 6.209–6.498 | 10/6 | 5.4385 / 0.1128 | native_medium_orifice_context,native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,local_isentropic,native_jacobian_scalar_get | 1 / 条件式 | C/E预算筛选;native默认A |
| R74 [389,397) | 1,4,6,7 | 3584 | 5.5372 | 1.545 / 1.515–1.593 | 8/1 | 5.0806 / 0.5075 | native_medium_orifice_context | state_valve,property_pt,native_temperature_ph_context | 4 / 条件式 | C/E预算筛选;native默认A |
| R136 [6,17) | 3 | 896 | 5.4670 | 6.102 / 5.978–6.196 | 11/6 | 5.2067 / 0.1092 | native_medium_orifice_context,native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,local_isentropic,property_pt | 1 / 条件式 | C/E预算筛选;native默认A |
| R77 [402,410) | 1,2,6,7 | 3584 | 5.3570 | 1.495 / 1.473–1.534 | 8/1 | 5.4196 / 0.5316 | native_medium_orifice_context | state_valve,property_pt,native_temperature_ph_context | 4 / 条件式 | C/E预算筛选;native默认A |
| R420 [12,20) | 9 | 896 | 5.3023 | 5.918 / 5.827–6.073 | 8/6 | 5.4184 / 0.0345 | native_medium_orifice_context,native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,local_isentropic,native_jacobian_scalar_get | 1 / 条件式 | C/E预算筛选;native默认A |
| R336 [14,21) | 7 | 896 | 5.2899 | 5.904 / 5.798–5.958 | 7/6 | 5.2751 / 0.0181 | native_medium_orifice_context,native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,local_isentropic,native_jacobian_scalar_get | 1 / 条件式 | C/E预算筛选;native默认A |
| R381 [28,35) | 8 | 896 | 5.0225 | 5.605 / 5.552–5.662 | 7/5 | 5.0696 / 0.0357 | native_medium_orifice_context,native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,local_isentropic,property_pt | 1 / 条件式 | C/E预算筛选;native默认A |
| R141 [40,42) | 3,4 | 1792 | 4.9690 | 2.773 / 2.347–3.588 | 2/2 | 4.4399 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,local_isentropic,property_pt | 2 / 条件式 | C/E预算筛选;native默认A |
| R295 [49,51) | 6,7 | 1792 | 4.5347 | 2.531 / 2.468–2.568 | 2/2 | 4.3244 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,property_pt,local_isentropic | 2 / 条件式 | C/E预算筛选;native默认A |
| R57 [42,44) | 1,2 | 1792 | 4.4972 | 2.510 / 2.475–2.531 | 2/2 | 4.5279 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,property_pt,local_isentropic | 2 / 条件式 | C/E预算筛选;native默认A |
| R379 [14,20) | 8 | 896 | 4.3643 | 4.871 / 4.820–4.952 | 6/5 | 4.2811 / 0.0170 | native_medium_orifice_context,native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,local_isentropic,native_jacobian_scalar_get | 1 / 条件式 | C/E预算筛选;native默认A |
| R56 [38,40) | 1,2 | 1792 | 4.3340 | 2.419 / 2.349–2.529 | 2/2 | 4.1204 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,local_isentropic,property_pt | 2 / 条件式 | C/E预算筛选;native默认A |
| R193 [51,55) | 4 | 896 | 4.2716 | 4.767 / 4.536–5.218 | 4/4 | 4.1530 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,property_pt,local_isentropic | 1 / 条件式 | C/E预算筛选;native默认A |
| R294 [43,47) | 6 | 896 | 4.2505 | 4.744 / 4.720–4.772 | 4/4 | 4.2438 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,local_isentropic,property_pt | 1 / 条件式 | C/E预算筛选;native默认A |
| R91 [26,33) | 2 | 896 | 4.1915 | 4.678 / 4.591–4.850 | 7/4 | 4.0233 / 0.0780 | native_medium_orifice_context,native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,local_isentropic,native_jacobian_scalar_get | 1 / 条件式 | C/E预算筛选;native默认A |
| R383 [41,45) | 8 | 896 | 4.1668 | 4.650 / 4.609–4.716 | 4/4 | 4.1610 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,local_isentropic,property_pt | 1 / 条件式 | C/E预算筛选;native默认A |
| R339 [39,43) | 7 | 896 | 4.1581 | 4.641 / 4.603–4.712 | 4/4 | 4.1346 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,property_pt,local_isentropic | 1 / 条件式 | C/E预算筛选;native默认A |
| R237 [35,39) | 5 | 896 | 3.9920 | 4.455 / 4.437–4.471 | 4/4 | 4.0420 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,property_pt,local_isentropic | 1 / 条件式 | C/E预算筛选;native默认A |
| R190 [34,38) | 4 | 896 | 3.9683 | 4.429 / 4.385–4.486 | 4/4 | 3.9788 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,local_isentropic,property_pt | 1 / 条件式 | C/E预算筛选;native默认A |
| R234 [23,26) | 5,8 | 1792 | 3.9382 | 2.198 / 2.182–2.222 | 3/2 | 3.8424 / 0.0353 | native_medium_orifice_context,native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,local_isentropic,property_pt | 2 / 条件式 | C/E预算筛选;native默认A |
| R80 [415,423) | 1,2,3 | 2688 | 3.8738 | 1.441 / 1.365–1.567 | 8/1 | 3.5162 / 0.4025 | native_medium_orifice_context | state_valve,property_pt,native_medium_orifice_context | 3 / 条件式 | C/E预算筛选;native默认A |
| R242 [55,84) | 5 | 896 | 3.7439 | 4.178 / 4.171–4.190 | 29/3 | 3.7042 / 0.4658 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,property_pt,local_isentropic | 1 / 条件式 | C/E预算筛选;native默认A |
| R233 [17,21) | 5 | 896 | 3.5485 | 3.960 / 3.946–3.979 | 4/4 | 3.5745 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,local_isentropic,native_pipe_flow_context | 1 / 条件式 | C/E预算筛选;native默认A |
| R232 [8,16) | 5 | 896 | 3.4362 | 3.835 / 3.790–3.892 | 8/4 | 3.3525 / 0.0677 | native_medium_orifice_context | state_valve,local_isentropic,property_pt | 1 / 条件式 | C/E预算筛选;native默认A |
| R195 [88,91) | 4 | 896 | 3.0152 | 3.365 / 3.330–3.416 | 3/3 | 2.9500 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,property_pt,local_isentropic | 1 / 条件式 | C/E预算筛选;native默认A |
| R421 [21,24) | 9 | 896 | 2.9554 | 3.298 / 3.220–3.414 | 3/3 | 2.8324 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,local_isentropic,property_pt | 1 / 条件式 | C/E预算筛选;native默认A |
| R189 [26,32) | 4 | 896 | 2.9367 | 3.278 / 3.224–3.347 | 6/3 | 2.8788 / 0.0538 | native_medium_orifice_context | state_valve,local_isentropic,property_pt | 1 / 条件式 | C/E预算筛选;native默认A |
| R186 [8,14) | 4 | 896 | 2.6459 | 2.953 / 2.901–3.055 | 6/3 | 2.5282 / 0.0518 | native_medium_orifice_context | state_valve,local_isentropic,property_pt | 1 / 条件式 | C/E预算筛选;native默认A |
| R225 [402,423) | 4 | 896 | 2.5916 | 2.892 / 2.854–2.922 | 21/2 | 2.5976 / 0.3629 | native_medium_orifice_context | state_valve,property_pt,native_temperature_ph_context | 1 / 条件式 | C/E预算筛选;native默认A |
| R1 [18,19) | 0,1 | 1792 | 2.3721 | 1.324 / 1.290–1.377 | 1/1 | 1.8634 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,native_pipe_flow_context,local_isentropic | 2 / 条件式 | A |
| R291 [33,34) | 6,7 | 1792 | 2.3257 | 1.298 / 1.249–1.363 | 1/1 | 2.1592 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,local_isentropic,property_pt | 2 / 条件式 | A |
| R287 [10,14) | 6 | 896 | 2.2976 | 2.564 / 2.004–3.618 | 4/2 | 1.7574 / 0.0339 | native_medium_orifice_context | state_valve,local_isentropic,property_pt | 1 / 条件式 | C/E预算筛选;native默认A |
| R95 [84,86) | 2,4 | 1792 | 2.2118 | 1.234 / 1.205–1.274 | 2/1 | 2.0616 / 0.0426 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,property_pt,local_isentropic | 2 / 条件式 | C/E预算筛选;native默认A |
| R238 [41,43) | 5 | 896 | 2.2003 | 2.456 / 2.370–2.522 | 2/2 | 2.0701 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,local_isentropic,property_pt | 1 / 条件式 | C/E预算筛选;native默认A |
| R142 [44,46) | 3 | 896 | 2.1845 | 2.438 / 2.411–2.455 | 2/2 | 2.3678 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,property_pt,local_isentropic | 1 / 条件式 | C/E预算筛选;native默认A |
| R93 [46,48) | 2 | 896 | 2.1591 | 2.410 / 2.387–2.439 | 2/2 | 2.1160 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,property_pt,local_isentropic | 1 / 条件式 | C/E预算筛选;native默认A |
| R293 [39,41) | 6 | 896 | 2.1526 | 2.402 / 2.387–2.423 | 2/2 | 2.0647 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,local_isentropic,property_pt | 1 / 条件式 | C/E预算筛选;native默认A |
| R292 [36,37) | 6,7 | 1792 | 2.1458 | 1.197 / 1.193–1.200 | 1/1 | 2.1295 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,property_pt,local_isentropic | 2 / 条件式 | A |
| R340 [45,47) | 7 | 896 | 2.1315 | 2.379 / 2.367–2.400 | 2/2 | 2.1093 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,local_isentropic,property_pt | 1 / 条件式 | C/E预算筛选;native默认A |
| R382 [37,39) | 8 | 896 | 2.0939 | 2.337 / 2.313–2.352 | 2/2 | 2.0737 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,local_isentropic,property_pt | 1 / 条件式 | C/E预算筛选;native默认A |
| R241 [53,54) | 5,8 | 1792 | 2.0765 | 1.159 / 1.156–1.161 | 1/1 | 2.0091 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,property_pt,local_isentropic | 2 / 条件式 | A |
| R290 [24,28) | 6 | 896 | 2.0487 | 2.286 / 2.234–2.331 | 4/2 | 1.9819 / 0.0359 | native_medium_orifice_context | state_valve,local_isentropic,property_pt | 1 / 条件式 | C/E预算筛选;native默认A |
| R143 [48,49) | 3,5 | 1792 | 2.0442 | 1.141 / 1.133–1.150 | 1/1 | 1.9807 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,local_isentropic,property_pt | 2 / 条件式 | A |
| R138 [22,23) | 3,6 | 1792 | 2.0032 | 1.118 / 1.057–1.229 | 1/1 | 1.8615 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,local_isentropic,property_pt | 2 / 条件式 | A |
| R338 [26,30) | 7 | 896 | 1.9554 | 2.182 / 2.170–2.199 | 4/2 | 1.9396 / 0.0353 | native_medium_orifice_context | state_valve,local_isentropic,property_pt | 1 / 条件式 | C/E预算筛选;native默认A |
| R55 [34,36) | 1 | 896 | 1.9532 | 2.180 / 2.159–2.204 | 2/2 | 1.9671 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,local_isentropic,property_pt | 1 / 条件式 | C/E预算筛选;native默认A |
| R426 [91,123) | 9 | 896 | 1.9529 | 2.180 / 2.141–2.225 | 32/1 | 1.6566 / 0.6333 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,property_pt,local_isentropic | 1 / 条件式 | C/E预算筛选;native默认A |
| R90 [21,23) | 2 | 896 | 1.9157 | 2.138 / 2.095–2.207 | 2/2 | 1.9115 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,local_isentropic,native_jacobian_scalar_get | 1 / 条件式 | C/E预算筛选;native默认A |
| R188 [20,22) | 4 | 896 | 1.8858 | 2.105 / 2.069–2.152 | 2/2 | 1.8282 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,local_isentropic,native_pipe_flow_context | 1 / 条件式 | C/E预算筛选;native默认A |
| R178 [362,384) | 3 | 896 | 1.8337 | 2.047 / 1.982–2.093 | 22/1 | 1.6246 / 0.4219 | native_medium_orifice_context | state_valve,native_temperature_ph_context,property_pt | 1 / 条件式 | C/E预算筛选;native默认A |
| R289 [19,21) | 6 | 896 | 1.8062 | 2.016 / 1.985–2.041 | 2/2 | 1.7858 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,local_isentropic,native_pipe_flow_context | 1 / 条件式 | C/E预算筛选;native默认A |
| R129 [369,389) | 2 | 896 | 1.7436 | 1.946 / 1.904–1.983 | 20/1 | 1.5722 / 0.4017 | native_medium_orifice_context | state_valve,native_temperature_ph_context,property_pt | 1 / 条件式 | C/E预算筛选;native默认A |
| R185 [2,6) | 4 | 896 | 1.7273 | 1.928 / 1.917–1.936 | 4/2 | 1.6485 / 0.0434 | native_medium_orifice_context | state_valve,native_medium_orifice_context,local_isentropic | 1 / 条件式 | C/E预算筛选;native默认A |
| R194 [56,82) | 4 | 896 | 1.6724 | 1.866 / 1.840–1.880 | 26/1 | 1.6574 / 0.4726 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,property_pt,local_isentropic | 1 / 条件式 | C/E预算筛选;native默认A |
| R224 [369,384) | 4 | 896 | 1.6049 | 1.791 / 1.720–1.827 | 15/1 | 1.4050 / 0.2447 | native_medium_orifice_context | state_valve,native_temperature_ph_context,property_pt | 1 / 条件式 | C/E预算筛选;native默认A |
| R71 [373,384) | 1 | 896 | 1.5497 | 1.730 / 1.685–1.788 | 11/1 | 1.4107 / 0.1898 | native_medium_orifice_context | state_valve,native_temperature_ph_context,property_pt | 1 / 条件式 | C/E预算筛选;native默认A |
| R243 [91,109) | 5 | 896 | 1.5234 | 1.700 / 1.626–1.806 | 18/1 | 1.3512 / 0.3231 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,property_pt,local_isentropic | 1 / 条件式 | C/E预算筛选;native默认A |
| R179 [389,402) | 3 | 896 | 1.4651 | 1.635 / 1.605–1.660 | 13/1 | 1.4121 / 0.2499 | native_medium_orifice_context | state_valve,property_pt,local_isentropic | 1 / 条件式 | C/E预算筛选;native默认A |
| R279 [375,384) | 5 | 896 | 1.4309 | 1.597 / 1.582–1.614 | 9/1 | 1.3503 / 0.1494 | native_medium_orifice_context | state_valve,native_temperature_ph_context,property_pt | 1 / 条件式 | C/E预算筛选;native默认A |
| R281 [415,428) | 5 | 896 | 1.4079 | 1.571 / 1.522–1.650 | 13/1 | 1.3219 / 0.2408 | native_medium_orifice_context | state_valve,native_temperature_ph_context,native_medium_orifice_context | 1 / 条件式 | C/E预算筛选;native默认A |
| R239 [45,46) | 5 | 896 | 1.2200 | 1.362 / 1.319–1.447 | 1/1 | 1.1263 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,property_pt,local_isentropic | 1 / 条件式 | A |
| R145 [82,84) | 3 | 896 | 1.2033 | 1.343 / 1.329–1.361 | 2/1 | 1.1604 / 0.0232 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,property_pt,local_isentropic | 1 / 条件式 | C/E预算筛选;native默认A |
| R384 [51,52) | 8 | 896 | 1.1141 | 1.243 / 1.219–1.280 | 1/1 | 1.0539 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,property_pt,local_isentropic | 1 / 条件式 | A |
| R240 [50,51) | 5 | 896 | 1.1029 | 1.231 / 1.222–1.241 | 1/1 | 1.0630 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,property_pt,local_isentropic | 1 / 条件式 | A |
| R88 [4,6) | 2 | 896 | 1.1024 | 1.230 / 1.206–1.246 | 2/1 | 0.9525 / 0.0296 | native_medium_orifice_context | state_valve,native_temperature_ph_context,native_medium_orifice_context | 1 / 条件式 | C/E预算筛选;native默认A |
| R341 [52,53) | 7 | 896 | 1.0995 | 1.227 / 1.187–1.302 | 1/1 | 1.0267 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,property_pt,local_isentropic | 1 / 条件式 | A |
| R140 [37,38) | 3 | 896 | 1.0528 | 1.175 / 1.165–1.184 | 1/1 | 1.0092 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,local_isentropic,native_pipe_flow_context | 1 / 条件式 | A |
| R54 [30,32) | 1 | 896 | 1.0525 | 1.175 / 1.161–1.194 | 2/1 | 1.3184 / 0.0187 | native_medium_orifice_context | state_valve,native_temperature_ph_context,native_medium_orifice_context | 1 / 条件式 | C/E预算筛选;native默认A |
| R386 [90,91) | 8 | 896 | 1.0474 | 1.169 / 1.142–1.208 | 1/1 | 1.0043 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,property_pt,local_isentropic | 1 / 条件式 | A |
| R337 [23,24) | 7 | 896 | 1.0466 | 1.168 / 1.107–1.288 | 1/1 | 0.9678 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,native_temperature_ph_context,local_isentropic | 1 / 条件式 | A |
| R191 [44,45) | 4 | 896 | 1.0381 | 1.159 / 1.153–1.167 | 1/1 | 0.9958 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,native_pipe_flow_context,local_isentropic | 1 / 条件式 | A |
| R425 [54,55) | 9 | 896 | 1.0337 | 1.154 / 1.142–1.164 | 1/1 | 0.9993 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,property_pt,local_isentropic | 1 / 条件式 | A |
| R343 [89,90) | 7 | 896 | 1.0302 | 1.150 / 1.139–1.157 | 1/1 | 1.0029 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,property_pt,local_isentropic | 1 / 条件式 | A |
| R236 [32,33) | 5 | 896 | 1.0186 | 1.137 / 1.127–1.145 | 1/1 | 0.9803 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,local_isentropic,property_pt | 1 / 条件式 | A |
| R422 [26,28) | 9 | 896 | 0.9927 | 1.108 / 1.101–1.117 | 2/1 | 0.9591 / 0.0169 | native_medium_orifice_context | state_valve,property_pt,local_isentropic | 1 / 条件式 | C/E预算筛选;native默认A |
| R235 [28,30) | 5 | 896 | 0.9892 | 1.104 / 1.098–1.112 | 2/1 | 0.9939 / 0.0166 | native_medium_orifice_context | state_valve,local_isentropic,property_pt | 1 / 条件式 | C/E预算筛选;native默认A |
| R92 [35,36) | 2 | 896 | 0.9890 | 1.104 / 1.091–1.116 | 1/1 | 0.9631 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,local_isentropic,property_pt | 1 / 条件式 | A |
| R380 [21,22) | 8 | 896 | 0.9846 | 1.099 / 1.082–1.117 | 1/1 | 0.9415 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,local_isentropic,property_pt | 1 / 条件式 | A |
| R137 [19,20) | 3 | 896 | 0.9560 | 1.067 / 1.057–1.081 | 1/1 | 0.9219 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,local_isentropic,native_pipe_flow_context | 1 / 条件式 | A |
| R187 [17,18) | 4 | 896 | 0.9394 | 1.048 / 1.024–1.061 | 1/1 | 0.8927 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,local_isentropic,native_pipe_flow_context | 1 / 条件式 | A |
| R335 [10,12) | 7 | 896 | 0.9204 | 1.027 / 1.021–1.032 | 2/1 | 0.8715 / 0.0167 | native_medium_orifice_context | state_valve,native_temperature_ph_context,native_medium_orifice_context | 1 / 条件式 | C/E预算筛选;native默认A |
| R288 [16,17) | 6 | 896 | 0.9177 | 1.024 / 1.010–1.041 | 1/1 | 0.8781 / 0.0000 | native_pipe_flow_cached_context,native_temperature_ph_context | state_valve,local_isentropic,native_pipe_flow_context | 1 / 条件式 | A |
| R286 [6,8) | 6 | 896 | 0.9080 | 1.013 / 1.005–1.021 | 2/1 | 0.8447 / 0.0194 | native_medium_orifice_context | state_valve,native_medium_orifice_context,native_temperature_ph_context | 1 / 条件式 | C/E预算筛选;native默认A |
| R231 [2,4) | 5 | 896 | 0.8181 | 0.913 / 0.893–0.949 | 2/1 | 0.7453 / 0.0184 | native_medium_orifice_context | state_valve,native_medium_orifice_context,property_density | 1 / 条件式 | C/E预算筛选;native默认A |
## 每个区间的break-even边界
Ptyped、Htyped取R288最新批次中位数,仅作成本量级情景。区间预算假设一套融合机制处理整个区间,不能按每个native重复付P后仍使用本预算。
| R | Cµs | m | Hmax(P=0)µs | Hmax(P=2/5/10)µs | Pmax(Htyped)µs | Ptyped,H=0可行 | Ptyped,Htyped可行 | op模式也支持该情景 |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 490 | 18.865 | 2 | 37.731 | 33.731/27.731/17.731 | 9.736 | True | True | True |
| 485 | 17.671 | 2 | 35.342 | 31.342/25.342/15.342 | 8.542 | True | True | True |
| 480 | 16.891 | 2 | 33.783 | 29.783/23.783/13.783 | 7.763 | True | True | True |
| 475 | 15.211 | 2 | 30.421 | 26.421/20.421/10.421 | 6.082 | True | False | False |
| 470 | 14.164 | 2 | 28.328 | 24.328/18.328/8.328 | 5.035 | True | False | False |
| 465 | 12.799 | 2 | 25.599 | 21.599/15.599/5.599 | 3.671 | True | False | False |
| 460 | 12.342 | 2 | 24.685 | 20.685/14.685/4.685 | 3.213 | True | False | False |
| 3 | 23.876 | 1 | 23.876 | 21.876/18.876/13.876 | 5.619 | True | False | False |
| 455 | 11.571 | 2 | 23.142 | 19.142/13.142/3.142 | 2.442 | True | False | False |
| 457 | 8.862 | 2 | 17.724 | 13.724/7.724/-2.276 | -0.267 | True | False | False |
| 49 | 17.614 | 1 | 17.614 | 15.614/12.614/7.614 | -0.644 | True | False | False |
| 5 | 8.210 | 2 | 16.420 | 12.420/6.420/-3.580 | -0.919 | True | False | False |
| 58 | 15.124 | 1 | 15.124 | 13.124/10.124/5.124 | -3.133 | True | False | False |
| 487 | 7.396 | 2 | 14.793 | 10.793/4.793/-5.207 | -1.732 | True | False | False |
| 477 | 7.349 | 2 | 14.699 | 10.699/4.699/-5.301 | -1.779 | True | False | False |
| 462 | 7.282 | 2 | 14.565 | 10.565/4.565/-5.435 | -1.846 | True | False | False |
| 492 | 7.210 | 2 | 14.421 | 10.421/4.421/-5.579 | -1.919 | True | False | False |
| 467 | 7.173 | 2 | 14.346 | 10.346/4.346/-5.654 | -1.956 | True | False | False |
| 472 | 7.141 | 2 | 14.282 | 10.282/4.282/-5.718 | -1.988 | True | False | False |
| 482 | 7.093 | 2 | 14.185 | 10.185/4.185/-5.815 | -2.036 | False | False | False |
| 2 | 6.167 | 2 | 12.333 | 8.333/2.333/-7.667 | -2.962 | False | False | False |
| 424 | 11.793 | 1 | 11.793 | 9.793/6.793/1.793 | -6.465 | True | False | False |
| 385 | 5.345 | 2 | 10.690 | 6.690/0.690/-9.310 | -3.784 | False | False | False |
| 96 | 5.138 | 2 | 10.276 | 6.276/0.276/-9.724 | -3.991 | False | False | False |
| 423 | 10.274 | 1 | 10.274 | 8.274/5.274/0.274 | -7.984 | True | False | False |
| 296 | 8.955 | 1 | 8.955 | 6.955/3.955/-1.045 | -9.303 | True | False | False |
| 139 | 7.864 | 1 | 7.864 | 5.864/2.864/-2.136 | -10.394 | True | False | False |
| 94 | 7.545 | 1 | 7.545 | 5.545/2.545/-2.455 | -10.713 | True | False | False |
| 192 | 3.650 | 2 | 7.300 | 3.300/-2.700/-12.700 | -5.479 | False | False | False |
| 144 | 6.793 | 1 | 6.793 | 4.793/1.793/-3.207 | -11.464 | False | False | False |
| 342 | 6.520 | 1 | 6.520 | 4.520/1.520/-3.480 | -11.738 | False | False | False |
| 89 | 6.322 | 1 | 6.322 | 4.322/1.322/-3.678 | -11.936 | False | False | False |
| 74 | 1.545 | 4 | 6.180 | -1.820/-13.820/-33.820 | -3.019 | False | False | False |
| 136 | 6.102 | 1 | 6.102 | 4.102/1.102/-3.898 | -12.156 | False | False | False |
| 77 | 1.495 | 4 | 5.979 | -2.021/-14.021/-34.021 | -3.070 | False | False | False |
| 420 | 5.918 | 1 | 5.918 | 3.918/0.918/-4.082 | -12.340 | False | False | False |
| 336 | 5.904 | 1 | 5.904 | 3.904/0.904/-4.096 | -12.354 | False | False | False |
| 381 | 5.605 | 1 | 5.605 | 3.605/0.605/-4.395 | -12.652 | False | False | False |
| 141 | 2.773 | 2 | 5.546 | 1.546/-4.454/-14.454 | -6.356 | False | False | False |
| 295 | 2.531 | 2 | 5.061 | 1.061/-4.939/-14.939 | -6.598 | False | False | False |
| 57 | 2.510 | 2 | 5.019 | 1.019/-4.981/-14.981 | -6.619 | False | False | False |
| 379 | 4.871 | 1 | 4.871 | 2.871/-0.129/-5.129 | -13.387 | False | False | False |
| 56 | 2.419 | 2 | 4.837 | 0.837/-5.163/-15.163 | -6.710 | False | False | False |
| 193 | 4.767 | 1 | 4.767 | 2.767/-0.233/-5.233 | -13.490 | False | False | False |
| 294 | 4.744 | 1 | 4.744 | 2.744/-0.256/-5.256 | -13.514 | False | False | False |
| 91 | 4.678 | 1 | 4.678 | 2.678/-0.322/-5.322 | -13.580 | False | False | False |
| 383 | 4.650 | 1 | 4.650 | 2.650/-0.350/-5.350 | -13.607 | False | False | False |
| 339 | 4.641 | 1 | 4.641 | 2.641/-0.359/-5.359 | -13.617 | False | False | False |
| 237 | 4.455 | 1 | 4.455 | 2.455/-0.545/-5.545 | -13.802 | False | False | False |
| 190 | 4.429 | 1 | 4.429 | 2.429/-0.571/-5.571 | -13.829 | False | False | False |
| 234 | 2.198 | 2 | 4.395 | 0.395/-5.605/-15.605 | -6.931 | False | False | False |
| 80 | 1.441 | 3 | 4.323 | -1.677/-10.677/-25.677 | -4.645 | False | False | False |
| 242 | 4.178 | 1 | 4.178 | 2.178/-0.822/-5.822 | -14.079 | False | False | False |
| 233 | 3.960 | 1 | 3.960 | 1.960/-1.040/-6.040 | -14.297 | False | False | False |
| 232 | 3.835 | 1 | 3.835 | 1.835/-1.165/-6.165 | -14.423 | False | False | False |
| 195 | 3.365 | 1 | 3.365 | 1.365/-1.635/-6.635 | -14.893 | False | False | False |
| 421 | 3.298 | 1 | 3.298 | 1.298/-1.702/-6.702 | -14.959 | False | False | False |
| 189 | 3.278 | 1 | 3.278 | 1.278/-1.722/-6.722 | -14.980 | False | False | False |
| 186 | 2.953 | 1 | 2.953 | 0.953/-2.047/-7.047 | -15.305 | False | False | False |
| 225 | 2.892 | 1 | 2.892 | 0.892/-2.108/-7.108 | -15.365 | False | False | False |
| 1 | 1.324 | 2 | 2.647 | -1.353/-7.353/-17.353 | -7.805 | False | False | False |
| 291 | 1.298 | 2 | 2.596 | -1.404/-7.404/-17.404 | -7.831 | False | False | False |
| 287 | 2.564 | 1 | 2.564 | 0.564/-2.436/-7.436 | -15.693 | False | False | False |
| 95 | 1.234 | 2 | 2.469 | -1.531/-7.531/-17.531 | -7.895 | False | False | False |
| 238 | 2.456 | 1 | 2.456 | 0.456/-2.544/-7.544 | -15.802 | False | False | False |
| 142 | 2.438 | 1 | 2.438 | 0.438/-2.562/-7.562 | -15.820 | False | False | False |
| 93 | 2.410 | 1 | 2.410 | 0.410/-2.590/-7.590 | -15.848 | False | False | False |
| 293 | 2.402 | 1 | 2.402 | 0.402/-2.598/-7.598 | -15.855 | False | False | False |
| 292 | 1.197 | 2 | 2.395 | -1.605/-7.605/-17.605 | -7.931 | False | False | False |
| 340 | 2.379 | 1 | 2.379 | 0.379/-2.621/-7.621 | -15.879 | False | False | False |
| 382 | 2.337 | 1 | 2.337 | 0.337/-2.663/-7.663 | -15.921 | False | False | False |
| 241 | 1.159 | 2 | 2.318 | -1.682/-7.682/-17.682 | -7.970 | False | False | False |
| 290 | 2.286 | 1 | 2.286 | 0.286/-2.714/-7.714 | -15.971 | False | False | False |
| 143 | 1.141 | 2 | 2.282 | -1.718/-7.718/-17.718 | -7.988 | False | False | False |
| 138 | 1.118 | 2 | 2.236 | -1.764/-7.764/-17.764 | -8.011 | False | False | False |
| 338 | 2.182 | 1 | 2.182 | 0.182/-2.818/-7.818 | -16.075 | False | False | False |
| 55 | 2.180 | 1 | 2.180 | 0.180/-2.820/-7.820 | -16.078 | False | False | False |
| 426 | 2.180 | 1 | 2.180 | 0.180/-2.820/-7.820 | -16.078 | False | False | False |
| 90 | 2.138 | 1 | 2.138 | 0.138/-2.862/-7.862 | -16.120 | False | False | False |
| 188 | 2.105 | 1 | 2.105 | 0.105/-2.895/-7.895 | -16.153 | False | False | False |
| 178 | 2.047 | 1 | 2.047 | 0.047/-2.953/-7.953 | -16.211 | False | False | False |
| 289 | 2.016 | 1 | 2.016 | 0.016/-2.984/-7.984 | -16.242 | False | False | False |
| 129 | 1.946 | 1 | 1.946 | -0.054/-3.054/-8.054 | -16.312 | False | False | False |
| 185 | 1.928 | 1 | 1.928 | -0.072/-3.072/-8.072 | -16.330 | False | False | False |
| 194 | 1.866 | 1 | 1.866 | -0.134/-3.134/-8.134 | -16.391 | False | False | False |
| 224 | 1.791 | 1 | 1.791 | -0.209/-3.209/-8.209 | -16.466 | False | False | False |
| 71 | 1.730 | 1 | 1.730 | -0.270/-3.270/-8.270 | -16.528 | False | False | False |
| 243 | 1.700 | 1 | 1.700 | -0.300/-3.300/-8.300 | -16.558 | False | False | False |
| 179 | 1.635 | 1 | 1.635 | -0.365/-3.365/-8.365 | -16.623 | False | False | False |
| 279 | 1.597 | 1 | 1.597 | -0.403/-3.403/-8.403 | -16.661 | False | False | False |
| 281 | 1.571 | 1 | 1.571 | -0.429/-3.429/-8.429 | -16.686 | False | False | False |
| 239 | 1.362 | 1 | 1.362 | -0.638/-3.638/-8.638 | -16.896 | False | False | False |
| 145 | 1.343 | 1 | 1.343 | -0.657/-3.657/-8.657 | -16.915 | False | False | False |
| 384 | 1.243 | 1 | 1.243 | -0.757/-3.757/-8.757 | -17.014 | False | False | False |
| 240 | 1.231 | 1 | 1.231 | -0.769/-3.769/-8.769 | -17.027 | False | False | False |
| 88 | 1.230 | 1 | 1.230 | -0.770/-3.770/-8.770 | -17.027 | False | False | False |
| 341 | 1.227 | 1 | 1.227 | -0.773/-3.773/-8.773 | -17.031 | False | False | False |
| 140 | 1.175 | 1 | 1.175 | -0.825/-3.825/-8.825 | -17.083 | False | False | False |
| 54 | 1.175 | 1 | 1.175 | -0.825/-3.825/-8.825 | -17.083 | False | False | False |
| 386 | 1.169 | 1 | 1.169 | -0.831/-3.831/-8.831 | -17.089 | False | False | False |
| 337 | 1.168 | 1 | 1.168 | -0.832/-3.832/-8.832 | -17.090 | False | False | False |
| 191 | 1.159 | 1 | 1.159 | -0.841/-3.841/-8.841 | -17.099 | False | False | False |
| 425 | 1.154 | 1 | 1.154 | -0.846/-3.846/-8.846 | -17.104 | False | False | False |
| 343 | 1.150 | 1 | 1.150 | -0.850/-3.850/-8.850 | -17.108 | False | False | False |
| 236 | 1.137 | 1 | 1.137 | -0.863/-3.863/-8.863 | -17.121 | False | False | False |
| 422 | 1.108 | 1 | 1.108 | -0.892/-3.892/-8.892 | -17.150 | False | False | False |
| 235 | 1.104 | 1 | 1.104 | -0.896/-3.896/-8.896 | -17.154 | False | False | False |
| 92 | 1.104 | 1 | 1.104 | -0.896/-3.896/-8.896 | -17.154 | False | False | False |
| 380 | 1.099 | 1 | 1.099 | -0.901/-3.901/-8.901 | -17.159 | False | False | False |
| 137 | 1.067 | 1 | 1.067 | -0.933/-3.933/-8.933 | -17.191 | False | False | False |
| 187 | 1.048 | 1 | 1.048 | -0.952/-3.952/-8.952 | -17.209 | False | False | False |
| 335 | 1.027 | 1 | 1.027 | -0.973/-3.973/-8.973 | -17.230 | False | False | False |
| 288 | 1.024 | 1 | 1.024 | -0.976/-3.976/-8.976 | -17.233 | False | False | False |
| 286 | 1.013 | 1 | 1.013 | -0.987/-3.987/-8.987 | -17.244 | False | False | False |
| 231 | 0.913 | 1 | 0.913 | -1.087/-4.087/-9.087 | -17.345 | False | False | False |
## 全部156条group/interval成本
| group | R | 次数 | 原计算ms | µs/次 | ops/native | 纯代数ms | m上限 | Pmax(Htyped/m)µs |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 0 | 3 | 896 | 21.3933 | 23.876 | 18/17 | 0.0327 | 1 | 5.619 |
| 24 | 490 | 896 | 16.9520 | 18.920 | 135/15 | 2.0761 | 2 | 9.791 |
| 25 | 490 | 896 | 16.8546 | 18.811 | 135/15 | 2.0627 | 2 | 9.682 |
| 22 | 485 | 896 | 15.8714 | 17.714 | 131/14 | 2.0171 | 2 | 8.585 |
| 23 | 485 | 896 | 15.7951 | 17.628 | 131/14 | 2.0546 | 2 | 8.500 |
| 0 | 49 | 896 | 15.7818 | 17.614 | 71/4 | 1.8142 | 1 | -0.644 |
| 21 | 480 | 896 | 15.1847 | 16.947 | 127/13 | 1.9563 | 2 | 7.818 |
| 20 | 480 | 896 | 15.0846 | 16.835 | 127/13 | 1.9974 | 2 | 7.707 |
| 18 | 475 | 896 | 13.7850 | 15.385 | 123/12 | 1.9185 | 2 | 6.256 |
| 1 | 58 | 896 | 13.5515 | 15.124 | 34/10 | 0.5253 | 1 | -3.133 |
| 19 | 475 | 896 | 13.4725 | 15.036 | 123/12 | 1.9228 | 2 | 5.907 |
| 16 | 470 | 896 | 12.8265 | 14.315 | 119/11 | 1.8549 | 2 | 5.186 |
| 17 | 470 | 896 | 12.5550 | 14.012 | 119/11 | 1.8573 | 2 | 4.883 |
| 15 | 465 | 896 | 11.5035 | 12.839 | 115/10 | 1.8024 | 2 | 3.710 |
| 14 | 465 | 896 | 11.4329 | 12.760 | 115/10 | 1.8059 | 2 | 3.631 |
| 13 | 460 | 896 | 11.2592 | 12.566 | 111/9 | 1.7614 | 2 | 3.437 |
| 12 | 460 | 896 | 10.8581 | 12.118 | 111/9 | 1.8369 | 2 | 2.990 |
| 10 | 455 | 896 | 10.6493 | 11.885 | 105/8 | 1.9083 | 2 | 2.757 |
| 9 | 424 | 896 | 10.5664 | 11.793 | 10/10 | 0.0000 | 1 | -6.465 |
| 11 | 455 | 896 | 10.0860 | 11.257 | 105/8 | 1.7455 | 2 | 2.128 |
| 9 | 423 | 896 | 9.2056 | 10.274 | 9/9 | 0.0000 | 1 | -7.984 |
| 0 | 5 | 896 | 8.9984 | 10.043 | 25/5 | 0.4921 | 2 | 0.914 |
| 10 | 457 | 896 | 8.7589 | 9.776 | 147/4 | 2.9745 | 2 | 0.647 |
| 6 | 296 | 896 | 8.0238 | 8.955 | 35/7 | 0.5007 | 1 | -9.303 |
| 11 | 457 | 896 | 7.1215 | 7.948 | 147/4 | 2.6785 | 2 | -1.181 |
| 3 | 139 | 896 | 7.0459 | 7.864 | 11/7 | 0.0871 | 1 | -10.394 |
| 23 | 487 | 896 | 6.7934 | 7.582 | 135/4 | 2.3194 | 2 | -1.547 |
| 2 | 94 | 896 | 6.7602 | 7.545 | 30/6 | 0.4611 | 1 | -10.713 |
| 19 | 477 | 896 | 6.6828 | 7.459 | 139/4 | 2.3443 | 2 | -1.670 |
| 12 | 462 | 896 | 6.6416 | 7.413 | 145/4 | 2.4650 | 2 | -1.716 |
| 15 | 467 | 896 | 6.5325 | 7.291 | 143/4 | 2.4717 | 2 | -1.838 |
| 25 | 492 | 896 | 6.5022 | 7.257 | 133/4 | 2.3466 | 2 | -1.872 |
| 16 | 472 | 896 | 6.4933 | 7.247 | 141/4 | 2.3687 | 2 | -1.882 |
| 18 | 477 | 896 | 6.4874 | 7.240 | 139/4 | 2.3671 | 2 | -1.888 |
| 22 | 487 | 896 | 6.4611 | 7.211 | 135/4 | 2.2854 | 2 | -1.918 |
| 24 | 492 | 896 | 6.4187 | 7.164 | 133/4 | 2.2585 | 2 | -1.965 |
| 13 | 462 | 896 | 6.4086 | 7.152 | 145/4 | 2.4618 | 2 | -1.976 |
| 20 | 482 | 896 | 6.3635 | 7.102 | 137/4 | 2.4323 | 2 | -2.027 |
| 21 | 482 | 896 | 6.3464 | 7.083 | 137/4 | 2.3085 | 2 | -2.046 |
| 14 | 467 | 896 | 6.3212 | 7.055 | 143/4 | 2.4350 | 2 | -2.074 |
| 17 | 472 | 896 | 6.3031 | 7.035 | 141/4 | 2.4044 | 2 | -2.094 |
| 0 | 2 | 896 | 6.1449 | 6.858 | 6/5 | 0.0297 | 2 | -2.271 |
| 3 | 144 | 896 | 6.0869 | 6.793 | 29/5 | 0.4702 | 1 | -11.464 |
| 7 | 342 | 896 | 5.8420 | 6.520 | 33/5 | 0.4929 | 1 | -11.738 |
| 1 | 5 | 896 | 5.7144 | 6.378 | 25/5 | 0.3788 | 2 | -2.751 |
| 2 | 89 | 896 | 5.6644 | 6.322 | 10/6 | 0.1128 | 1 | -11.936 |
| 3 | 136 | 896 | 5.4670 | 6.102 | 11/6 | 0.1092 | 1 | -12.156 |
| 9 | 420 | 896 | 5.3023 | 5.918 | 8/6 | 0.0345 | 1 | -12.340 |
| 7 | 336 | 896 | 5.2899 | 5.904 | 7/6 | 0.0181 | 1 | -12.354 |
| 8 | 381 | 896 | 5.0225 | 5.605 | 7/5 | 0.0357 | 1 | -12.652 |
| 1 | 2 | 896 | 4.9056 | 5.475 | 6/5 | 0.0195 | 2 | -3.654 |
| 8 | 385 | 896 | 4.8364 | 5.398 | 32/4 | 0.5041 | 2 | -3.731 |
| 2 | 96 | 896 | 4.8297 | 5.390 | 20/4 | 0.2967 | 2 | -3.739 |
| 9 | 385 | 896 | 4.7423 | 5.293 | 32/4 | 0.4831 | 2 | -3.836 |
| 3 | 96 | 896 | 4.3775 | 4.886 | 20/4 | 0.2943 | 2 | -4.243 |
| 8 | 379 | 896 | 4.3643 | 4.871 | 6/5 | 0.0170 | 1 | -13.387 |
| 4 | 193 | 896 | 4.2716 | 4.767 | 4/4 | 0.0000 | 1 | -13.490 |
| 6 | 294 | 896 | 4.2505 | 4.744 | 4/4 | 0.0000 | 1 | -13.514 |
| 2 | 91 | 896 | 4.1915 | 4.678 | 7/4 | 0.0780 | 1 | -13.580 |
| 8 | 383 | 896 | 4.1668 | 4.650 | 4/4 | 0.0000 | 1 | -13.607 |
| 7 | 339 | 896 | 4.1581 | 4.641 | 4/4 | 0.0000 | 1 | -13.617 |
| 5 | 237 | 896 | 3.9920 | 4.455 | 4/4 | 0.0000 | 1 | -13.802 |
| 4 | 190 | 896 | 3.9683 | 4.429 | 4/4 | 0.0000 | 1 | -13.829 |
| 5 | 242 | 896 | 3.7439 | 4.178 | 29/3 | 0.4658 | 1 | -14.079 |
| 5 | 233 | 896 | 3.5485 | 3.960 | 4/4 | 0.0000 | 1 | -14.297 |
| 5 | 232 | 896 | 3.4362 | 3.835 | 8/4 | 0.0677 | 1 | -14.423 |
| 4 | 192 | 896 | 3.2727 | 3.653 | 3/3 | 0.0000 | 2 | -5.476 |
| 8 | 192 | 896 | 3.2677 | 3.647 | 3/3 | 0.0000 | 2 | -5.482 |
| 4 | 195 | 896 | 3.0152 | 3.365 | 3/3 | 0.0000 | 1 | -14.893 |
| 9 | 421 | 896 | 2.9554 | 3.298 | 3/3 | 0.0000 | 1 | -14.959 |
| 4 | 189 | 896 | 2.9367 | 3.278 | 6/3 | 0.0538 | 1 | -14.980 |
| 3 | 141 | 896 | 2.8715 | 3.205 | 2/2 | 0.0000 | 2 | -5.924 |
| 4 | 186 | 896 | 2.6459 | 2.953 | 6/3 | 0.0518 | 1 | -15.305 |
| 4 | 225 | 896 | 2.5916 | 2.892 | 21/2 | 0.3629 | 1 | -15.365 |
| 7 | 295 | 896 | 2.3380 | 2.609 | 2/2 | 0.0000 | 2 | -6.519 |
| 6 | 287 | 896 | 2.2976 | 2.564 | 4/2 | 0.0339 | 1 | -15.693 |
| 1 | 57 | 896 | 2.2575 | 2.520 | 2/2 | 0.0000 | 2 | -6.609 |
| 2 | 57 | 896 | 2.2397 | 2.500 | 2/2 | 0.0000 | 2 | -6.629 |
| 5 | 238 | 896 | 2.2003 | 2.456 | 2/2 | 0.0000 | 1 | -15.802 |
| 6 | 295 | 896 | 2.1968 | 2.452 | 2/2 | 0.0000 | 2 | -6.677 |
| 3 | 142 | 896 | 2.1845 | 2.438 | 2/2 | 0.0000 | 1 | -15.820 |
| 2 | 56 | 896 | 2.1837 | 2.437 | 2/2 | 0.0000 | 2 | -6.692 |
| 2 | 93 | 896 | 2.1591 | 2.410 | 2/2 | 0.0000 | 1 | -15.848 |
| 6 | 293 | 896 | 2.1526 | 2.402 | 2/2 | 0.0000 | 1 | -15.855 |
| 1 | 56 | 896 | 2.1503 | 2.400 | 2/2 | 0.0000 | 2 | -6.729 |
| 7 | 340 | 896 | 2.1315 | 2.379 | 2/2 | 0.0000 | 1 | -15.879 |
| 4 | 141 | 896 | 2.0975 | 2.341 | 2/2 | 0.0000 | 2 | -6.788 |
| 8 | 382 | 896 | 2.0939 | 2.337 | 2/2 | 0.0000 | 1 | -15.921 |
| 6 | 290 | 896 | 2.0487 | 2.286 | 4/2 | 0.0359 | 1 | -15.971 |
| 5 | 234 | 896 | 1.9742 | 2.203 | 3/2 | 0.0180 | 2 | -6.926 |
| 8 | 234 | 896 | 1.9641 | 2.192 | 3/2 | 0.0173 | 2 | -6.937 |
| 7 | 338 | 896 | 1.9554 | 2.182 | 4/2 | 0.0353 | 1 | -16.075 |
| 1 | 55 | 896 | 1.9532 | 2.180 | 2/2 | 0.0000 | 1 | -16.078 |
| 9 | 426 | 896 | 1.9529 | 2.180 | 32/1 | 0.6333 | 1 | -16.078 |
| 2 | 90 | 896 | 1.9157 | 2.138 | 2/2 | 0.0000 | 1 | -16.120 |
| 4 | 188 | 896 | 1.8858 | 2.105 | 2/2 | 0.0000 | 1 | -16.153 |
| 3 | 178 | 896 | 1.8337 | 2.047 | 22/1 | 0.4219 | 1 | -16.211 |
| 6 | 289 | 896 | 1.8062 | 2.016 | 2/2 | 0.0000 | 1 | -16.242 |
| 2 | 129 | 896 | 1.7436 | 1.946 | 20/1 | 0.4017 | 1 | -16.312 |
| 4 | 185 | 896 | 1.7273 | 1.928 | 4/2 | 0.0434 | 1 | -16.330 |
| 4 | 194 | 896 | 1.6724 | 1.866 | 26/1 | 0.4726 | 1 | -16.391 |
| 4 | 224 | 896 | 1.6049 | 1.791 | 15/1 | 0.2447 | 1 | -16.466 |
| 1 | 71 | 896 | 1.5497 | 1.730 | 11/1 | 0.1898 | 1 | -16.528 |
| 5 | 243 | 896 | 1.5234 | 1.700 | 18/1 | 0.3231 | 1 | -16.558 |
| 3 | 179 | 896 | 1.4651 | 1.635 | 13/1 | 0.2499 | 1 | -16.623 |
| 6 | 74 | 896 | 1.4566 | 1.626 | 8/1 | 0.1271 | 4 | -2.939 |
| 7 | 74 | 896 | 1.4445 | 1.612 | 8/1 | 0.1211 | 4 | -2.952 |
| 5 | 279 | 896 | 1.4309 | 1.597 | 9/1 | 0.1494 | 1 | -16.661 |
| 2 | 77 | 896 | 1.4098 | 1.573 | 8/1 | 0.1430 | 4 | -2.991 |
| 5 | 281 | 896 | 1.4079 | 1.571 | 13/1 | 0.2408 | 1 | -16.686 |
| 1 | 74 | 896 | 1.3865 | 1.547 | 8/1 | 0.1382 | 4 | -3.017 |
| 1 | 77 | 896 | 1.3560 | 1.513 | 8/1 | 0.1433 | 4 | -3.051 |
| 1 | 80 | 896 | 1.3495 | 1.506 | 8/1 | 0.1368 | 3 | -4.580 |
| 0 | 1 | 896 | 1.3383 | 1.494 | 1/1 | 0.0000 | 2 | -7.635 |
| 6 | 77 | 896 | 1.3075 | 1.459 | 8/1 | 0.1253 | 4 | -3.105 |
| 3 | 80 | 896 | 1.2856 | 1.435 | 8/1 | 0.1277 | 3 | -4.651 |
| 7 | 77 | 896 | 1.2837 | 1.433 | 8/1 | 0.1201 | 4 | -3.132 |
| 4 | 74 | 896 | 1.2496 | 1.395 | 8/1 | 0.1212 | 4 | -3.170 |
| 2 | 80 | 896 | 1.2388 | 1.383 | 8/1 | 0.1380 | 3 | -4.703 |
| 5 | 239 | 896 | 1.2200 | 1.362 | 1/1 | 0.0000 | 1 | -16.896 |
| 7 | 291 | 896 | 1.2061 | 1.346 | 1/1 | 0.0000 | 2 | -7.783 |
| 3 | 145 | 896 | 1.2033 | 1.343 | 2/1 | 0.0232 | 1 | -16.915 |
| 2 | 95 | 896 | 1.1244 | 1.255 | 2/1 | 0.0246 | 2 | -7.874 |
| 6 | 291 | 896 | 1.1197 | 1.250 | 1/1 | 0.0000 | 2 | -7.879 |
| 8 | 384 | 896 | 1.1141 | 1.243 | 1/1 | 0.0000 | 1 | -17.014 |
| 5 | 240 | 896 | 1.1029 | 1.231 | 1/1 | 0.0000 | 1 | -17.027 |
| 2 | 88 | 896 | 1.1024 | 1.230 | 2/1 | 0.0296 | 1 | -17.027 |
| 7 | 341 | 896 | 1.0995 | 1.227 | 1/1 | 0.0000 | 1 | -17.031 |
| 4 | 95 | 896 | 1.0874 | 1.214 | 2/1 | 0.0180 | 2 | -7.915 |
| 6 | 292 | 896 | 1.0769 | 1.202 | 1/1 | 0.0000 | 2 | -7.927 |
| 7 | 292 | 896 | 1.0689 | 1.193 | 1/1 | 0.0000 | 2 | -7.936 |
| 3 | 140 | 896 | 1.0528 | 1.175 | 1/1 | 0.0000 | 1 | -17.083 |
| 1 | 54 | 896 | 1.0525 | 1.175 | 2/1 | 0.0187 | 1 | -17.083 |
| 8 | 386 | 896 | 1.0474 | 1.169 | 1/1 | 0.0000 | 1 | -17.089 |
| 7 | 337 | 896 | 1.0466 | 1.168 | 1/1 | 0.0000 | 1 | -17.090 |
| 8 | 241 | 896 | 1.0383 | 1.159 | 1/1 | 0.0000 | 2 | -7.970 |
| 5 | 241 | 896 | 1.0383 | 1.159 | 1/1 | 0.0000 | 2 | -7.970 |
| 4 | 191 | 896 | 1.0381 | 1.159 | 1/1 | 0.0000 | 1 | -17.099 |
| 1 | 1 | 896 | 1.0338 | 1.154 | 1/1 | 0.0000 | 2 | -7.975 |
| 9 | 425 | 896 | 1.0337 | 1.154 | 1/1 | 0.0000 | 1 | -17.104 |
| 7 | 343 | 896 | 1.0302 | 1.150 | 1/1 | 0.0000 | 1 | -17.108 |
| 3 | 143 | 896 | 1.0268 | 1.146 | 1/1 | 0.0000 | 2 | -7.983 |
| 3 | 138 | 896 | 1.0242 | 1.143 | 1/1 | 0.0000 | 2 | -7.986 |
| 5 | 236 | 896 | 1.0186 | 1.137 | 1/1 | 0.0000 | 1 | -17.121 |
| 5 | 143 | 896 | 1.0174 | 1.135 | 1/1 | 0.0000 | 2 | -7.993 |
| 9 | 422 | 896 | 0.9927 | 1.108 | 2/1 | 0.0169 | 1 | -17.150 |
| 5 | 235 | 896 | 0.9892 | 1.104 | 2/1 | 0.0166 | 1 | -17.154 |
| 2 | 92 | 896 | 0.9890 | 1.104 | 1/1 | 0.0000 | 1 | -17.154 |
| 8 | 380 | 896 | 0.9846 | 1.099 | 1/1 | 0.0000 | 1 | -17.159 |
| 6 | 138 | 896 | 0.9791 | 1.093 | 1/1 | 0.0000 | 2 | -8.036 |
| 3 | 137 | 896 | 0.9560 | 1.067 | 1/1 | 0.0000 | 1 | -17.191 |
| 4 | 187 | 896 | 0.9394 | 1.048 | 1/1 | 0.0000 | 1 | -17.209 |
| 7 | 335 | 896 | 0.9204 | 1.027 | 2/1 | 0.0167 | 1 | -17.230 |
| 6 | 288 | 896 | 0.9177 | 1.024 | 1/1 | 0.0000 | 1 | -17.233 |
| 6 | 286 | 896 | 0.9080 | 1.013 | 2/1 | 0.0194 | 1 | -17.244 |
| 5 | 231 | 896 | 0.8181 | 0.913 | 2/1 | 0.0184 | 1 | -17.345 |
## 全部纯代数连续段(区间内切分候选)
| R | group | 范围 | ops | 执行次数 | 累计ms | 每段µs / 允许的最大新增成本 |
| --- | --- | --- | --- | --- | --- | --- |
| 457 | 10,11 | [297,379) | 82 | 1792 | 3.3077 | 1.846 |
| 490 | 24,25 | [92,184) | 92 | 1792 | 3.2223 | 1.798 |
| 485 | 22,23 | [92,181) | 89 | 1792 | 3.1546 | 1.760 |
| 480 | 20,21 | [92,178) | 86 | 1792 | 3.0110 | 1.680 |
| 475 | 18,19 | [92,175) | 83 | 1792 | 2.9102 | 1.624 |
| 470 | 16,17 | [92,172) | 80 | 1792 | 2.7958 | 1.560 |
| 465 | 14,15 | [92,169) | 77 | 1792 | 2.6935 | 1.503 |
| 455 | 10,11 | [92,161) | 69 | 1792 | 2.6903 | 1.501 |
| 460 | 12,13 | [92,166) | 74 | 1792 | 2.6698 | 1.490 |
| 462 | 12,13 | [301,379) | 78 | 1792 | 2.6357 | 1.471 |
| 467 | 14,15 | [305,379) | 74 | 1792 | 2.4664 | 1.376 |
| 472 | 16,17 | [309,379) | 70 | 1792 | 2.3281 | 1.299 |
| 477 | 18,19 | [313,379) | 66 | 1792 | 2.1989 | 1.227 |
| 482 | 20,21 | [317,379) | 62 | 1792 | 2.0955 | 1.169 |
| 487 | 22,23 | [321,379) | 58 | 1792 | 1.9230 | 1.073 |
| 492 | 24,25 | [325,379) | 54 | 1792 | 1.7984 | 1.004 |
| 492 | 24,25 | [419,458) | 39 | 1792 | 1.4920 | 0.833 |
| 487 | 22,23 | [419,456) | 37 | 1792 | 1.3685 | 0.764 |
| 482 | 20,21 | [419,454) | 35 | 1792 | 1.3025 | 0.727 |
| 477 | 18,19 | [419,452) | 33 | 1792 | 1.2131 | 0.677 |
| 472 | 16,17 | [419,450) | 31 | 1792 | 1.1501 | 0.642 |
| 467 | 14,15 | [419,448) | 29 | 1792 | 1.0842 | 0.605 |
| 457 | 10,11 | [419,444) | 25 | 1792 | 0.9992 | 0.558 |
| 462 | 12,13 | [419,446) | 27 | 1792 | 0.9883 | 0.552 |
| 385 | 8,9 | [56,80) | 24 | 1792 | 0.8584 | 0.479 |
| 455 | 10,11 | [56,80) | 24 | 1792 | 0.8358 | 0.466 |
| 5 | 0,1 | [92,111) | 19 | 1792 | 0.8188 | 0.457 |
| 480 | 20,21 | [56,80) | 24 | 1792 | 0.8166 | 0.456 |
| 475 | 18,19 | [56,80) | 24 | 1792 | 0.8055 | 0.449 |
| 460 | 12,13 | [56,80) | 24 | 1792 | 0.8024 | 0.448 |
| 485 | 22,23 | [56,80) | 24 | 1792 | 0.7914 | 0.442 |
| 470 | 16,17 | [56,80) | 24 | 1792 | 0.7909 | 0.441 |
| 490 | 24,25 | [56,80) | 24 | 1792 | 0.7907 | 0.441 |
| 465 | 14,15 | [56,80) | 24 | 1792 | 0.7892 | 0.440 |
| 49 | 0 | [419,444) | 25 | 896 | 0.7564 | 0.844 |
| 426 | 9 | [92,123) | 31 | 896 | 0.6333 | 0.707 |
| 96 | 2,3 | [92,108) | 16 | 1792 | 0.5910 | 0.330 |
| 58 | 1 | [56,80) | 24 | 896 | 0.5253 | 0.586 |
| 482 | 20,21 | [406,418) | 12 | 1792 | 0.4822 | 0.269 |
| 144 | 3 | [56,80) | 24 | 896 | 0.4702 | 0.525 |
| 94 | 2 | [56,80) | 24 | 896 | 0.4611 | 0.515 |
| 467 | 14,15 | [393,405) | 12 | 1792 | 0.4566 | 0.255 |
| 492 | 24,25 | [380,392) | 12 | 1792 | 0.4551 | 0.254 |
| 467 | 14,15 | [380,392) | 12 | 1792 | 0.4548 | 0.254 |
| 194 | 4 | [56,80) | 24 | 896 | 0.4518 | 0.504 |
| 457 | 10,11 | [393,405) | 12 | 1792 | 0.4509 | 0.252 |
| 457 | 10,11 | [380,392) | 12 | 1792 | 0.4505 | 0.251 |
| 487 | 22,23 | [380,392) | 12 | 1792 | 0.4502 | 0.251 |
| 467 | 14,15 | [406,418) | 12 | 1792 | 0.4447 | 0.248 |
| 457 | 10,11 | [406,418) | 12 | 1792 | 0.4447 | 0.248 |
| 477 | 18,19 | [406,418) | 12 | 1792 | 0.4357 | 0.243 |
| 462 | 12,13 | [406,418) | 12 | 1792 | 0.4355 | 0.243 |
| 472 | 16,17 | [406,418) | 12 | 1792 | 0.4352 | 0.243 |
| 487 | 22,23 | [406,418) | 12 | 1792 | 0.4341 | 0.242 |
| 462 | 12,13 | [380,392) | 12 | 1792 | 0.4337 | 0.242 |
| 462 | 12,13 | [393,405) | 12 | 1792 | 0.4336 | 0.242 |
| 477 | 18,19 | [393,405) | 12 | 1792 | 0.4319 | 0.241 |
| 477 | 18,19 | [380,392) | 12 | 1792 | 0.4318 | 0.241 |
| 482 | 20,21 | [380,392) | 12 | 1792 | 0.4309 | 0.240 |
| 472 | 16,17 | [380,392) | 12 | 1792 | 0.4309 | 0.240 |
| 492 | 24,25 | [406,418) | 12 | 1792 | 0.4302 | 0.240 |
| 296 | 6 | [56,80) | 24 | 896 | 0.4301 | 0.480 |
| 242 | 5 | [56,80) | 24 | 896 | 0.4301 | 0.480 |
| 482 | 20,21 | [393,405) | 12 | 1792 | 0.4297 | 0.240 |
| 492 | 24,25 | [393,405) | 12 | 1792 | 0.4293 | 0.240 |
| 487 | 22,23 | [393,405) | 12 | 1792 | 0.4289 | 0.239 |
| 472 | 16,17 | [393,405) | 12 | 1792 | 0.4288 | 0.239 |
| 342 | 7 | [56,80) | 24 | 896 | 0.4261 | 0.476 |
| 178 | 3 | [362,379) | 17 | 896 | 0.3506 | 0.391 |
| 243 | 5 | [92,109) | 17 | 896 | 0.3231 | 0.361 |
| 49 | 0 | [393,405) | 12 | 896 | 0.3148 | 0.351 |
| 49 | 0 | [380,392) | 12 | 896 | 0.3013 | 0.336 |
| 77 | 1,2,6,7 | [406,410) | 4 | 3584 | 0.3006 | 0.084 |
| 49 | 0 | [406,418) | 12 | 896 | 0.2995 | 0.334 |
| 74 | 1,4,6,7 | [393,397) | 4 | 3584 | 0.2893 | 0.081 |
| 225 | 4 | [406,418) | 12 | 896 | 0.2406 | 0.269 |
| 77 | 1,2,6,7 | [402,405) | 3 | 3584 | 0.2310 | 0.064 |
| 80 | 1,2,3 | [419,423) | 4 | 2688 | 0.2248 | 0.084 |
| 74 | 1,4,6,7 | [389,392) | 3 | 3584 | 0.2182 | 0.061 |
| 129 | 2 | [369,379) | 10 | 896 | 0.2139 | 0.239 |
| 179 | 3 | [393,402) | 9 | 896 | 0.1948 | 0.217 |
| 281 | 5 | [419,428) | 9 | 896 | 0.1882 | 0.210 |
| 129 | 2 | [380,389) | 9 | 896 | 0.1878 | 0.210 |
| 80 | 1,2,3 | [415,418) | 3 | 2688 | 0.1777 | 0.066 |
| 224 | 4 | [369,379) | 10 | 896 | 0.1752 | 0.196 |
| 49 | 0 | [373,379) | 6 | 896 | 0.1422 | 0.159 |
| 71 | 1 | [373,379) | 6 | 896 | 0.1153 | 0.129 |
| 279 | 5 | [375,379) | 4 | 896 | 0.0785 | 0.088 |
| 71 | 1 | [380,384) | 4 | 896 | 0.0746 | 0.083 |
| 178 | 3 | [380,384) | 4 | 896 | 0.0713 | 0.080 |
| 279 | 5 | [380,384) | 4 | 896 | 0.0709 | 0.079 |
| 224 | 4 | [380,384) | 4 | 896 | 0.0694 | 0.077 |
| 225 | 4 | [419,423) | 4 | 896 | 0.0693 | 0.077 |
| 179 | 3 | [389,392) | 3 | 896 | 0.0551 | 0.062 |
| 225 | 4 | [402,405) | 3 | 896 | 0.0530 | 0.059 |
| 281 | 5 | [415,418) | 3 | 896 | 0.0526 | 0.059 |
| 5 | 0,1 | [87,88) | 1 | 1792 | 0.0521 | 0.029 |
| 2 | 0,1 | [25,26) | 1 | 1792 | 0.0493 | 0.027 |
| 95 | 2,4 | [85,86) | 1 | 1792 | 0.0426 | 0.024 |
| 234 | 5,8 | [25,26) | 1 | 1792 | 0.0353 | 0.020 |
| 91 | 2 | [27,28) | 1 | 896 | 0.0333 | 0.037 |
| 3 | 0 | [31,32) | 1 | 896 | 0.0327 | 0.036 |
| 385 | 8,9 | [83,84) | 1 | 1792 | 0.0324 | 0.018 |
| 385 | 8,9 | [87,88) | 1 | 1792 | 0.0323 | 0.018 |
| 89 | 2 | [13,14) | 1 | 896 | 0.0323 | 0.036 |
| 455 | 10,11 | [81,82) | 1 | 1792 | 0.0322 | 0.018 |
| 455 | 10,11 | [83,84) | 1 | 1792 | 0.0321 | 0.018 |
| 385 | 8,9 | [81,82) | 1 | 1792 | 0.0321 | 0.018 |
| 385 | 8,9 | [85,86) | 1 | 1792 | 0.0320 | 0.018 |
| 455 | 10,11 | [85,86) | 1 | 1792 | 0.0319 | 0.018 |
| 480 | 20,21 | [83,84) | 1 | 1792 | 0.0319 | 0.018 |
| 490 | 24,25 | [83,84) | 1 | 1792 | 0.0318 | 0.018 |
| 485 | 22,23 | [83,84) | 1 | 1792 | 0.0317 | 0.018 |
| 460 | 12,13 | [83,84) | 1 | 1792 | 0.0317 | 0.018 |
| 465 | 14,15 | [87,88) | 1 | 1792 | 0.0317 | 0.018 |
| 480 | 20,21 | [81,82) | 1 | 1792 | 0.0316 | 0.018 |
| 475 | 18,19 | [83,84) | 1 | 1792 | 0.0316 | 0.018 |
| 490 | 24,25 | [81,82) | 1 | 1792 | 0.0315 | 0.018 |
| 455 | 10,11 | [87,88) | 1 | 1792 | 0.0315 | 0.018 |
| 465 | 14,15 | [83,84) | 1 | 1792 | 0.0315 | 0.018 |
| 485 | 22,23 | [81,82) | 1 | 1792 | 0.0315 | 0.018 |
| 460 | 12,13 | [85,86) | 1 | 1792 | 0.0315 | 0.018 |
| 460 | 12,13 | [81,82) | 1 | 1792 | 0.0315 | 0.018 |
| 470 | 16,17 | [83,84) | 1 | 1792 | 0.0315 | 0.018 |
| 460 | 12,13 | [87,88) | 1 | 1792 | 0.0315 | 0.018 |
| 475 | 18,19 | [87,88) | 1 | 1792 | 0.0314 | 0.018 |
| 480 | 20,21 | [87,88) | 1 | 1792 | 0.0314 | 0.018 |
| 470 | 16,17 | [81,82) | 1 | 1792 | 0.0314 | 0.018 |
| 490 | 24,25 | [85,86) | 1 | 1792 | 0.0314 | 0.018 |
| 475 | 18,19 | [85,86) | 1 | 1792 | 0.0314 | 0.018 |
| 470 | 16,17 | [87,88) | 1 | 1792 | 0.0313 | 0.017 |
| 485 | 22,23 | [85,86) | 1 | 1792 | 0.0313 | 0.017 |
| 480 | 20,21 | [85,86) | 1 | 1792 | 0.0313 | 0.017 |
| 475 | 18,19 | [81,82) | 1 | 1792 | 0.0313 | 0.017 |
| 465 | 14,15 | [81,82) | 1 | 1792 | 0.0313 | 0.017 |
| 485 | 22,23 | [87,88) | 1 | 1792 | 0.0312 | 0.017 |
| 470 | 16,17 | [85,86) | 1 | 1792 | 0.0312 | 0.017 |
| 465 | 14,15 | [85,86) | 1 | 1792 | 0.0311 | 0.017 |
| 490 | 24,25 | [87,88) | 1 | 1792 | 0.0311 | 0.017 |
| 88 | 2 | [5,6) | 1 | 896 | 0.0296 | 0.033 |
| 89 | 2 | [9,10) | 1 | 896 | 0.0283 | 0.032 |
| 89 | 2 | [11,12) | 1 | 896 | 0.0268 | 0.030 |
| 91 | 2 | [29,30) | 1 | 896 | 0.0268 | 0.030 |
| 136 | 3 | [13,14) | 1 | 896 | 0.0255 | 0.028 |
| 89 | 2 | [15,16) | 1 | 896 | 0.0254 | 0.028 |
| 136 | 3 | [7,8) | 1 | 896 | 0.0253 | 0.028 |
| 139 | 3 | [29,30) | 1 | 896 | 0.0242 | 0.027 |
| 185 | 4 | [3,4) | 1 | 896 | 0.0237 | 0.027 |
| 139 | 3 | [27,28) | 1 | 896 | 0.0236 | 0.026 |
| 145 | 3 | [83,84) | 1 | 896 | 0.0232 | 0.026 |
| 139 | 3 | [25,26) | 1 | 896 | 0.0216 | 0.024 |
| 194 | 4 | [81,82) | 1 | 896 | 0.0208 | 0.023 |
| 136 | 3 | [9,10) | 1 | 896 | 0.0200 | 0.022 |
| 185 | 4 | [5,6) | 1 | 896 | 0.0196 | 0.022 |
| 286 | 6 | [7,8) | 1 | 896 | 0.0194 | 0.022 |
| 136 | 3 | [11,12) | 1 | 896 | 0.0192 | 0.021 |
| 136 | 3 | [15,16) | 1 | 896 | 0.0191 | 0.021 |
| 296 | 6 | [87,88) | 1 | 896 | 0.0188 | 0.021 |
| 54 | 1 | [31,32) | 1 | 896 | 0.0187 | 0.021 |
| 381 | 8 | [31,32) | 1 | 896 | 0.0185 | 0.021 |
| 242 | 5 | [83,84) | 1 | 896 | 0.0185 | 0.021 |
| 231 | 5 | [3,4) | 1 | 896 | 0.0184 | 0.020 |
| 186 | 4 | [13,14) | 1 | 896 | 0.0182 | 0.020 |
| 420 | 9 | [13,14) | 1 | 896 | 0.0182 | 0.020 |
| 189 | 4 | [29,30) | 1 | 896 | 0.0182 | 0.020 |
| 336 | 7 | [15,16) | 1 | 896 | 0.0181 | 0.020 |
| 189 | 4 | [27,28) | 1 | 896 | 0.0181 | 0.020 |
| 290 | 6 | [27,28) | 1 | 896 | 0.0181 | 0.020 |
| 338 | 7 | [29,30) | 1 | 896 | 0.0181 | 0.020 |
| 91 | 2 | [31,32) | 1 | 896 | 0.0179 | 0.020 |
| 290 | 6 | [25,26) | 1 | 896 | 0.0178 | 0.020 |
| 139 | 3 | [31,32) | 1 | 896 | 0.0177 | 0.020 |
| 232 | 5 | [15,16) | 1 | 896 | 0.0176 | 0.020 |
| 189 | 4 | [31,32) | 1 | 896 | 0.0175 | 0.020 |
| 296 | 6 | [85,86) | 1 | 896 | 0.0175 | 0.020 |
| 232 | 5 | [13,14) | 1 | 896 | 0.0173 | 0.019 |
| 296 | 6 | [81,82) | 1 | 896 | 0.0172 | 0.019 |
| 242 | 5 | [81,82) | 1 | 896 | 0.0172 | 0.019 |
| 287 | 6 | [13,14) | 1 | 896 | 0.0172 | 0.019 |
| 338 | 7 | [27,28) | 1 | 896 | 0.0172 | 0.019 |
| 381 | 8 | [29,30) | 1 | 896 | 0.0172 | 0.019 |
| 342 | 7 | [87,88) | 1 | 896 | 0.0171 | 0.019 |
| 296 | 6 | [83,84) | 1 | 896 | 0.0171 | 0.019 |
| 379 | 8 | [15,16) | 1 | 896 | 0.0170 | 0.019 |
| 186 | 4 | [11,12) | 1 | 896 | 0.0170 | 0.019 |
| 422 | 9 | [27,28) | 1 | 896 | 0.0169 | 0.019 |
| 342 | 7 | [83,84) | 1 | 896 | 0.0167 | 0.019 |
| 287 | 6 | [11,12) | 1 | 896 | 0.0167 | 0.019 |
| 342 | 7 | [85,86) | 1 | 896 | 0.0167 | 0.019 |
| 335 | 7 | [11,12) | 1 | 896 | 0.0167 | 0.019 |
| 186 | 4 | [9,10) | 1 | 896 | 0.0166 | 0.019 |
| 235 | 5 | [29,30) | 1 | 896 | 0.0166 | 0.018 |
| 232 | 5 | [9,10) | 1 | 896 | 0.0164 | 0.018 |
| 232 | 5 | [11,12) | 1 | 896 | 0.0164 | 0.018 |
| 420 | 9 | [15,16) | 1 | 896 | 0.0164 | 0.018 |
| 342 | 7 | [81,82) | 1 | 896 | 0.0163 | 0.018 |
@@ -0,0 +1,352 @@
# 全部343个 fallback operation
按跨group累计原计算时间排序。一个position计作一个operation,native=1表示原语句含native调用(不等于动态native调用总次数)。纯代数operation的纯代数耗时等于其累计时间;native语句内部的代数/函数耗时没有独立position级测量。具体(group,position)到R的关联见JSON groupOperations,不将各列group和R做笛卡尔积。
所有m都是每Jacobian结构可共享上限,非语义证明。Hmax/Pmax使用与区间表相同的严格盈亏公式。逐operation的7.109µs typed量级,即使H=0也全部不盈利。A=原计算;C=批量代数段切分;E=只评估纯kernel memo,绝非跳过native的context副作用。
| position / 原ID | operation | R | group | 次数 | 累计ms | 每次µs | native | 主要native | m | Hmax(P=0)µs | Hmax(P=2/5/10)µs | Pmax(Htyped)µs | 选择 |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 379/24 | flow:amesim_pnvo001_1.port_2 | R49,R71,R129,R178,R224,R279,R457,R462,R467,R472,R477,R482,R487,R492 | 0,1,2,3,4,5,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 25.9256 | 1.315 | 1 | native_medium_orifice_context | 22 | 28.935 | -15.065/-81.065/-191.065 | 0.485 | A;E只测纯数值尾部 |
| 405/28 | flow:amesim_pnvo001_3.port_2 | R49,R77,R225,R457,R462,R467,R472,R477,R482,R487,R492 | 0,1,2,4,6,7,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 24.9072 | 1.264 | 1 | native_medium_orifice_context | 22 | 27.798 | -16.202/-82.202/-192.202 | 0.434 | A;E只测纯数值尾部 |
| 392/26 | flow:amesim_pnvo001_2.port_2 | R49,R74,R179,R457,R462,R467,R472,R477,R482,R487,R492 | 0,1,3,4,6,7,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 24.7206 | 1.254 | 1 | native_medium_orifice_context | 22 | 27.590 | -16.410/-82.410/-192.410 | 0.424 | A;E只测纯数值尾部 |
| 80/88 | flow:amesim_pnl00r_4.port_1 | R194,R242,R296,R342,R385,R455,R460,R465,R470,R475,R480,R485,R490 | 4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 24.4096 | 1.238 | 1 | native_pipe_flow_cached_context,native_temperature_ph_context | 22 | 27.243 | -16.757/-82.757/-192.757 | 0.408 | A;E只测纯数值尾部 |
| 82/90 | flow:amesim_pnl00r_5.port_1 | R145,R242,R296,R342,R385,R455,R460,R465,R470,R475,R480,R485,R490 | 3,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 23.7212 | 1.203 | 1 | native_pipe_flow_cached_context,native_temperature_ph_context | 22 | 26.475 | -17.525/-83.525/-193.525 | 0.373 | A;E只测纯数值尾部 |
| 418/30 | flow:amesim_pnvo001_4.port_2 | R49,R80,R225,R281,R457,R462,R467,R472,R477,R482,R487,R492 | 0,1,2,3,4,5,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 23.0211 | 1.168 | 1 | native_medium_orifice_context | 22 | 25.693 | -18.307/-84.307/-194.307 | 0.338 | A;E只测纯数值尾部 |
| 86/94 | flow:amesim_pnl00r_7.port_1 | R5,R296,R342,R385,R455,R460,R465,R470,R475,R480,R485,R490 | 0,1,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 22.8168 | 1.158 | 1 | native_pipe_flow_cached_context,native_temperature_ph_context | 22 | 25.465 | -18.535/-84.535/-194.535 | 0.328 | A;E只测纯数值尾部 |
| 88/96 | flow:amesim_pnl0001_21.port_1 | R5,R96,R195,R296,R455,R460,R465,R470,R475,R480,R485,R490 | 0,1,2,3,4,6,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 22.3970 | 1.136 | 1 | native_pipe_flow_cached_context,native_temperature_ph_context | 22 | 24.997 | -19.003/-85.003/-195.003 | 0.306 | A;E只测纯数值尾部 |
| 91/99 | flow:amesim_pnl0001_27.port_1 | R5,R96,R243,R426,R455,R460,R465,R470,R475,R480,R485,R490 | 0,1,2,3,5,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 22.3067 | 1.132 | 1 | native_pipe_flow_cached_context,native_temperature_ph_context | 22 | 24.896 | -19.104/-85.104/-195.104 | 0.302 | A;E只测纯数值尾部 |
| 84/92 | flow:amesim_pnl00r_6.port_1 | R95,R296,R342,R385,R455,R460,R465,R470,R475,R480,R485,R490 | 2,4,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 21.5297 | 1.092 | 1 | native_pipe_flow_cached_context,native_temperature_ph_context | 22 | 24.029 | -19.971/-85.971/-195.971 | 0.262 | A;E只测纯数值尾部 |
| 89/97 | flow:amesim_pnl0001_25.port_1 | R5,R96,R195,R343,R455,R460,R465,R470,R475,R480,R485,R490 | 0,1,2,3,4,7,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 21.2854 | 1.080 | 1 | native_pipe_flow_cached_context,native_temperature_ph_context | 22 | 23.756 | -20.244/-86.244/-196.244 | 0.250 | A;E只测纯数值尾部 |
| 90/98 | flow:amesim_pnl0001_26.port_1 | R5,R96,R195,R386,R455,R460,R465,R470,R475,R480,R485,R490 | 0,1,2,3,4,8,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 21.2271 | 1.077 | 1 | native_pipe_flow_cached_context,native_temperature_ph_context | 22 | 23.691 | -20.309/-86.309/-196.309 | 0.247 | A;E只测纯数值尾部 |
| 55/63 | flow:amesim_pnl0001_20.port_1 | R58,R94,R144,R242,R296,R342,R460,R465,R470,R475,R480,R485,R490 | 1,2,3,5,6,7,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 17920 | 19.7423 | 1.102 | 1 | native_pipe_flow_cached_context,native_temperature_ph_context | 20 | 22.034 | -17.966/-77.966/-177.966 | 0.189 | A;E只测纯数值尾部 |
| 54/62 | flow:amesim_pnl0001_19.port_1 | R58,R94,R144,R193,R296,R425,R465,R470,R475,R480,R485,R490 | 1,2,3,4,6,9,14,15,16,17,18,19,20,21,22,23,24,25 | 16128 | 17.8718 | 1.108 | 1 | native_pipe_flow_cached_context,native_temperature_ph_context | 18 | 19.946 | -16.054/-70.054/-160.054 | 0.094 | A;E只测纯数值尾部 |
| 53/61 | flow:amesim_pnl0001_18.port_1 | R58,R94,R144,R193,R241,R470,R475,R480,R485,R490 | 1,2,3,4,5,8,16,17,18,19,20,21,22,23,24,25 | 14336 | 16.1206 | 1.124 | 1 | native_pipe_flow_cached_context,native_temperature_ph_context | 16 | 17.992 | -14.008/-62.008/-142.008 | -0.017 | A;E只测纯数值尾部 |
| 52/60 | flow:amesim_pnl0001_17.port_1 | R58,R94,R144,R193,R341,R424,R475,R480,R485,R490 | 1,2,3,4,7,9,18,19,20,21,22,23,24,25 | 12544 | 14.2730 | 1.138 | 1 | native_pipe_flow_cached_context,native_temperature_ph_context | 14 | 15.930 | -12.070/-54.070/-124.070 | -0.166 | A;E只测纯数值尾部 |
| 51/59 | flow:amesim_pnl0001_16.port_1 | R58,R94,R144,R193,R384,R424,R480,R485,R490 | 1,2,3,4,8,9,20,21,22,23,24,25 | 10752 | 12.6662 | 1.178 | 1 | native_pipe_flow_cached_context,native_temperature_ph_context | 12 | 14.136 | -9.864/-45.864/-105.864 | -0.343 | A;E只测纯数值尾部 |
| 50/58 | flow:amesim_pnl0001_15.port_1 | R58,R94,R240,R295,R424,R485,R490 | 1,2,5,6,7,9,22,23,24,25 | 8960 | 10.6532 | 1.189 | 1 | native_pipe_flow_cached_context,native_temperature_ph_context | 10 | 11.890 | -8.110/-38.110/-88.110 | -0.637 | A;E只测纯数值尾部 |
| 49/57 | flow:amesim_pnl0001_14.port_1 | R58,R192,R295,R424,R490 | 1,4,6,7,8,9,24,25 | 7168 | 9.1021 | 1.270 | 1 | native_pipe_flow_cached_context,native_temperature_ph_context | 8 | 10.159 | -5.841/-29.841/-69.841 | -1.012 | A;E只测纯数值尾部 |
| 40/48 | flow:amesim_pnl0002_5.port_1 | R3,R141,R293,R339,R423 | 0,3,4,6,7,9 | 5376 | 6.9862 | 1.300 | 1 | native_pipe_flow_cached_context,native_temperature_ph_context | 6 | 7.797 | -4.203/-22.203/-52.203 | -1.743 | A;E只测纯数值尾部 |
| 47/55 | flow:amesim_pnl0002_8.port_2 | R3,R58,R93,R192,R424 | 0,1,2,4,8,9 | 5376 | 6.8524 | 1.275 | 1 | native_pipe_flow_cached_context,native_temperature_ph_context | 6 | 7.648 | -4.352/-22.352/-52.352 | -1.768 | A;E只测纯数值尾部 |
| 43/51 | flow:amesim_pnl0002_6.port_2 | R3,R57,R294,R383,R424 | 0,1,2,6,8,9 | 5376 | 6.8264 | 1.270 | 1 | native_pipe_flow_cached_context,native_temperature_ph_context | 6 | 7.619 | -4.381/-22.381/-52.381 | -1.773 | A;E只测纯数值尾部 |
| 45/53 | flow:amesim_pnl0002_7.port_2 | R3,R142,R239,R294,R340,R424 | 0,3,5,6,7,9 | 5376 | 6.7114 | 1.248 | 1 | native_pipe_flow_cached_context,native_temperature_ph_context | 6 | 7.490 | -4.510/-22.510/-52.510 | -1.795 | A;E只测纯数值尾部 |
| 42/50 | flow:amesim_pnl0002_6.port_1 | R3,R57,R238,R339,R383 | 0,1,2,5,7,8 | 5376 | 6.6735 | 1.241 | 1 | native_pipe_flow_cached_context,native_temperature_ph_context | 6 | 7.448 | -4.552/-22.552/-52.552 | -1.802 | A;E只测纯数值尾部 |
| 33/41 | flow:amesim_pnl0002_1.port_2 | R3,R139,R291,R381,R423 | 0,3,6,7,8,9 | 5376 | 6.4783 | 1.205 | 1 | native_pipe_flow_cached_context,native_temperature_ph_context | 6 | 7.230 | -4.770/-22.770/-52.770 | -1.838 | A;E只测纯数值尾部 |
| 38/46 | flow:amesim_pnl0002_4.port_1 | R3,R56,R237,R382,R423 | 0,1,2,5,8,9 | 5376 | 6.4745 | 1.204 | 1 | native_pipe_flow_cached_context,native_temperature_ph_context | 6 | 7.226 | -4.774/-22.774/-52.774 | -1.839 | A;E只测纯数值尾部 |
| 36/44 | flow:amesim_pnl0002_3.port_1 | R3,R190,R237,R292,R423 | 0,4,5,6,7,9 | 5376 | 6.3731 | 1.185 | 1 | native_pipe_flow_cached_context,native_temperature_ph_context | 6 | 7.113 | -4.887/-22.887/-52.887 | -1.857 | A;E只测纯数值尾部 |
| 44/52 | flow:amesim_pnl0002_7.port_1 | R3,R142,R191,R294,R383,R424 | 0,3,4,6,8,9 | 5376 | 6.2368 | 1.160 | 1 | native_pipe_flow_cached_context,native_temperature_ph_context | 6 | 6.961 | -5.039/-23.039/-53.039 | -1.883 | A;E只测纯数值尾部 |
| 24/32 | flow:amesim_pnvo001_5.port_2 | R2,R139,R234,R290 | 0,1,3,5,6,8 | 5376 | 6.2069 | 1.155 | 1 | native_medium_orifice_context | 6 | 6.927 | -5.073/-23.073/-53.073 | -1.888 | A;E只测纯数值尾部 |
| 30/38 | flow:amesim_pnvo001_8.port_2 | R3,R54,R91,R139,R189,R381 | 0,1,2,3,4,8 | 5376 | 6.1412 | 1.142 | 1 | native_medium_orifice_context | 6 | 6.854 | -5.146/-23.146/-53.146 | -1.901 | A;E只测纯数值尾部 |
| 37/45 | flow:amesim_pnl0002_3.port_2 | R3,R140,R190,R237,R382,R423 | 0,3,4,5,8,9 | 5376 | 6.0932 | 1.133 | 1 | native_pipe_flow_cached_context,native_temperature_ph_context | 6 | 6.800 | -5.200/-23.200/-53.200 | -1.910 | A;E只测纯数值尾部 |
| 32/40 | flow:amesim_pnl0002_1.port_1 | R3,R91,R139,R236,R381,R423 | 0,2,3,5,8,9 | 5376 | 6.0223 | 1.120 | 1 | native_pipe_flow_cached_context,native_temperature_ph_context | 6 | 6.721 | -5.279/-23.279/-53.279 | -1.923 | A;E只测纯数值尾部 |
| 46/54 | flow:amesim_pnl0002_8.port_1 | R3,R58,R93,R294,R340,R424 | 0,1,2,6,7,9 | 5376 | 5.9758 | 1.112 | 1 | native_pipe_flow_cached_context,native_temperature_ph_context | 6 | 6.669 | -5.331/-23.331/-53.331 | -1.931 | A;E只测纯数值尾部 |
| 48/56 | flow:amesim_pnl0001_13.port_1 | R58,R143,R192,R424 | 1,3,4,5,8,9 | 5376 | 5.9419 | 1.105 | 1 | native_pipe_flow_cached_context,native_temperature_ph_context | 6 | 6.632 | -5.368/-23.368/-53.368 | -1.938 | A;E只测纯数值尾部 |
| 39/47 | flow:amesim_pnl0002_4.port_2 | R3,R56,R293,R339,R423 | 0,1,2,6,7,9 | 5376 | 5.9384 | 1.105 | 1 | native_pipe_flow_cached_context,native_temperature_ph_context | 6 | 6.628 | -5.372/-23.372/-53.372 | -1.938 | A;E只测纯数值尾部 |
| 41/49 | flow:amesim_pnl0002_5.port_2 | R3,R141,R238,R339,R383 | 0,3,4,5,7,8 | 5376 | 5.9276 | 1.103 | 1 | native_pipe_flow_cached_context,native_temperature_ph_context | 6 | 6.616 | -5.384/-23.384/-53.384 | -1.940 | A;E只测纯数值尾部 |
| 23/23 | flow:amesim_pnl0001_11.port_1 | R2,R234,R337,R421 | 0,1,5,7,8,9 | 5376 | 5.8836 | 1.094 | 1 | native_pipe_flow_cached_context,native_temperature_ph_context | 6 | 6.566 | -5.434/-23.434/-53.434 | -1.949 | A;E只测纯数值尾部 |
| 28/36 | flow:amesim_pnvo001_7.port_2 | R91,R139,R189,R235,R338,R381 | 2,3,4,5,7,8 | 5376 | 5.8624 | 1.090 | 1 | native_medium_orifice_context | 6 | 6.543 | -5.457/-23.457/-53.457 | -1.952 | A;E只测纯数值尾部 |
| 34/42 | flow:amesim_pnl0002_2.port_1 | R3,R55,R139,R190,R381,R423 | 0,1,3,4,8,9 | 5376 | 5.8478 | 1.088 | 1 | native_pipe_flow_cached_context,native_temperature_ph_context | 6 | 6.527 | -5.473/-23.473/-53.473 | -1.955 | A;E只测纯数值尾部 |
| 26/34 | flow:amesim_pnvo001_6.port_2 | R91,R139,R189,R290,R338,R422 | 2,3,4,6,7,9 | 5376 | 5.7655 | 1.072 | 1 | native_medium_orifice_context | 6 | 6.435 | -5.565/-23.565/-53.565 | -1.970 | A;E只测纯数值尾部 |
| 21/21 | flow:amesim_pnl0001_9.port_1 | R2,R90,R188,R380,R421 | 0,1,2,4,8,9 | 5376 | 5.7596 | 1.071 | 1 | native_pipe_flow_cached_context,native_temperature_ph_context | 6 | 6.428 | -5.572/-23.572/-53.572 | -1.972 | A;E只测纯数值尾部 |
| 35/43 | flow:amesim_pnl0002_2.port_2 | R3,R55,R92,R190,R237,R423 | 0,1,2,4,5,9 | 5376 | 5.6729 | 1.055 | 1 | native_pipe_flow_cached_context,native_temperature_ph_context | 6 | 6.331 | -5.669/-23.669/-53.669 | -1.988 | A;E只测纯数值尾部 |
| 22/22 | flow:amesim_pnl0001_10.port_1 | R2,R90,R138,R421 | 0,1,2,3,6,9 | 5376 | 5.6316 | 1.048 | 1 | native_pipe_flow_cached_context,native_temperature_ph_context | 6 | 6.285 | -5.715/-23.715/-53.715 | -1.995 | A;E只测纯数值尾部 |
| 18/18 | flow:amesim_pnl0001_4.port_1 | R1,R233,R336,R379,R420 | 0,1,5,7,8,9 | 5376 | 5.6088 | 1.043 | 1 | native_pipe_flow_cached_context,native_temperature_ph_context | 6 | 6.260 | -5.740/-23.740/-53.740 | -2.000 | A;E只测纯数值尾部 |
| 20/20 | flow:amesim_pnl0001_7.port_1 | R2,R188,R233,R289,R336 | 0,1,4,5,6,7 | 5376 | 5.5929 | 1.040 | 1 | native_pipe_flow_cached_context,native_temperature_ph_context | 6 | 6.242 | -5.758/-23.758/-53.758 | -2.003 | A;E只测纯数值尾部 |
| 19/19 | flow:amesim_pnl0001_5.port_1 | R137,R233,R289,R336,R379,R420 | 3,5,6,7,8,9 | 5376 | 5.3133 | 0.988 | 1 | native_pipe_flow_cached_context,native_temperature_ph_context | 6 | 5.930 | -6.070/-24.070/-54.070 | -2.055 | A;E只测纯数值尾部 |
| 17/17 | flow:amesim_pnl0001_2.port_1 | R89,R187,R233,R336,R379,R420 | 2,4,5,7,8,9 | 5376 | 5.2407 | 0.975 | 1 | native_pipe_flow_cached_context,native_temperature_ph_context | 6 | 5.849 | -6.151/-24.151/-54.151 | -2.068 | A;E只测纯数值尾部 |
| 16/16 | flow:amesim_pnl0001_1.port_1 | R89,R136,R288,R336,R379,R420 | 2,3,6,7,8,9 | 5376 | 5.1746 | 0.963 | 1 | native_pipe_flow_cached_context,native_temperature_ph_context | 6 | 5.775 | -6.225/-24.225/-54.225 | -2.080 | A;停止R288后续优化 |
| 12/12 | flow:amesim_pnor001_7.port_1 | R89,R136,R186,R232,R287,R420 | 2,3,4,5,6,9 | 5376 | 5.1265 | 0.954 | 1 | native_medium_orifice_context | 6 | 5.722 | -6.278/-24.278/-54.278 | -2.089 | A;E只测纯数值尾部 |
| 10/10 | flow:amesim_pnor001_6.port_1 | R89,R136,R186,R232,R287,R335 | 2,3,4,5,6,7 | 5376 | 5.0914 | 0.947 | 1 | native_medium_orifice_context | 6 | 5.682 | -6.318/-24.318/-54.318 | -2.096 | A;E只测纯数值尾部 |
| 14/14 | flow:amesim_pnor001_8.port_1 | R89,R136,R232,R336,R379,R420 | 2,3,5,7,8,9 | 5376 | 5.0226 | 0.934 | 1 | native_medium_orifice_context | 6 | 5.606 | -6.394/-24.394/-54.394 | -2.109 | A;E只测纯数值尾部 |
| 8/8 | flow:amesim_pnor001_5.port_1 | R89,R136,R186,R232 | 2,3,4,5 | 3584 | 3.3942 | 0.947 | 1 | native_medium_orifice_context | 4 | 3.788 | -4.212/-16.212/-36.212 | -3.617 | A;E只测纯数值尾部 |
| 4/4 | flow:amesim_pnor001_3.port_1 | R88,R185 | 2,4 | 1792 | 1.7647 | 0.985 | 1 | native_medium_orifice_context | 2 | 1.970 | -2.030/-8.030/-18.030 | -8.144 | A;E只测纯数值尾部 |
| 6/6 | flow:amesim_pnor001_4.port_1 | R136,R286 | 3,6 | 1792 | 1.6856 | 0.941 | 1 | native_medium_orifice_context | 2 | 1.881 | -2.119/-8.119/-18.119 | -8.188 | A;E只测纯数值尾部 |
| 2/2 | flow:amesim_pnor001_2.port_1 | R185,R231 | 4,5 | 1792 | 1.4902 | 0.832 | 1 | native_medium_orifice_context | 2 | 1.663 | -2.337/-8.337/-18.337 | -8.297 | A;E只测纯数值尾部 |
| 401/427 | stream:amesim_p4node2_2 | R49,R179,R457,R462,R467,R472,R477,R482,R487,R492 | 0,3,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 16128 | 0.5801 | 0.036 | 0 | 纯代数/alias | 18 | 0.647 | -35.353/-89.353/-179.353 | -0.978 | A;C合并代数段 |
| 388/423 | stream:amesim_p4node2_1 | R49,R129,R457,R462,R467,R472,R477,R482,R487,R492 | 0,2,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 16128 | 0.5655 | 0.035 | 0 | 纯代数/alias | 18 | 0.631 | -35.369/-89.369/-179.369 | -0.979 | A;C合并代数段 |
| 431/439 | stream:amesim_p4node2_5 | R49,R457,R462,R467,R472,R477,R482,R487,R492 | 0,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 15232 | 0.5407 | 0.035 | 0 | 纯代数/alias | 17 | 0.603 | -33.397/-84.397/-169.397 | -1.038 | A;C合并代数段 |
| 427/435 | stream:amesim_p4node2_4 | R49,R281,R457,R462,R467,R472,R477,R482,R487,R492 | 0,5,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 16128 | 0.5360 | 0.033 | 0 | 纯代数/alias | 18 | 0.598 | -35.402/-89.402/-179.402 | -0.981 | A;C合并代数段 |
| 414/431 | stream:amesim_p4node2_3 | R49,R225,R457,R462,R467,R472,R477,R482,R487,R492 | 0,4,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 16128 | 0.5268 | 0.033 | 0 | 纯代数/alias | 18 | 0.588 | -35.412/-89.412/-179.412 | -0.982 | A;C合并代数段 |
| 439/447 | stream:amesim_p4node2_7 | R49,R457,R462,R467,R472,R477,R482,R487,R492 | 0,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 15232 | 0.4841 | 0.032 | 0 | 纯代数/alias | 17 | 0.540 | -33.460/-84.460/-169.460 | -1.042 | A;C合并代数段 |
| 435/443 | stream:amesim_p4node2_6 | R49,R457,R462,R467,R472,R477,R482,R487,R492 | 0,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 15232 | 0.4747 | 0.031 | 0 | 纯代数/alias | 17 | 0.530 | -33.470/-84.470/-169.470 | -1.043 | A;C合并代数段 |
| 443/451 | stream:amesim_p4node2_8 | R49,R457,R462,R467,R472,R477,R482,R487,R492 | 0,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 15232 | 0.4713 | 0.031 | 0 | 纯代数/alias | 17 | 0.526 | -33.474/-84.474/-169.474 | -1.043 | A;C合并代数段 |
| 59/67 | flow:amesim_pnpl01_4.port_1 | R58,R94,R144,R194,R242,R296,R342,R385,R455,R460,R465,R470,R475,R480,R485,R490 | 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 22400 | 0.4703 | 0.021 | 0 | 纯代数/alias | 25 | 0.525 | -49.475/-124.475/-249.475 | -0.709 | A;C合并代数段 |
| 71/79 | flow:amesim_pnpl01_16.port_1 | R58,R94,R144,R194,R242,R296,R342,R385,R455,R460,R465,R470,R475,R480,R485,R490 | 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 22400 | 0.4669 | 0.021 | 0 | 纯代数/alias | 25 | 0.521 | -49.479/-124.479/-249.479 | -0.709 | A;C合并代数段 |
| 67/75 | flow:amesim_pnpl01_12.port_1 | R58,R94,R144,R194,R242,R296,R342,R385,R455,R460,R465,R470,R475,R480,R485,R490 | 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 22400 | 0.4554 | 0.020 | 0 | 纯代数/alias | 25 | 0.508 | -49.492/-124.492/-249.492 | -0.710 | A;C合并代数段 |
| 56/64 | flow:amesim_pnpl01_1.port_1 | R58,R94,R144,R194,R242,R296,R342,R385,R455,R460,R465,R470,R475,R480,R485,R490 | 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 22400 | 0.4544 | 0.020 | 0 | 纯代数/alias | 25 | 0.507 | -49.493/-124.493/-249.493 | -0.710 | A;C合并代数段 |
| 65/73 | flow:amesim_pnpl01_10.port_1 | R58,R94,R144,R194,R242,R296,R342,R385,R455,R460,R465,R470,R475,R480,R485,R490 | 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 22400 | 0.4439 | 0.020 | 0 | 纯代数/alias | 25 | 0.495 | -49.505/-124.505/-249.505 | -0.710 | A;C合并代数段 |
| 64/72 | flow:amesim_pnpl01_9.port_1 | R58,R94,R144,R194,R242,R296,R342,R385,R455,R460,R465,R470,R475,R480,R485,R490 | 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 22400 | 0.4390 | 0.020 | 0 | 纯代数/alias | 25 | 0.490 | -49.510/-124.510/-249.510 | -0.711 | A;C合并代数段 |
| 62/70 | flow:amesim_pnpl01_7.port_1 | R58,R94,R144,R194,R242,R296,R342,R385,R455,R460,R465,R470,R475,R480,R485,R490 | 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 22400 | 0.4385 | 0.020 | 0 | 纯代数/alias | 25 | 0.489 | -49.511/-124.511/-249.511 | -0.711 | A;C合并代数段 |
| 58/66 | flow:amesim_pnpl01_3.port_1 | R58,R94,R144,R194,R242,R296,R342,R385,R455,R460,R465,R470,R475,R480,R485,R490 | 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 22400 | 0.4369 | 0.020 | 0 | 纯代数/alias | 25 | 0.488 | -49.512/-124.512/-249.512 | -0.711 | A;C合并代数段 |
| 61/69 | flow:amesim_pnpl01_6.port_1 | R58,R94,R144,R194,R242,R296,R342,R385,R455,R460,R465,R470,R475,R480,R485,R490 | 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 22400 | 0.4363 | 0.019 | 0 | 纯代数/alias | 25 | 0.487 | -49.513/-124.513/-249.513 | -0.711 | A;C合并代数段 |
| 77/85 | flow:amesim_pnrp17_6.port_1 | R58,R94,R144,R194,R242,R296,R342,R385,R455,R460,R465,R470,R475,R480,R485,R490 | 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 22400 | 0.4347 | 0.019 | 0 | 纯代数/alias | 25 | 0.485 | -49.515/-124.515/-249.515 | -0.711 | A;C合并代数段 |
| 57/65 | flow:amesim_pnpl01_2.port_1 | R58,R94,R144,R194,R242,R296,R342,R385,R455,R460,R465,R470,R475,R480,R485,R490 | 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 22400 | 0.4344 | 0.019 | 0 | 纯代数/alias | 25 | 0.485 | -49.515/-124.515/-249.515 | -0.711 | A;C合并代数段 |
| 63/71 | flow:amesim_pnpl01_8.port_1 | R58,R94,R144,R194,R242,R296,R342,R385,R455,R460,R465,R470,R475,R480,R485,R490 | 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 22400 | 0.4332 | 0.019 | 0 | 纯代数/alias | 25 | 0.483 | -49.517/-124.517/-249.517 | -0.711 | A;C合并代数段 |
| 72/80 | flow:amesim_pnrp17_1.port_1 | R58,R94,R144,R194,R242,R296,R342,R385,R455,R460,R465,R470,R475,R480,R485,R490 | 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 22400 | 0.4331 | 0.019 | 0 | 纯代数/alias | 25 | 0.483 | -49.517/-124.517/-249.517 | -0.711 | A;C合并代数段 |
| 78/86 | flow:amesim_pnrp17_7.port_1 | R58,R94,R144,R194,R242,R296,R342,R385,R455,R460,R465,R470,R475,R480,R485,R490 | 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 22400 | 0.4327 | 0.019 | 0 | 纯代数/alias | 25 | 0.483 | -49.517/-124.517/-249.517 | -0.711 | A;C合并代数段 |
| 79/87 | flow:amesim_pnrp17_8.port_1 | R58,R94,R144,R194,R242,R296,R342,R385,R455,R460,R465,R470,R475,R480,R485,R490 | 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 22400 | 0.4323 | 0.019 | 0 | 纯代数/alias | 25 | 0.482 | -49.518/-124.518/-249.518 | -0.711 | A;C合并代数段 |
| 70/78 | flow:amesim_pnpl01_15.port_1 | R58,R94,R144,R194,R242,R296,R342,R385,R455,R460,R465,R470,R475,R480,R485,R490 | 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 22400 | 0.4309 | 0.019 | 0 | 纯代数/alias | 25 | 0.481 | -49.519/-124.519/-249.519 | -0.711 | A;C合并代数段 |
| 68/76 | flow:amesim_pnpl01_13.port_1 | R58,R94,R144,R194,R242,R296,R342,R385,R455,R460,R465,R470,R475,R480,R485,R490 | 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 22400 | 0.4289 | 0.019 | 0 | 纯代数/alias | 25 | 0.479 | -49.521/-124.521/-249.521 | -0.711 | A;C合并代数段 |
| 60/68 | flow:amesim_pnpl01_5.port_1 | R58,R94,R144,R194,R242,R296,R342,R385,R455,R460,R465,R470,R475,R480,R485,R490 | 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 22400 | 0.4274 | 0.019 | 0 | 纯代数/alias | 25 | 0.477 | -49.523/-124.523/-249.523 | -0.711 | A;C合并代数段 |
| 66/74 | flow:amesim_pnpl01_11.port_1 | R58,R94,R144,R194,R242,R296,R342,R385,R455,R460,R465,R470,R475,R480,R485,R490 | 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 22400 | 0.4270 | 0.019 | 0 | 纯代数/alias | 25 | 0.477 | -49.523/-124.523/-249.523 | -0.711 | A;C合并代数段 |
| 417/434 | alias:amesim_p4node2_4.port_4 | R49,R80,R225,R281,R457,R462,R467,R472,R477,R482,R487,R492 | 0,1,2,3,4,5,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.4268 | 0.022 | 0 | 纯代数/alias | 22 | 0.476 | -43.524/-109.524/-219.524 | -0.808 | A;C合并代数段 |
| 75/83 | flow:amesim_pnrp17_4.port_1 | R58,R94,R144,R194,R242,R296,R342,R385,R455,R460,R465,R470,R475,R480,R485,R490 | 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 22400 | 0.4265 | 0.019 | 0 | 纯代数/alias | 25 | 0.476 | -49.524/-124.524/-249.524 | -0.711 | A;C合并代数段 |
| 76/84 | flow:amesim_pnrp17_5.port_1 | R58,R94,R144,R194,R242,R296,R342,R385,R455,R460,R465,R470,R475,R480,R485,R490 | 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 22400 | 0.4255 | 0.019 | 0 | 纯代数/alias | 25 | 0.475 | -49.525/-124.525/-249.525 | -0.711 | A;C合并代数段 |
| 73/81 | flow:amesim_pnrp17_2.port_1 | R58,R94,R144,R194,R242,R296,R342,R385,R455,R460,R465,R470,R475,R480,R485,R490 | 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 22400 | 0.4249 | 0.019 | 0 | 纯代数/alias | 25 | 0.474 | -49.526/-124.526/-249.526 | -0.711 | A;C合并代数段 |
| 69/77 | flow:amesim_pnpl01_14.port_1 | R58,R94,R144,R194,R242,R296,R342,R385,R455,R460,R465,R470,R475,R480,R485,R490 | 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 22400 | 0.4246 | 0.019 | 0 | 纯代数/alias | 25 | 0.474 | -49.526/-124.526/-249.526 | -0.711 | A;C合并代数段 |
| 99/107 | connection:b15 | R5,R96,R243,R426,R455,R460,R465,R470,R475,R480,R485,R490 | 0,1,2,3,5,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.4185 | 0.021 | 0 | 纯代数/alias | 22 | 0.467 | -43.533/-109.533/-219.533 | -0.809 | A;C合并代数段 |
| 74/82 | flow:amesim_pnrp17_3.port_1 | R58,R94,R144,R194,R242,R296,R342,R385,R455,R460,R465,R470,R475,R480,R485,R490 | 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 22400 | 0.4179 | 0.019 | 0 | 纯代数/alias | 25 | 0.466 | -49.534/-124.534/-249.534 | -0.712 | A;C合并代数段 |
| 96/104 | connection:b8 | R5,R96,R243,R426,R455,R460,R465,R470,R475,R480,R485,R490 | 0,1,2,3,5,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.4146 | 0.021 | 0 | 纯代数/alias | 22 | 0.463 | -43.537/-109.537/-219.537 | -0.809 | A;C合并代数段 |
| 415/432 | alias:amesim_p4node2_4.port_1 | R49,R80,R225,R281,R457,R462,R467,R472,R477,R482,R487,R492 | 0,1,2,3,4,5,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.4138 | 0.021 | 0 | 纯代数/alias | 22 | 0.462 | -43.538/-109.538/-219.538 | -0.809 | A;C合并代数段 |
| 376/420 | alias:amesim_p4node2_1.port_1 | R49,R71,R129,R178,R224,R279,R457,R462,R467,R472,R477,R482,R487,R492 | 0,1,2,3,4,5,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.4046 | 0.021 | 0 | 纯代数/alias | 22 | 0.452 | -43.548/-109.548/-219.548 | -0.809 | A;C合并代数段 |
| 381/146 | connection:b66 | R49,R71,R129,R178,R224,R279,R457,R462,R467,R472,R477,R482,R487,R492 | 0,1,2,3,4,5,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3991 | 0.020 | 0 | 纯代数/alias | 22 | 0.445 | -43.555/-109.555/-219.555 | -0.810 | A;C合并代数段 |
| 386/320 | connection:q[145] | R49,R129,R457,R462,R467,R472,R477,R482,R487,R492 | 0,2,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 16128 | 0.3989 | 0.025 | 0 | 纯代数/alias | 18 | 0.445 | -35.555/-89.555/-179.555 | -0.990 | A;C合并代数段 |
| 408/156 | connection:b76 | R49,R77,R225,R457,R462,R467,R472,R477,R482,R487,R492 | 0,1,2,4,6,7,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3982 | 0.020 | 0 | 纯代数/alias | 22 | 0.444 | -43.556/-109.556/-219.556 | -0.810 | A;C合并代数段 |
| 407/148 | connection:b68 | R49,R77,R225,R457,R462,R467,R472,R477,R482,R487,R492 | 0,1,2,4,6,7,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3967 | 0.020 | 0 | 纯代数/alias | 22 | 0.443 | -43.557/-109.557/-219.557 | -0.810 | A;C合并代数段 |
| 92/100 | connection:b2 | R5,R96,R243,R426,R455,R460,R465,R470,R475,R480,R485,R490 | 0,1,2,3,5,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3951 | 0.020 | 0 | 纯代数/alias | 22 | 0.441 | -43.559/-109.559/-219.559 | -0.810 | A;C合并代数段 |
| 382/154 | connection:b74 | R49,R71,R129,R178,R224,R279,R457,R462,R467,R472,R477,R482,R487,R492 | 0,1,2,3,4,5,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3944 | 0.020 | 0 | 纯代数/alias | 22 | 0.440 | -43.560/-109.560/-219.560 | -0.810 | A;C合并代数段 |
| 420/149 | connection:b69 | R49,R80,R225,R281,R457,R462,R467,R472,R477,R482,R487,R492 | 0,1,2,3,4,5,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3939 | 0.020 | 0 | 纯代数/alias | 22 | 0.440 | -43.560/-109.560/-219.560 | -0.810 | A;C合并代数段 |
| 395/155 | connection:b75 | R49,R74,R179,R457,R462,R467,R472,R477,R482,R487,R492 | 0,1,3,4,6,7,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3929 | 0.020 | 0 | 纯代数/alias | 22 | 0.439 | -43.561/-109.561/-219.561 | -0.810 | A;C合并代数段 |
| 421/157 | connection:b77 | R49,R80,R225,R281,R457,R462,R467,R472,R477,R482,R487,R492 | 0,1,2,3,4,5,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3919 | 0.020 | 0 | 纯代数/alias | 22 | 0.437 | -43.563/-109.563/-219.563 | -0.810 | A;C合并代数段 |
| 375/419 | alias:amesim_pnvo001_8.port_3 | R49,R71,R129,R178,R224,R279,R457,R462,R467,R472,R477,R482,R487,R492 | 0,1,2,3,4,5,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3911 | 0.020 | 0 | 纯代数/alias | 22 | 0.436 | -43.564/-109.564/-219.564 | -0.810 | A;C合并代数段 |
| 97/105 | connection:b11 | R5,R96,R243,R426,R455,R460,R465,R470,R475,R480,R485,R490 | 0,1,2,3,5,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3892 | 0.020 | 0 | 纯代数/alias | 22 | 0.434 | -43.566/-109.566/-219.566 | -0.810 | A;C合并代数段 |
| 98/106 | connection:b14 | R5,R96,R243,R426,R455,R460,R465,R470,R475,R480,R485,R490 | 0,1,2,3,5,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3879 | 0.020 | 0 | 纯代数/alias | 22 | 0.433 | -43.567/-109.567/-219.567 | -0.810 | A;C合并代数段 |
| 394/147 | connection:b67 | R49,R74,R179,R457,R462,R467,R472,R477,R482,R487,R492 | 0,1,3,4,6,7,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3874 | 0.020 | 0 | 纯代数/alias | 22 | 0.432 | -43.568/-109.568/-219.568 | -0.810 | A;C合并代数段 |
| 391/426 | alias:amesim_p4node2_2.port_4 | R49,R74,R179,R457,R462,R467,R472,R477,R482,R487,R492 | 0,1,3,4,6,7,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3848 | 0.020 | 0 | 纯代数/alias | 22 | 0.429 | -43.571/-109.571/-219.571 | -0.810 | A;C合并代数段 |
| 378/422 | alias:amesim_p4node2_1.port_4 | R49,R71,R129,R178,R224,R279,R457,R462,R467,R472,R477,R482,R487,R492 | 0,1,2,3,4,5,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3838 | 0.019 | 0 | 纯代数/alias | 22 | 0.428 | -43.572/-109.572/-219.572 | -0.810 | A;C合并代数段 |
| 416/433 | alias:amesim_p4node2_4.port_3 | R49,R80,R225,R281,R457,R462,R467,R472,R477,R482,R487,R492 | 0,1,2,3,4,5,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3838 | 0.019 | 0 | 纯代数/alias | 22 | 0.428 | -43.572/-109.572/-219.572 | -0.810 | A;C合并代数段 |
| 104/112 | connection:b28 | R5,R96,R243,R426,R455,R460,R465,R470,R475,R480,R485,R490 | 0,1,2,3,5,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3825 | 0.019 | 0 | 纯代数/alias | 22 | 0.427 | -43.573/-109.573/-219.573 | -0.810 | A;C合并代数段 |
| 399/321 | connection:q[147] | R49,R179,R457,R462,R467,R472,R477,R482,R487,R492 | 0,3,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 16128 | 0.3825 | 0.024 | 0 | 纯代数/alias | 18 | 0.427 | -35.573/-89.573/-179.573 | -0.991 | A;C合并代数段 |
| 94/102 | connection:b4 | R5,R96,R243,R426,R455,R460,R465,R470,R475,R480,R485,R490 | 0,1,2,3,5,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3802 | 0.019 | 0 | 纯代数/alias | 22 | 0.424 | -43.576/-109.576/-219.576 | -0.811 | A;C合并代数段 |
| 106/114 | connection:b30 | R5,R96,R243,R426,R455,R460,R465,R470,R475,R480,R485,R490 | 0,1,2,3,5,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3796 | 0.019 | 0 | 纯代数/alias | 22 | 0.424 | -43.576/-109.576/-219.576 | -0.811 | A;C合并代数段 |
| 95/103 | connection:b7 | R5,R96,R243,R426,R455,R460,R465,R470,R475,R480,R485,R490 | 0,1,2,3,5,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3794 | 0.019 | 0 | 纯代数/alias | 22 | 0.423 | -43.577/-109.577/-219.577 | -0.811 | A;C合并代数段 |
| 105/113 | connection:b29 | R5,R96,R243,R426,R455,R460,R465,R470,R475,R480,R485,R490 | 0,1,2,3,5,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3781 | 0.019 | 0 | 纯代数/alias | 22 | 0.422 | -43.578/-109.578/-219.578 | -0.811 | A;C合并代数段 |
| 389/424 | alias:amesim_p4node2_2.port_1 | R49,R74,R179,R457,R462,R467,R472,R477,R482,R487,R492 | 0,1,3,4,6,7,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3767 | 0.019 | 0 | 纯代数/alias | 22 | 0.420 | -43.580/-109.580/-219.580 | -0.811 | A;C合并代数段 |
| 402/428 | alias:amesim_p4node2_3.port_1 | R49,R77,R225,R457,R462,R467,R472,R477,R482,R487,R492 | 0,1,2,4,6,7,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3765 | 0.019 | 0 | 纯代数/alias | 22 | 0.420 | -43.580/-109.580/-219.580 | -0.811 | A;C合并代数段 |
| 403/429 | alias:amesim_p4node2_3.port_3 | R49,R77,R225,R457,R462,R467,R472,R477,R482,R487,R492 | 0,1,2,4,6,7,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3765 | 0.019 | 0 | 纯代数/alias | 22 | 0.420 | -43.580/-109.580/-219.580 | -0.811 | A;C合并代数段 |
| 404/430 | alias:amesim_p4node2_3.port_4 | R49,R77,R225,R457,R462,R467,R472,R477,R482,R487,R492 | 0,1,2,4,6,7,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3763 | 0.019 | 0 | 纯代数/alias | 22 | 0.420 | -43.580/-109.580/-219.580 | -0.811 | A;C合并代数段 |
| 409/287 | connection:q[79] | R49,R77,R225,R457,R462,R467,R472,R477,R482,R487,R492 | 0,1,2,4,6,7,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3756 | 0.019 | 0 | 纯代数/alias | 22 | 0.419 | -43.581/-109.581/-219.581 | -0.811 | A;C合并代数段 |
| 100/108 | connection:b24 | R5,R96,R243,R426,R455,R460,R465,R470,R475,R480,R485,R490 | 0,1,2,3,5,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3756 | 0.019 | 0 | 纯代数/alias | 22 | 0.419 | -43.581/-109.581/-219.581 | -0.811 | A;C合并代数段 |
| 93/101 | connection:b3 | R5,R96,R243,R426,R455,R460,R465,R470,R475,R480,R485,R490 | 0,1,2,3,5,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3751 | 0.019 | 0 | 纯代数/alias | 22 | 0.419 | -43.581/-109.581/-219.581 | -0.811 | A;C合并代数段 |
| 107/115 | connection:b31 | R5,R96,R243,R426,R455,R460,R465,R470,R475,R480,R485,R490 | 0,1,2,3,5,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3736 | 0.019 | 0 | 纯代数/alias | 22 | 0.417 | -43.583/-109.583/-219.583 | -0.811 | A;C合并代数段 |
| 390/425 | alias:amesim_p4node2_2.port_3 | R49,R74,R179,R457,R462,R467,R472,R477,R482,R487,R492 | 0,1,3,4,6,7,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3720 | 0.019 | 0 | 纯代数/alias | 22 | 0.415 | -43.585/-109.585/-219.585 | -0.811 | A;C合并代数段 |
| 87/95 | flow:amesim_pnl00r_7.port_2 | R5,R296,R342,R385,R455,R460,R465,R470,R475,R480,R485,R490 | 0,1,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3716 | 0.019 | 0 | 纯代数/alias | 22 | 0.415 | -43.585/-109.585/-219.585 | -0.811 | A;C合并代数段 |
| 101/109 | connection:b25 | R5,R96,R243,R426,R455,R460,R465,R470,R475,R480,R485,R490 | 0,1,2,3,5,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3712 | 0.019 | 0 | 纯代数/alias | 22 | 0.414 | -43.586/-109.586/-219.586 | -0.811 | A;C合并代数段 |
| 380/25 | flow:amesim_pnvo001_1.port_3 | R49,R71,R129,R178,R224,R279,R457,R462,R467,R472,R477,R482,R487,R492 | 0,1,2,3,4,5,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3703 | 0.019 | 0 | 纯代数/alias | 22 | 0.413 | -43.587/-109.587/-219.587 | -0.811 | A;C合并代数段 |
| 422/277 | connection:q[69] | R49,R80,R225,R281,R457,R462,R467,R472,R477,R482,R487,R492 | 0,1,2,3,4,5,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3701 | 0.019 | 0 | 纯代数/alias | 22 | 0.413 | -43.587/-109.587/-219.587 | -0.811 | A;C合并代数段 |
| 396/275 | connection:q[67] | R49,R74,R179,R457,R462,R467,R472,R477,R482,R487,R492 | 0,1,3,4,6,7,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3684 | 0.019 | 0 | 纯代数/alias | 22 | 0.411 | -43.589/-109.589/-219.589 | -0.811 | A;C合并代数段 |
| 102/110 | connection:b26 | R5,R96,R243,R426,R455,R460,R465,R470,R475,R480,R485,R490 | 0,1,2,3,5,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3678 | 0.019 | 0 | 纯代数/alias | 22 | 0.410 | -43.590/-109.590/-219.590 | -0.811 | A;C合并代数段 |
| 423/301 | connection:q[109] | R49,R281,R457,R462,R467,R472,R477,R482,R487,R492 | 0,5,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 16128 | 0.3677 | 0.023 | 0 | 纯代数/alias | 18 | 0.410 | -35.590/-89.590/-179.590 | -0.992 | A;C合并代数段 |
| 377/421 | alias:amesim_p4node2_1.port_3 | R49,R71,R129,R178,R224,R279,R457,R462,R467,R472,R477,R482,R487,R492 | 0,1,2,3,4,5,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3676 | 0.019 | 0 | 纯代数/alias | 22 | 0.410 | -43.590/-109.590/-219.590 | -0.811 | A;C合并代数段 |
| 383/273 | connection:q[65] | R49,R71,R129,R178,R224,R279,R457,R462,R467,R472,R477,R482,R487,R492 | 0,1,2,3,4,5,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3671 | 0.019 | 0 | 纯代数/alias | 22 | 0.410 | -43.590/-109.590/-219.590 | -0.811 | A;C合并代数段 |
| 419/31 | flow:amesim_pnvo001_4.port_3 | R49,R80,R225,R281,R457,R462,R467,R472,R477,R482,R487,R492 | 0,1,2,3,4,5,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3671 | 0.019 | 0 | 纯代数/alias | 22 | 0.410 | -43.590/-109.590/-219.590 | -0.811 | A;C合并代数段 |
| 103/111 | connection:b27 | R5,R96,R243,R426,R455,R460,R465,R470,R475,R480,R485,R490 | 0,1,2,3,5,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3667 | 0.019 | 0 | 纯代数/alias | 22 | 0.409 | -43.591/-109.591/-219.591 | -0.811 | A;C合并代数段 |
| 412/322 | connection:q[149] | R49,R225,R457,R462,R467,R472,R477,R482,R487,R492 | 0,4,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 16128 | 0.3664 | 0.023 | 0 | 纯代数/alias | 18 | 0.409 | -35.591/-89.591/-179.591 | -0.992 | A;C合并代数段 |
| 393/27 | flow:amesim_pnvo001_2.port_3 | R49,R74,R179,R457,R462,R467,R472,R477,R482,R487,R492 | 0,1,3,4,6,7,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3650 | 0.019 | 0 | 纯代数/alias | 22 | 0.407 | -43.593/-109.593/-219.593 | -0.811 | A;C合并代数段 |
| 108/116 | connection:b36 | R5,R243,R426,R455,R460,R465,R470,R475,R480,R485,R490 | 0,1,5,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 17920 | 0.3647 | 0.020 | 0 | 纯代数/alias | 20 | 0.407 | -39.593/-99.593/-199.593 | -0.893 | A;C合并代数段 |
| 384/289 | connection:q[97] | R49,R129,R457,R462,R467,R472,R477,R482,R487,R492 | 0,2,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 16128 | 0.3636 | 0.023 | 0 | 纯代数/alias | 18 | 0.406 | -35.594/-89.594/-179.594 | -0.992 | A;C合并代数段 |
| 406/29 | flow:amesim_pnvo001_3.port_3 | R49,R77,R225,R457,R462,R467,R472,R477,R482,R487,R492 | 0,1,2,4,6,7,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3617 | 0.018 | 0 | 纯代数/alias | 22 | 0.404 | -43.596/-109.596/-219.596 | -0.812 | A;C合并代数段 |
| 83/91 | flow:amesim_pnl00r_5.port_2 | R145,R242,R296,R342,R385,R455,R460,R465,R470,R475,R480,R485,R490 | 3,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3616 | 0.018 | 0 | 纯代数/alias | 22 | 0.404 | -43.596/-109.596/-219.596 | -0.812 | A;C合并代数段 |
| 425/323 | connection:q[151] | R49,R281,R457,R462,R467,R472,R477,R482,R487,R492 | 0,5,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 16128 | 0.3615 | 0.022 | 0 | 纯代数/alias | 18 | 0.403 | -35.597/-89.597/-179.597 | -0.992 | A;C合并代数段 |
| 430/438 | alias:amesim_p4node2_5.port_4 | R49,R457,R462,R467,R472,R477,R482,R487,R492 | 0,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 15232 | 0.3608 | 0.024 | 0 | 纯代数/alias | 17 | 0.403 | -33.597/-84.597/-169.597 | -1.050 | A;C合并代数段 |
| 85/93 | flow:amesim_pnl00r_6.port_2 | R95,R296,R342,R385,R455,R460,R465,R470,R475,R480,R485,R490 | 2,4,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3601 | 0.018 | 0 | 纯代数/alias | 22 | 0.402 | -43.598/-109.598/-219.598 | -0.812 | A;C合并代数段 |
| 374/418 | alias:amesim_pnvo001_8.port_2 | R49,R71,R129,R178,R224,R457,R462,R467,R472,R477,R482,R487,R492 | 0,1,2,3,4,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 18816 | 0.3588 | 0.019 | 0 | 纯代数/alias | 21 | 0.400 | -41.600/-104.600/-209.600 | -0.850 | A;C合并代数段 |
| 373/417 | alias:amesim_pnvo001_7.port_3 | R49,R71,R129,R178,R224,R457,R462,R467,R472,R477,R482,R487,R492 | 0,1,2,3,4,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 18816 | 0.3583 | 0.019 | 0 | 纯代数/alias | 21 | 0.400 | -41.600/-104.600/-209.600 | -0.850 | A;C合并代数段 |
| 81/89 | flow:amesim_pnl00r_4.port_2 | R194,R242,R296,R342,R385,R455,R460,R465,R470,R475,R480,R485,R490 | 4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 19712 | 0.3560 | 0.018 | 0 | 纯代数/alias | 22 | 0.397 | -43.603/-109.603/-219.603 | -0.812 | A;C合并代数段 |
| 397/293 | connection:q[101] | R49,R179,R457,R462,R467,R472,R477,R482,R487,R492 | 0,3,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 16128 | 0.3553 | 0.022 | 0 | 纯代数/alias | 18 | 0.397 | -35.603/-89.603/-179.603 | -0.992 | A;C合并代数段 |
| 369/413 | alias:amesim_pnvo001_5.port_3 | R129,R178,R224,R457,R462,R467,R472,R477,R482,R487,R492 | 2,3,4,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 17024 | 0.3526 | 0.021 | 0 | 纯代数/alias | 19 | 0.393 | -37.607/-94.607/-189.607 | -0.940 | A;C合并代数段 |
| 113/121 | connection:b41 | R426,R455,R460,R465,R470,R475,R480,R485,R490 | 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 15232 | 0.3520 | 0.023 | 0 | 纯代数/alias | 17 | 0.393 | -33.607/-84.607/-169.607 | -1.051 | A;C合并代数段 |
| 410/297 | connection:q[105] | R49,R225,R457,R462,R467,R472,R477,R482,R487,R492 | 0,4,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 16128 | 0.3483 | 0.022 | 0 | 纯代数/alias | 18 | 0.389 | -35.611/-89.611/-179.611 | -0.993 | A;C合并代数段 |
| 110/118 | connection:b38 | R5,R426,R455,R460,R465,R470,R475,R480,R485,R490 | 0,1,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 17024 | 0.3413 | 0.020 | 0 | 纯代数/alias | 19 | 0.381 | -37.619/-94.619/-189.619 | -0.941 | A;C合并代数段 |
| 109/117 | connection:b37 | R5,R426,R455,R460,R465,R470,R475,R480,R485,R490 | 0,1,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 17024 | 0.3377 | 0.020 | 0 | 纯代数/alias | 19 | 0.377 | -37.623/-94.623/-189.623 | -0.941 | A;C合并代数段 |
| 111/119 | connection:b39 | R426,R455,R460,R465,R470,R475,R480,R485,R490 | 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 15232 | 0.3241 | 0.021 | 0 | 纯代数/alias | 17 | 0.362 | -33.638/-84.638/-169.638 | -1.053 | A;C合并代数段 |
| 372/416 | alias:amesim_pnvo001_7.port_2 | R129,R178,R224,R457,R462,R467,R472,R477,R482,R487,R492 | 2,3,4,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 17024 | 0.3236 | 0.019 | 0 | 纯代数/alias | 19 | 0.361 | -37.639/-94.639/-189.639 | -0.942 | A;C合并代数段 |
| 115/123 | connection:b43 | R426,R455,R460,R465,R470,R475,R480,R485,R490 | 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 15232 | 0.3230 | 0.021 | 0 | 纯代数/alias | 17 | 0.360 | -33.640/-84.640/-169.640 | -1.053 | A;C合并代数段 |
| 122/130 | connection:b50 | R426,R455,R460,R465,R470,R475,R480,R485,R490 | 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 15232 | 0.3227 | 0.021 | 0 | 纯代数/alias | 17 | 0.360 | -33.640/-84.640/-169.640 | -1.053 | A;C合并代数段 |
| 371/415 | alias:amesim_pnvo001_6.port_3 | R129,R178,R224,R457,R462,R467,R472,R477,R482,R487,R492 | 2,3,4,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 17024 | 0.3205 | 0.019 | 0 | 纯代数/alias | 19 | 0.358 | -37.642/-94.642/-189.642 | -0.942 | A;C合并代数段 |
| 146/162 | connection:b82 | R455,R460,R465,R470,R475,R480,R485,R490 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.3201 | 0.022 | 0 | 纯代数/alias | 16 | 0.357 | -31.643/-79.643/-159.643 | -1.119 | A;C合并代数段 |
| 367/410 | alias:amesim_pnvo001_4.port_2 | R178,R457,R462,R467,R472,R477,R482,R487,R492 | 3,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 15232 | 0.3189 | 0.021 | 0 | 纯代数/alias | 17 | 0.356 | -33.644/-84.644/-169.644 | -1.053 | A;C合并代数段 |
| 121/129 | connection:b49 | R426,R455,R460,R465,R470,R475,R480,R485,R490 | 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 15232 | 0.3148 | 0.021 | 0 | 纯代数/alias | 17 | 0.351 | -33.649/-84.649/-169.649 | -1.053 | A;C合并代数段 |
| 120/128 | connection:b48 | R426,R455,R460,R465,R470,R475,R480,R485,R490 | 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 15232 | 0.3145 | 0.021 | 0 | 纯代数/alias | 17 | 0.351 | -33.649/-84.649/-169.649 | -1.053 | A;C合并代数段 |
| 433/441 | alias:amesim_p4node2_6.port_3 | R49,R457,R462,R467,R472,R477,R482,R487,R492 | 0,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 15232 | 0.3130 | 0.021 | 0 | 纯代数/alias | 17 | 0.349 | -33.651/-84.651/-169.651 | -1.053 | A;C合并代数段 |
| 432/440 | alias:amesim_p4node2_6.port_1 | R49,R457,R462,R467,R472,R477,R482,R487,R492 | 0,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 15232 | 0.3123 | 0.020 | 0 | 纯代数/alias | 17 | 0.348 | -33.652/-84.652/-169.652 | -1.053 | A;C合并代数段 |
| 370/414 | alias:amesim_pnvo001_6.port_2 | R129,R178,R224,R457,R462,R467,R472,R477,R482,R487,R492 | 2,3,4,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 17024 | 0.3118 | 0.018 | 0 | 纯代数/alias | 19 | 0.348 | -37.652/-94.652/-189.652 | -0.943 | A;C合并代数段 |
| 411/299 | connection:q[107] | R49,R225,R457,R462,R467,R472,R477,R482,R487,R492 | 0,4,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 16128 | 0.3110 | 0.019 | 0 | 纯代数/alias | 18 | 0.347 | -35.653/-89.653/-179.653 | -0.995 | A;C合并代数段 |
| 114/122 | connection:b42 | R426,R455,R460,R465,R470,R475,R480,R485,R490 | 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 15232 | 0.3093 | 0.020 | 0 | 纯代数/alias | 17 | 0.345 | -33.655/-84.655/-169.655 | -1.054 | A;C合并代数段 |
| 142/158 | connection:b78 | R455,R460,R465,R470,R475,R480,R485,R490 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.3089 | 0.022 | 0 | 纯代数/alias | 16 | 0.345 | -31.655/-79.655/-159.655 | -1.120 | A;C合并代数段 |
| 112/120 | connection:b40 | R426,R455,R460,R465,R470,R475,R480,R485,R490 | 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 15232 | 0.3088 | 0.020 | 0 | 纯代数/alias | 17 | 0.345 | -33.655/-84.655/-169.655 | -1.054 | A;C合并代数段 |
| 123/131 | connection:b51 | R455,R460,R465,R470,R475,R480,R485,R490 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.3087 | 0.022 | 0 | 纯代数/alias | 16 | 0.344 | -31.656/-79.656/-159.656 | -1.120 | A;C合并代数段 |
| 398/295 | connection:q[103] | R49,R179,R457,R462,R467,R472,R477,R482,R487,R492 | 0,3,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 16128 | 0.3083 | 0.019 | 0 | 纯代数/alias | 18 | 0.344 | -35.656/-89.656/-179.656 | -0.995 | A;C合并代数段 |
| 161/177 | connection:b97 | R460,R465,R470,R475,R480,R485,R490 | 12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 12544 | 0.3081 | 0.025 | 0 | 纯代数/alias | 14 | 0.344 | -27.656/-69.656/-139.656 | -1.280 | A;C合并代数段 |
| 424/303 | connection:q[111] | R49,R281,R457,R462,R467,R472,R477,R482,R487,R492 | 0,5,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 16128 | 0.3061 | 0.019 | 0 | 纯代数/alias | 18 | 0.342 | -35.658/-89.658/-179.658 | -0.995 | A;C合并代数段 |
| 440/448 | alias:amesim_p4node2_8.port_1 | R49,R457,R462,R467,R472,R477,R482,R487,R492 | 0,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 15232 | 0.3059 | 0.020 | 0 | 纯代数/alias | 17 | 0.341 | -33.659/-84.659/-169.659 | -1.054 | A;C合并代数段 |
| 413/409 | alias:amesim_pnvo001_3.port_3 | R49,R225,R457,R462,R467,R472,R477,R482,R487,R492 | 0,4,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 16128 | 0.3043 | 0.019 | 0 | 纯代数/alias | 18 | 0.340 | -35.660/-89.660/-179.660 | -0.995 | A;C合并代数段 |
| 400/407 | alias:amesim_pnvo001_2.port_3 | R49,R179,R457,R462,R467,R472,R477,R482,R487,R492 | 0,3,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 16128 | 0.3033 | 0.019 | 0 | 纯代数/alias | 18 | 0.338 | -35.662/-89.662/-179.662 | -0.996 | A;C合并代数段 |
| 426/411 | alias:amesim_pnvo001_4.port_3 | R49,R281,R457,R462,R467,R472,R477,R482,R487,R492 | 0,5,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 16128 | 0.3024 | 0.019 | 0 | 纯代数/alias | 18 | 0.337 | -35.663/-89.663/-179.663 | -0.996 | A;C合并代数段 |
| 116/124 | connection:b44 | R426,R455,R460,R465,R470,R475,R480,R485,R490 | 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 15232 | 0.3023 | 0.020 | 0 | 纯代数/alias | 17 | 0.337 | -33.663/-84.663/-169.663 | -1.054 | A;C合并代数段 |
| 119/127 | connection:b47 | R426,R455,R460,R465,R470,R475,R480,R485,R490 | 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 15232 | 0.3016 | 0.020 | 0 | 纯代数/alias | 17 | 0.337 | -33.663/-84.663/-169.663 | -1.054 | A;C合并代数段 |
| 332/364 | alias:amesim_pnor001_1.port_1 | R457,R462,R467,R472,R477,R482,R487,R492 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.3013 | 0.021 | 0 | 纯代数/alias | 16 | 0.336 | -31.664/-79.664/-159.664 | -1.120 | A;C合并代数段 |
| 340/374 | alias:amesim_pn3node2_3.port_1 | R457,R462,R467,R472,R477,R482,R487,R492 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.3008 | 0.021 | 0 | 纯代数/alias | 16 | 0.336 | -31.664/-79.664/-159.664 | -1.120 | A;C合并代数段 |
| 118/126 | connection:b46 | R426,R455,R460,R465,R470,R475,R480,R485,R490 | 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 15232 | 0.3000 | 0.020 | 0 | 纯代数/alias | 17 | 0.335 | -33.665/-84.665/-169.665 | -1.054 | A;C合并代数段 |
| 138/150 | connection:b70 | R455,R460,R465,R470,R475,R480,R485,R490 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2990 | 0.021 | 0 | 纯代数/alias | 16 | 0.334 | -31.666/-79.666/-159.666 | -1.120 | A;C合并代数段 |
| 387/405 | alias:amesim_pnvo001_1.port_3 | R49,R129,R457,R462,R467,R472,R477,R482,R487,R492 | 0,2,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 16128 | 0.2988 | 0.019 | 0 | 纯代数/alias | 18 | 0.334 | -35.666/-89.666/-179.666 | -0.996 | A;C合并代数段 |
| 127/135 | connection:b55 | R455,R460,R465,R470,R475,R480,R485,R490 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2983 | 0.021 | 0 | 纯代数/alias | 16 | 0.333 | -31.667/-79.667/-159.667 | -1.120 | A;C合并代数段 |
| 117/125 | connection:b45 | R426,R455,R460,R465,R470,R475,R480,R485,R490 | 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 15232 | 0.2963 | 0.019 | 0 | 纯代数/alias | 17 | 0.331 | -33.669/-84.669/-169.669 | -1.055 | A;C合并代数段 |
| 141/153 | connection:b73 | R455,R460,R465,R470,R475,R480,R485,R490 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2962 | 0.021 | 0 | 纯代数/alias | 16 | 0.331 | -31.669/-79.669/-159.669 | -1.120 | A;C合并代数段 |
| 364/404 | alias:amesim_pnvo001_1.port_2 | R178,R457,R462,R467,R472,R477,R482,R487,R492 | 3,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 15232 | 0.2960 | 0.019 | 0 | 纯代数/alias | 17 | 0.330 | -33.670/-84.670/-169.670 | -1.055 | A;C合并代数段 |
| 385/291 | connection:q[99] | R49,R129,R457,R462,R467,R472,R477,R482,R487,R492 | 0,2,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 16128 | 0.2955 | 0.018 | 0 | 纯代数/alias | 18 | 0.330 | -35.670/-89.670/-179.670 | -0.996 | A;C合并代数段 |
| 348/384 | alias:amesim_pn3node2_5.port_1 | R457,R462,R467,R472,R477,R482,R487,R492 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2954 | 0.021 | 0 | 纯代数/alias | 16 | 0.330 | -31.670/-79.670/-159.670 | -1.121 | A;C合并代数段 |
| 145/161 | connection:b81 | R455,R460,R465,R470,R475,R480,R485,R490 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2939 | 0.021 | 0 | 纯代数/alias | 16 | 0.328 | -31.672/-79.672/-159.672 | -1.121 | A;C合并代数段 |
| 368/412 | alias:amesim_pnvo001_5.port_2 | R178,R457,R462,R467,R472,R477,R482,R487,R492 | 3,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 15232 | 0.2924 | 0.019 | 0 | 纯代数/alias | 17 | 0.326 | -33.674/-84.674/-169.674 | -1.055 | A;C合并代数段 |
| 350/387 | alias:amesim_pnor001_5.port_1 | R457,R462,R467,R472,R477,R482,R487,R492 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2922 | 0.020 | 0 | 纯代数/alias | 16 | 0.326 | -31.674/-79.674/-159.674 | -1.121 | A;C合并代数段 |
| 147/163 | connection:b83 | R455,R460,R465,R470,R475,R480,R485,R490 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2917 | 0.020 | 0 | 纯代数/alias | 16 | 0.326 | -31.674/-79.674/-159.674 | -1.121 | A;C合并代数段 |
| 129/137 | connection:b57 | R455,R460,R465,R470,R475,R480,R485,R490 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2913 | 0.020 | 0 | 纯代数/alias | 16 | 0.325 | -31.675/-79.675/-159.675 | -1.121 | A;C合并代数段 |
| 149/165 | connection:b85 | R455,R460,R465,R470,R475,R480,R485,R490 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2912 | 0.020 | 0 | 纯代数/alias | 16 | 0.325 | -31.675/-79.675/-159.675 | -1.121 | A;C合并代数段 |
| 143/159 | connection:b79 | R455,R460,R465,R470,R475,R480,R485,R490 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2910 | 0.020 | 0 | 纯代数/alias | 16 | 0.325 | -31.675/-79.675/-159.675 | -1.121 | A;C合并代数段 |
| 150/166 | connection:b86 | R455,R460,R465,R470,R475,R480,R485,R490 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2903 | 0.020 | 0 | 纯代数/alias | 16 | 0.324 | -31.676/-79.676/-159.676 | -1.121 | A;C合并代数段 |
| 437/445 | alias:amesim_p4node2_7.port_3 | R49,R457,R462,R467,R472,R477,R482,R487,R492 | 0,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 15232 | 0.2900 | 0.019 | 0 | 纯代数/alias | 17 | 0.324 | -33.676/-84.676/-169.676 | -1.055 | A;C合并代数段 |
| 366/408 | alias:amesim_pnvo001_3.port_2 | R178,R457,R462,R467,R472,R477,R482,R487,R492 | 3,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 15232 | 0.2898 | 0.019 | 0 | 纯代数/alias | 17 | 0.323 | -33.677/-84.677/-169.677 | -1.055 | A;C合并代数段 |
| 153/169 | connection:b89 | R455,R460,R465,R470,R475,R480,R485,R490 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2898 | 0.020 | 0 | 纯代数/alias | 16 | 0.323 | -31.677/-79.677/-159.677 | -1.121 | A;C合并代数段 |
| 126/134 | connection:b54 | R455,R460,R465,R470,R475,R480,R485,R490 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2893 | 0.020 | 0 | 纯代数/alias | 16 | 0.323 | -31.677/-79.677/-159.677 | -1.121 | A;C合并代数段 |
| 128/136 | connection:b56 | R455,R460,R465,R470,R475,R480,R485,R490 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2885 | 0.020 | 0 | 纯代数/alias | 16 | 0.322 | -31.678/-79.678/-159.678 | -1.121 | A;C合并代数段 |
| 362/401 | alias:amesim_pn3node2_9.port_1 | R178,R457,R462,R467,R472,R477,R482,R487,R492 | 3,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 15232 | 0.2882 | 0.019 | 0 | 纯代数/alias | 17 | 0.322 | -33.678/-84.678/-169.678 | -1.055 | A;C合并代数段 |
| 152/168 | connection:b88 | R455,R460,R465,R470,R475,R480,R485,R490 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2881 | 0.020 | 0 | 纯代数/alias | 16 | 0.322 | -31.678/-79.678/-159.678 | -1.121 | A;C合并代数段 |
| 148/164 | connection:b84 | R455,R460,R465,R470,R475,R480,R485,R490 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2877 | 0.020 | 0 | 纯代数/alias | 16 | 0.321 | -31.679/-79.679/-159.679 | -1.121 | A;C合并代数段 |
| 151/167 | connection:b87 | R455,R460,R465,R470,R475,R480,R485,R490 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2872 | 0.020 | 0 | 纯代数/alias | 16 | 0.321 | -31.679/-79.679/-159.679 | -1.121 | A;C合并代数段 |
| 363/402 | alias:amesim_pn3node2_9.port_3 | R178,R457,R462,R467,R472,R477,R482,R487,R492 | 3,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 15232 | 0.2868 | 0.019 | 0 | 纯代数/alias | 17 | 0.320 | -33.680/-84.680/-169.680 | -1.055 | A;C合并代数段 |
| 428/436 | alias:amesim_p4node2_5.port_1 | R49,R457,R462,R467,R472,R477,R482,R487,R492 | 0,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 15232 | 0.2858 | 0.019 | 0 | 纯代数/alias | 17 | 0.319 | -33.681/-84.681/-169.681 | -1.055 | A;C合并代数段 |
| 131/139 | connection:b59 | R455,R460,R465,R470,R475,R480,R485,R490 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2857 | 0.020 | 0 | 纯代数/alias | 16 | 0.319 | -31.681/-79.681/-159.681 | -1.121 | A;C合并代数段 |
| 429/437 | alias:amesim_p4node2_5.port_3 | R49,R457,R462,R467,R472,R477,R482,R487,R492 | 0,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 15232 | 0.2854 | 0.019 | 0 | 纯代数/alias | 17 | 0.319 | -33.681/-84.681/-169.681 | -1.055 | A;C合并代数段 |
| 365/406 | alias:amesim_pnvo001_2.port_2 | R178,R457,R462,R467,R472,R477,R482,R487,R492 | 3,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 15232 | 0.2843 | 0.019 | 0 | 纯代数/alias | 17 | 0.317 | -33.683/-84.683/-169.683 | -1.055 | A;C合并代数段 |
| 140/152 | connection:b72 | R455,R460,R465,R470,R475,R480,R485,R490 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2832 | 0.020 | 0 | 纯代数/alias | 16 | 0.316 | -31.684/-79.684/-159.684 | -1.121 | A;C合并代数段 |
| 132/140 | connection:b60 | R455,R460,R465,R470,R475,R480,R485,R490 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2831 | 0.020 | 0 | 纯代数/alias | 16 | 0.316 | -31.684/-79.684/-159.684 | -1.121 | A;C合并代数段 |
| 156/172 | connection:b92 | R455,R460,R465,R470,R475,R480,R485,R490 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2829 | 0.020 | 0 | 纯代数/alias | 16 | 0.316 | -31.684/-79.684/-159.684 | -1.121 | A;C合并代数段 |
| 164/180 | connection:b100 | R460,R465,R470,R475,R480,R485,R490 | 12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 12544 | 0.2828 | 0.023 | 0 | 纯代数/alias | 14 | 0.316 | -27.684/-69.684/-139.684 | -1.282 | A;C合并代数段 |
| 442/450 | alias:amesim_p4node2_8.port_4 | R49,R457,R462,R467,R472,R477,R482,R487,R492 | 0,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 15232 | 0.2825 | 0.019 | 0 | 纯代数/alias | 17 | 0.315 | -33.685/-84.685/-169.685 | -1.055 | A;C合并代数段 |
| 441/449 | alias:amesim_p4node2_8.port_3 | R49,R457,R462,R467,R472,R477,R482,R487,R492 | 0,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 15232 | 0.2824 | 0.019 | 0 | 纯代数/alias | 17 | 0.315 | -33.685/-84.685/-169.685 | -1.055 | A;C合并代数段 |
| 159/175 | connection:b95 | R455,R460,R465,R470,R475,R480,R485,R490 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2821 | 0.020 | 0 | 纯代数/alias | 16 | 0.315 | -31.685/-79.685/-159.685 | -1.121 | A;C合并代数段 |
| 434/442 | alias:amesim_p4node2_6.port_4 | R49,R457,R462,R467,R472,R477,R482,R487,R492 | 0,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 15232 | 0.2819 | 0.019 | 0 | 纯代数/alias | 17 | 0.315 | -33.685/-84.685/-169.685 | -1.055 | A;C合并代数段 |
| 336/368 | alias:amesim_pn3node2_1.port_1 | R457,R462,R467,R472,R477,R482,R487,R492 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2815 | 0.020 | 0 | 纯代数/alias | 16 | 0.314 | -31.686/-79.686/-159.686 | -1.121 | A;C合并代数段 |
| 133/141 | connection:b61 | R455,R460,R465,R470,R475,R480,R485,R490 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2809 | 0.020 | 0 | 纯代数/alias | 16 | 0.314 | -31.686/-79.686/-159.686 | -1.122 | A;C合并代数段 |
| 438/446 | alias:amesim_p4node2_7.port_4 | R49,R457,R462,R467,R472,R477,R482,R487,R492 | 0,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 15232 | 0.2808 | 0.018 | 0 | 纯代数/alias | 17 | 0.313 | -33.687/-84.687/-169.687 | -1.056 | A;C合并代数段 |
| 155/171 | connection:b91 | R455,R460,R465,R470,R475,R480,R485,R490 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2804 | 0.020 | 0 | 纯代数/alias | 16 | 0.313 | -31.687/-79.687/-159.687 | -1.122 | A;C合并代数段 |
| 124/132 | connection:b52 | R455,R460,R465,R470,R475,R480,R485,R490 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2804 | 0.020 | 0 | 纯代数/alias | 16 | 0.313 | -31.687/-79.687/-159.687 | -1.122 | A;C合并代数段 |
| 139/151 | connection:b71 | R455,R460,R465,R470,R475,R480,R485,R490 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2799 | 0.020 | 0 | 纯代数/alias | 16 | 0.312 | -31.688/-79.688/-159.688 | -1.122 | A;C合并代数段 |
| 157/173 | connection:b93 | R455,R460,R465,R470,R475,R480,R485,R490 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2793 | 0.019 | 0 | 纯代数/alias | 16 | 0.312 | -31.688/-79.688/-159.688 | -1.122 | A;C合并代数段 |
| 160/176 | connection:b96 | R455,R460,R465,R470,R475,R480,R485,R490 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2793 | 0.019 | 0 | 纯代数/alias | 16 | 0.312 | -31.688/-79.688/-159.688 | -1.122 | A;C合并代数段 |
| 436/444 | alias:amesim_p4node2_7.port_1 | R49,R457,R462,R467,R472,R477,R482,R487,R492 | 0,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 15232 | 0.2786 | 0.018 | 0 | 纯代数/alias | 17 | 0.311 | -33.689/-84.689/-169.689 | -1.056 | A;C合并代数段 |
| 144/160 | connection:b80 | R455,R460,R465,R470,R475,R480,R485,R490 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2784 | 0.019 | 0 | 纯代数/alias | 16 | 0.311 | -31.689/-79.689/-159.689 | -1.122 | A;C合并代数段 |
| 134/142 | connection:b62 | R455,R460,R465,R470,R475,R480,R485,R490 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2774 | 0.019 | 0 | 纯代数/alias | 16 | 0.310 | -31.690/-79.690/-159.690 | -1.122 | A;C合并代数段 |
| 326/358 | connection:q[204] | R457,R462,R467,R472,R477,R482,R487,R492 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2772 | 0.019 | 0 | 纯代数/alias | 16 | 0.309 | -31.691/-79.691/-159.691 | -1.122 | A;C合并代数段 |
| 344/379 | alias:amesim_pnor001_4.port_1 | R457,R462,R467,R472,R477,R482,R487,R492 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2771 | 0.019 | 0 | 纯代数/alias | 16 | 0.309 | -31.691/-79.691/-159.691 | -1.122 | A;C合并代数段 |
| 158/174 | connection:b94 | R455,R460,R465,R470,R475,R480,R485,R490 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2766 | 0.019 | 0 | 纯代数/alias | 16 | 0.309 | -31.691/-79.691/-159.691 | -1.122 | A;C合并代数段 |
| 136/144 | connection:b64 | R455,R460,R465,R470,R475,R480,R485,R490 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2739 | 0.019 | 0 | 纯代数/alias | 16 | 0.306 | -31.694/-79.694/-159.694 | -1.122 | A;C合并代数段 |
| 325/357 | connection:q[203] | R457,R462,R467,R472,R477,R482,R487,R492 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2734 | 0.019 | 0 | 纯代数/alias | 16 | 0.305 | -31.695/-79.695/-159.695 | -1.122 | A;C合并代数段 |
| 137/145 | connection:b65 | R455,R460,R465,R470,R475,R480,R485,R490 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2733 | 0.019 | 0 | 纯代数/alias | 16 | 0.305 | -31.695/-79.695/-159.695 | -1.122 | A;C合并代数段 |
| 360/398 | alias:amesim_pn3node2_8.port_1 | R457,R462,R467,R472,R477,R482,R487,R492 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2723 | 0.019 | 0 | 纯代数/alias | 16 | 0.304 | -31.696/-79.696/-159.696 | -1.122 | A;C合并代数段 |
| 345/380 | alias:amesim_pnor001_4.port_2 | R457,R462,R467,R472,R477,R482,R487,R492 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2723 | 0.019 | 0 | 纯代数/alias | 16 | 0.304 | -31.696/-79.696/-159.696 | -1.122 | A;C合并代数段 |
| 135/143 | connection:b63 | R455,R460,R465,R470,R475,R480,R485,R490 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2719 | 0.019 | 0 | 纯代数/alias | 16 | 0.303 | -31.697/-79.697/-159.697 | -1.122 | A;C合并代数段 |
| 349/385 | alias:amesim_pn3node2_5.port_3 | R457,R462,R467,R472,R477,R482,R487,R492 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2717 | 0.019 | 0 | 纯代数/alias | 16 | 0.303 | -31.697/-79.697/-159.697 | -1.122 | A;C合并代数段 |
| 339/372 | alias:amesim_pn3node2_2.port_3 | R457,R462,R467,R472,R477,R482,R487,R492 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2713 | 0.019 | 0 | 纯代数/alias | 16 | 0.303 | -31.697/-79.697/-159.697 | -1.122 | A;C合并代数段 |
| 357/395 | alias:amesim_pnor001_7.port_2 | R457,R462,R467,R472,R477,R482,R487,R492 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2710 | 0.019 | 0 | 纯代数/alias | 16 | 0.302 | -31.698/-79.698/-159.698 | -1.122 | A;C合并代数段 |
| 125/133 | connection:b53 | R455,R460,R465,R470,R475,R480,R485,R490 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2707 | 0.019 | 0 | 纯代数/alias | 16 | 0.302 | -31.698/-79.698/-159.698 | -1.122 | A;C合并代数段 |
| 327/359 | connection:q[205] | R457,R462,R467,R472,R477,R482,R487,R492 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2704 | 0.019 | 0 | 纯代数/alias | 16 | 0.302 | -31.698/-79.698/-159.698 | -1.122 | A;C合并代数段 |
| 333/365 | alias:amesim_pnor001_1.port_2 | R457,R462,R467,R472,R477,R482,R487,R492 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2695 | 0.019 | 0 | 纯代数/alias | 16 | 0.301 | -31.699/-79.699/-159.699 | -1.122 | A;C合并代数段 |
| 355/392 | alias:amesim_pn3node2_6.port_3 | R457,R462,R467,R472,R477,R482,R487,R492 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2688 | 0.019 | 0 | 纯代数/alias | 16 | 0.300 | -31.700/-79.700/-159.700 | -1.122 | A;C合并代数段 |
| 354/391 | alias:amesim_pn3node2_6.port_1 | R457,R462,R467,R472,R477,R482,R487,R492 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2685 | 0.019 | 0 | 纯代数/alias | 16 | 0.300 | -31.700/-79.700/-159.700 | -1.122 | A;C合并代数段 |
| 334/366 | alias:amesim_pnor001_2.port_1 | R457,R462,R467,R472,R477,R482,R487,R492 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2684 | 0.019 | 0 | 纯代数/alias | 16 | 0.300 | -31.700/-79.700/-159.700 | -1.122 | A;C合并代数段 |
| 329/361 | connection:q[227] | R457,R462,R467,R472,R477,R482,R487,R492 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2671 | 0.019 | 0 | 纯代数/alias | 16 | 0.298 | -31.702/-79.702/-159.702 | -1.122 | A;C合并代数段 |
| 335/367 | alias:amesim_pnor001_2.port_2 | R457,R462,R467,R472,R477,R482,R487,R492 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2662 | 0.019 | 0 | 纯代数/alias | 16 | 0.297 | -31.703/-79.703/-159.703 | -1.123 | A;C合并代数段 |
| 353/390 | alias:amesim_pnor001_6.port_2 | R457,R462,R467,R472,R477,R482,R487,R492 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2649 | 0.018 | 0 | 纯代数/alias | 16 | 0.296 | -31.704/-79.704/-159.704 | -1.123 | A;C合并代数段 |
| 130/138 | connection:b58 | R455,R460,R465,R470,R475,R480,R485,R490 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2646 | 0.018 | 0 | 纯代数/alias | 16 | 0.295 | -31.705/-79.705/-159.705 | -1.123 | A;C合并代数段 |
| 338/371 | alias:amesim_pn3node2_2.port_1 | R457,R462,R467,R472,R477,R482,R487,R492 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2645 | 0.018 | 0 | 纯代数/alias | 16 | 0.295 | -31.705/-79.705/-159.705 | -1.123 | A;C合并代数段 |
| 361/399 | alias:amesim_pn3node2_8.port_3 | R457,R462,R467,R472,R477,R482,R487,R492 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2645 | 0.018 | 0 | 纯代数/alias | 16 | 0.295 | -31.705/-79.705/-159.705 | -1.123 | A;C合并代数段 |
| 328/360 | connection:q[225] | R457,R462,R467,R472,R477,R482,R487,R492 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2643 | 0.018 | 0 | 纯代数/alias | 16 | 0.295 | -31.705/-79.705/-159.705 | -1.123 | A;C合并代数段 |
| 359/397 | alias:amesim_pnor001_8.port_2 | R457,R462,R467,R472,R477,R482,R487,R492 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2642 | 0.018 | 0 | 纯代数/alias | 16 | 0.295 | -31.705/-79.705/-159.705 | -1.123 | A;C合并代数段 |
| 337/369 | alias:amesim_pn3node2_1.port_3 | R457,R462,R467,R472,R477,R482,R487,R492 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2642 | 0.018 | 0 | 纯代数/alias | 16 | 0.295 | -31.705/-79.705/-159.705 | -1.123 | A;C合并代数段 |
| 352/389 | alias:amesim_pnor001_6.port_1 | R457,R462,R467,R472,R477,R482,R487,R492 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2641 | 0.018 | 0 | 纯代数/alias | 16 | 0.295 | -31.705/-79.705/-159.705 | -1.123 | A;C合并代数段 |
| 154/170 | connection:b90 | R455,R460,R465,R470,R475,R480,R485,R490 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2638 | 0.018 | 0 | 纯代数/alias | 16 | 0.294 | -31.706/-79.706/-159.706 | -1.123 | A;C合并代数段 |
| 347/382 | alias:amesim_pn3node2_4.port_3 | R457,R462,R467,R472,R477,R482,R487,R492 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2636 | 0.018 | 0 | 纯代数/alias | 16 | 0.294 | -31.706/-79.706/-159.706 | -1.123 | A;C合并代数段 |
| 351/388 | alias:amesim_pnor001_5.port_2 | R457,R462,R467,R472,R477,R482,R487,R492 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2635 | 0.018 | 0 | 纯代数/alias | 16 | 0.294 | -31.706/-79.706/-159.706 | -1.123 | A;C合并代数段 |
| 346/381 | alias:amesim_pn3node2_4.port_1 | R457,R462,R467,R472,R477,R482,R487,R492 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2635 | 0.018 | 0 | 纯代数/alias | 16 | 0.294 | -31.706/-79.706/-159.706 | -1.123 | A;C合并代数段 |
| 358/396 | alias:amesim_pnor001_8.port_1 | R457,R462,R467,R472,R477,R482,R487,R492 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2633 | 0.018 | 0 | 纯代数/alias | 16 | 0.294 | -31.706/-79.706/-159.706 | -1.123 | A;C合并代数段 |
| 343/378 | alias:amesim_pnor001_3.port_2 | R457,R462,R467,R472,R477,R482,R487,R492 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2632 | 0.018 | 0 | 纯代数/alias | 16 | 0.294 | -31.706/-79.706/-159.706 | -1.123 | A;C合并代数段 |
| 341/375 | alias:amesim_pn3node2_3.port_3 | R457,R462,R467,R472,R477,R482,R487,R492 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2628 | 0.018 | 0 | 纯代数/alias | 16 | 0.293 | -31.707/-79.707/-159.707 | -1.123 | A;C合并代数段 |
| 331/363 | connection:q[231] | R457,R462,R467,R472,R477,R482,R487,R492 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2608 | 0.018 | 0 | 纯代数/alias | 16 | 0.291 | -31.709/-79.709/-159.709 | -1.123 | A;C合并代数段 |
| 323/355 | connection:q[199] | R457,R462,R467,R472,R477,R482,R487 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23 | 12544 | 0.2600 | 0.021 | 0 | 纯代数/alias | 14 | 0.290 | -27.710/-69.710/-139.710 | -1.283 | A;C合并代数段 |
| 330/362 | connection:q[229] | R457,R462,R467,R472,R477,R482,R487,R492 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2593 | 0.018 | 0 | 纯代数/alias | 16 | 0.289 | -31.711/-79.711/-159.711 | -1.123 | A;C合并代数段 |
| 342/377 | alias:amesim_pnor001_3.port_1 | R457,R462,R467,R472,R477,R482,R487,R492 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2588 | 0.018 | 0 | 纯代数/alias | 16 | 0.289 | -31.711/-79.711/-159.711 | -1.123 | A;C合并代数段 |
| 356/394 | alias:amesim_pnor001_7.port_1 | R457,R462,R467,R472,R477,R482,R487,R492 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 14336 | 0.2587 | 0.018 | 0 | 纯代数/alias | 16 | 0.289 | -31.711/-79.711/-159.711 | -1.123 | A;C合并代数段 |
| 163/179 | connection:b99 | R460,R465,R470,R475,R480,R485,R490 | 12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 12544 | 0.2570 | 0.020 | 0 | 纯代数/alias | 14 | 0.287 | -27.713/-69.713/-139.713 | -1.284 | A;C合并代数段 |
| 165/181 | connection:b101 | R460,R465,R470,R475,R480,R485,R490 | 12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 12544 | 0.2553 | 0.020 | 0 | 纯代数/alias | 14 | 0.285 | -27.715/-69.715/-139.715 | -1.284 | A;C合并代数段 |
| 162/178 | connection:b98 | R460,R465,R470,R475,R480,R485,R490 | 12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 12544 | 0.2540 | 0.020 | 0 | 纯代数/alias | 14 | 0.284 | -27.716/-69.716/-139.716 | -1.284 | A;C合并代数段 |
| 307/339 | connection:q[175] | R457,R462,R467 | 10,11,12,13,14,15 | 5376 | 0.2491 | 0.046 | 0 | 纯代数/alias | 6 | 0.278 | -11.722/-29.722/-59.722 | -2.997 | A;C合并代数段 |
| 322/354 | connection:q[198] | R457,R462,R467,R472,R477,R482,R487 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23 | 12544 | 0.2453 | 0.020 | 0 | 纯代数/alias | 14 | 0.274 | -27.726/-69.726/-139.726 | -1.285 | A;C合并代数段 |
| 166/182 | connection:b102 | R465,R470,R475,R480,R485,R490 | 14,15,16,17,18,19,20,21,22,23,24,25 | 10752 | 0.2440 | 0.023 | 0 | 纯代数/alias | 12 | 0.272 | -23.728/-59.728/-119.728 | -1.499 | A;C合并代数段 |
| 321/353 | connection:q[197] | R457,R462,R467,R472,R477,R482,R487 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23 | 12544 | 0.2388 | 0.019 | 0 | 纯代数/alias | 14 | 0.267 | -27.733/-69.733/-139.733 | -1.285 | A;C合并代数段 |
| 324/356 | connection:q[202] | R457,R462,R467,R472,R477,R482,R487 | 10,11,12,13,14,15,16,17,18,19,20,21,22,23 | 12544 | 0.2360 | 0.019 | 0 | 纯代数/alias | 14 | 0.263 | -27.737/-69.737/-139.737 | -1.285 | A;C合并代数段 |
| 167/183 | connection:b103 | R465,R470,R475,R480,R485,R490 | 14,15,16,17,18,19,20,21,22,23,24,25 | 10752 | 0.2334 | 0.022 | 0 | 纯代数/alias | 12 | 0.260 | -23.740/-59.740/-119.740 | -1.500 | A;C合并代数段 |
| 445/453 | alias:amesim_pnpl01_2.port_1 | R462,R467,R472,R477,R482,R487,R492 | 12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 12544 | 0.2320 | 0.018 | 0 | 纯代数/alias | 14 | 0.259 | -27.741/-69.741/-139.741 | -1.286 | A;C合并代数段 |
| 444/452 | alias:amesim_pnpl01_1.port_1 | R462,R467,R472,R477,R482,R487,R492 | 12,13,14,15,16,17,18,19,20,21,22,23,24,25 | 12544 | 0.2265 | 0.018 | 0 | 纯代数/alias | 14 | 0.253 | -27.747/-69.747/-139.747 | -1.286 | A;C合并代数段 |
| 446/454 | alias:amesim_pnpl01_3.port_1 | R467,R472,R477,R482,R487,R492 | 14,15,16,17,18,19,20,21,22,23,24,25 | 10752 | 0.2229 | 0.021 | 0 | 纯代数/alias | 12 | 0.249 | -23.751/-59.751/-119.751 | -1.501 | A;C合并代数段 |
| 168/184 | connection:b104 | R465,R470,R475,R480,R485,R490 | 14,15,16,17,18,19,20,21,22,23,24,25 | 10752 | 0.2211 | 0.021 | 0 | 纯代数/alias | 12 | 0.247 | -23.753/-59.753/-119.753 | -1.501 | A;C合并代数段 |
| 317/349 | connection:q[191] | R457,R462,R467,R472,R477,R482 | 10,11,12,13,14,15,16,17,18,19,20,21 | 10752 | 0.2148 | 0.020 | 0 | 纯代数/alias | 12 | 0.240 | -23.760/-59.760/-119.760 | -1.501 | A;C合并代数段 |
| 318/350 | connection:q[192] | R457,R462,R467,R472,R477,R482 | 10,11,12,13,14,15,16,17,18,19,20,21 | 10752 | 0.2124 | 0.020 | 0 | 纯代数/alias | 12 | 0.237 | -23.763/-59.763/-119.763 | -1.502 | A;C合并代数段 |
| 319/351 | connection:q[193] | R457,R462,R467,R472,R477,R482 | 10,11,12,13,14,15,16,17,18,19,20,21 | 10752 | 0.2111 | 0.020 | 0 | 纯代数/alias | 12 | 0.236 | -23.764/-59.764/-119.764 | -1.502 | A;C合并代数段 |
| 447/455 | alias:amesim_pnpl01_4.port_1 | R467,R472,R477,R482,R487,R492 | 14,15,16,17,18,19,20,21,22,23,24,25 | 10752 | 0.1987 | 0.018 | 0 | 纯代数/alias | 12 | 0.222 | -23.778/-59.778/-119.778 | -1.503 | A;C合并代数段 |
| 320/352 | connection:q[196] | R457,R462,R467,R472,R477,R482 | 10,11,12,13,14,15,16,17,18,19,20,21 | 10752 | 0.1975 | 0.018 | 0 | 纯代数/alias | 12 | 0.220 | -23.780/-59.780/-119.780 | -1.503 | A;C合并代数段 |
| 448/456 | alias:amesim_pnpl01_5.port_1 | R472,R477,R482,R487,R492 | 16,17,18,19,20,21,22,23,24,25 | 8960 | 0.1857 | 0.021 | 0 | 纯代数/alias | 10 | 0.207 | -19.793/-49.793/-99.793 | -1.805 | A;C合并代数段 |
| 170/186 | connection:b106 | R470,R475,R480,R485,R490 | 16,17,18,19,20,21,22,23,24,25 | 8960 | 0.1836 | 0.020 | 0 | 纯代数/alias | 10 | 0.205 | -19.795/-49.795/-99.795 | -1.805 | A;C合并代数段 |
| 171/187 | connection:b107 | R470,R475,R480,R485,R490 | 16,17,18,19,20,21,22,23,24,25 | 8960 | 0.1789 | 0.020 | 0 | 纯代数/alias | 10 | 0.200 | -19.800/-49.800/-99.800 | -1.806 | A;C合并代数段 |
| 313/345 | connection:q[185] | R457,R462,R467,R472,R477 | 10,11,12,13,14,15,16,17,18,19 | 8960 | 0.1765 | 0.020 | 0 | 纯代数/alias | 10 | 0.197 | -19.803/-49.803/-99.803 | -1.806 | A;C合并代数段 |
| 314/346 | connection:q[186] | R457,R462,R467,R472,R477 | 10,11,12,13,14,15,16,17,18,19 | 8960 | 0.1755 | 0.020 | 0 | 纯代数/alias | 10 | 0.196 | -19.804/-49.804/-99.804 | -1.806 | A;C合并代数段 |
| 169/185 | connection:b105 | R470,R475,R480,R485,R490 | 16,17,18,19,20,21,22,23,24,25 | 8960 | 0.1752 | 0.020 | 0 | 纯代数/alias | 10 | 0.196 | -19.804/-49.804/-99.804 | -1.806 | A;C合并代数段 |
| 315/347 | connection:q[187] | R457,R462,R467,R472,R477 | 10,11,12,13,14,15,16,17,18,19 | 8960 | 0.1730 | 0.019 | 0 | 纯代数/alias | 10 | 0.193 | -19.807/-49.807/-99.807 | -1.806 | A;C合并代数段 |
| 308/340 | connection:q[178] | R457,R462,R467 | 10,11,12,13,14,15 | 5376 | 0.1706 | 0.032 | 0 | 纯代数/alias | 6 | 0.190 | -11.810/-29.810/-59.810 | -3.011 | A;C合并代数段 |
| 316/348 | connection:q[190] | R457,R462,R467,R472,R477 | 10,11,12,13,14,15,16,17,18,19 | 8960 | 0.1644 | 0.018 | 0 | 纯代数/alias | 10 | 0.183 | -19.817/-49.817/-99.817 | -1.807 | A;C合并代数段 |
| 449/457 | alias:amesim_pnpl01_6.port_1 | R472,R477,R482,R487,R492 | 16,17,18,19,20,21,22,23,24,25 | 8960 | 0.1624 | 0.018 | 0 | 纯代数/alias | 10 | 0.181 | -19.819/-49.819/-99.819 | -1.808 | A;C合并代数段 |
| 310/342 | connection:q[180] | R457,R462,R467,R472 | 10,11,12,13,14,15,16,17 | 7168 | 0.1525 | 0.021 | 0 | 纯代数/alias | 8 | 0.170 | -15.830/-39.830/-79.830 | -2.261 | A;C合并代数段 |
| 173/189 | connection:b109 | R475,R480,R485,R490 | 18,19,20,21,22,23,24,25 | 7168 | 0.1500 | 0.021 | 0 | 纯代数/alias | 8 | 0.167 | -15.833/-39.833/-79.833 | -2.261 | A;C合并代数段 |
| 311/343 | connection:q[181] | R457,R462,R467,R472 | 10,11,12,13,14,15,16,17 | 7168 | 0.1468 | 0.020 | 0 | 纯代数/alias | 8 | 0.164 | -15.836/-39.836/-79.836 | -2.262 | A;C合并代数段 |
| 172/188 | connection:b108 | R475,R480,R485,R490 | 18,19,20,21,22,23,24,25 | 7168 | 0.1435 | 0.020 | 0 | 纯代数/alias | 8 | 0.160 | -15.840/-39.840/-79.840 | -2.262 | A;C合并代数段 |
| 174/190 | connection:b110 | R475,R480,R485,R490 | 18,19,20,21,22,23,24,25 | 7168 | 0.1435 | 0.020 | 0 | 纯代数/alias | 8 | 0.160 | -15.840/-39.840/-79.840 | -2.262 | A;C合并代数段 |
| 309/341 | connection:q[179] | R457,R462,R467,R472 | 10,11,12,13,14,15,16,17 | 7168 | 0.1425 | 0.020 | 0 | 纯代数/alias | 8 | 0.159 | -15.841/-39.841/-79.841 | -2.262 | A;C合并代数段 |
| 312/344 | connection:q[184] | R457,R462,R467,R472 | 10,11,12,13,14,15,16,17 | 7168 | 0.1358 | 0.019 | 0 | 纯代数/alias | 8 | 0.152 | -15.848/-39.848/-79.848 | -2.263 | A;C合并代数段 |
| 451/459 | alias:amesim_pnpl01_8.port_1 | R477,R482,R487,R492 | 18,19,20,21,22,23,24,25 | 7168 | 0.1349 | 0.019 | 0 | 纯代数/alias | 8 | 0.151 | -15.849/-39.849/-79.849 | -2.263 | A;C合并代数段 |
| 450/458 | alias:amesim_pnpl01_7.port_1 | R477,R482,R487,R492 | 18,19,20,21,22,23,24,25 | 7168 | 0.1348 | 0.019 | 0 | 纯代数/alias | 8 | 0.150 | -15.850/-39.850/-79.850 | -2.263 | A;C合并代数段 |
| 13/13 | flow:amesim_pnor001_7.port_2 | R89,R136,R186,R232,R287,R420 | 2,3,4,5,6,9 | 5376 | 0.1287 | 0.024 | 0 | 纯代数/alias | 6 | 0.144 | -11.856/-29.856/-59.856 | -3.019 | A;C合并代数段 |
| 27/35 | flow:amesim_pnvo001_6.port_3 | R91,R139,R189,R290,R338,R422 | 2,3,4,6,7,9 | 5376 | 0.1272 | 0.024 | 0 | 纯代数/alias | 6 | 0.142 | -11.858/-29.858/-59.858 | -3.019 | A;C合并代数段 |
| 25/33 | flow:amesim_pnvo001_5.port_3 | R2,R139,R234,R290 | 0,1,3,5,6,8 | 5376 | 0.1240 | 0.023 | 0 | 纯代数/alias | 6 | 0.138 | -11.862/-29.862/-59.862 | -3.020 | A;C合并代数段 |
| 31/39 | flow:amesim_pnvo001_8.port_3 | R3,R54,R91,R139,R189,R381 | 0,1,2,3,4,8 | 5376 | 0.1230 | 0.023 | 0 | 纯代数/alias | 6 | 0.137 | -11.863/-29.863/-59.863 | -3.020 | A;C合并代数段 |
| 29/37 | flow:amesim_pnvo001_7.port_3 | R91,R139,R189,R235,R338,R381 | 2,3,4,5,7,8 | 5376 | 0.1209 | 0.022 | 0 | 纯代数/alias | 6 | 0.135 | -11.865/-29.865/-59.865 | -3.020 | A;C合并代数段 |
| 176/192 | connection:b112 | R480,R485,R490 | 20,21,22,23,24,25 | 5376 | 0.1148 | 0.021 | 0 | 纯代数/alias | 6 | 0.128 | -11.872/-29.872/-59.872 | -3.022 | A;C合并代数段 |
| 15/15 | flow:amesim_pnor001_8.port_2 | R89,R136,R232,R336,R379,R420 | 2,3,5,7,8,9 | 5376 | 0.1136 | 0.021 | 0 | 纯代数/alias | 6 | 0.127 | -11.873/-29.873/-59.873 | -3.022 | A;C合并代数段 |
| 11/11 | flow:amesim_pnor001_6.port_2 | R89,R136,R186,R232,R287,R335 | 2,3,4,5,6,7 | 5376 | 0.1128 | 0.021 | 0 | 纯代数/alias | 6 | 0.126 | -11.874/-29.874/-59.874 | -3.022 | A;C合并代数段 |
| 306/338 | connection:q[174] | R457,R462,R467 | 10,11,12,13,14,15 | 5376 | 0.1104 | 0.021 | 0 | 纯代数/alias | 6 | 0.123 | -11.877/-29.877/-59.877 | -3.022 | A;C合并代数段 |
| 305/337 | connection:q[173] | R457,R462,R467 | 10,11,12,13,14,15 | 5376 | 0.1104 | 0.021 | 0 | 纯代数/alias | 6 | 0.123 | -11.877/-29.877/-59.877 | -3.022 | A;C合并代数段 |
| 177/193 | connection:b113 | R480,R485,R490 | 20,21,22,23,24,25 | 5376 | 0.1102 | 0.020 | 0 | 纯代数/alias | 6 | 0.123 | -11.877/-29.877/-59.877 | -3.022 | A;C合并代数段 |
| 175/191 | connection:b111 | R480,R485,R490 | 20,21,22,23,24,25 | 5376 | 0.1049 | 0.020 | 0 | 纯代数/alias | 6 | 0.117 | -11.883/-29.883/-59.883 | -3.023 | A;C合并代数段 |
| 453/461 | alias:amesim_pnpl01_10.port_1 | R482,R487,R492 | 20,21,22,23,24,25 | 5376 | 0.0992 | 0.018 | 0 | 纯代数/alias | 6 | 0.111 | -11.889/-29.889/-59.889 | -3.024 | A;C合并代数段 |
| 452/460 | alias:amesim_pnpl01_9.port_1 | R482,R487,R492 | 20,21,22,23,24,25 | 5376 | 0.0981 | 0.018 | 0 | 纯代数/alias | 6 | 0.109 | -11.891/-29.891/-59.891 | -3.025 | A;C合并代数段 |
| 9/9 | flow:amesim_pnor001_5.port_2 | R89,R136,R186,R232 | 2,3,4,5 | 3584 | 0.0814 | 0.023 | 0 | 纯代数/alias | 4 | 0.091 | -7.909/-19.909/-39.909 | -4.542 | A;C合并代数段 |
| 303/335 | connection:q[169] | R457,R462 | 10,11,12,13 | 3584 | 0.0803 | 0.022 | 0 | 纯代数/alias | 4 | 0.090 | -7.910/-19.910/-39.910 | -4.542 | A;C合并代数段 |
| 179/195 | connection:b115 | R485,R490 | 22,23,24,25 | 3584 | 0.0791 | 0.022 | 0 | 纯代数/alias | 4 | 0.088 | -7.912/-19.912/-39.912 | -4.542 | A;C合并代数段 |
| 302/334 | connection:q[168] | R457,R462 | 10,11,12,13 | 3584 | 0.0762 | 0.021 | 0 | 纯代数/alias | 4 | 0.085 | -7.915/-19.915/-39.915 | -4.543 | A;C合并代数段 |
| 178/194 | connection:b114 | R485,R490 | 22,23,24,25 | 3584 | 0.0743 | 0.021 | 0 | 纯代数/alias | 4 | 0.083 | -7.917/-19.917/-39.917 | -4.544 | A;C合并代数段 |
| 301/333 | connection:q[167] | R457,R462 | 10,11,12,13 | 3584 | 0.0732 | 0.020 | 0 | 纯代数/alias | 4 | 0.082 | -7.918/-19.918/-39.918 | -4.544 | A;C合并代数段 |
| 304/336 | connection:q[172] | R457,R462 | 10,11,12,13 | 3584 | 0.0729 | 0.020 | 0 | 纯代数/alias | 4 | 0.081 | -7.919/-19.919/-39.919 | -4.544 | A;C合并代数段 |
| 180/196 | connection:b116 | R485,R490 | 22,23,24,25 | 3584 | 0.0728 | 0.020 | 0 | 纯代数/alias | 4 | 0.081 | -7.919/-19.919/-39.919 | -4.544 | A;C合并代数段 |
| 454/462 | alias:amesim_pnpl01_11.port_1 | R487,R492 | 22,23,24,25 | 3584 | 0.0720 | 0.020 | 0 | 纯代数/alias | 4 | 0.080 | -7.920/-19.920/-39.920 | -4.544 | A;C合并代数段 |
| 455/463 | alias:amesim_pnpl01_12.port_1 | R487,R492 | 22,23,24,25 | 3584 | 0.0690 | 0.019 | 0 | 纯代数/alias | 4 | 0.077 | -7.923/-19.923/-39.923 | -4.545 | A;C合并代数段 |
| 5/5 | flow:amesim_pnor001_3.port_2 | R88,R185 | 2,4 | 1792 | 0.0492 | 0.027 | 0 | 纯代数/alias | 2 | 0.055 | -3.945/-9.945/-19.945 | -9.101 | A;C合并代数段 |
| 299/331 | connection:q[163] | R457 | 10,11 | 1792 | 0.0464 | 0.026 | 0 | 纯代数/alias | 2 | 0.052 | -3.948/-9.948/-19.948 | -9.103 | A;C合并代数段 |
| 298/330 | connection:q[162] | R457 | 10,11 | 1792 | 0.0454 | 0.025 | 0 | 纯代数/alias | 2 | 0.051 | -3.949/-9.949/-19.949 | -9.104 | A;C合并代数段 |
| 7/7 | flow:amesim_pnor001_4.port_2 | R136,R286 | 3,6 | 1792 | 0.0448 | 0.025 | 0 | 纯代数/alias | 2 | 0.050 | -3.950/-9.950/-19.950 | -9.104 | A;C合并代数段 |
| 183/199 | connection:b119 | R490 | 24,25 | 1792 | 0.0422 | 0.024 | 0 | 纯代数/alias | 2 | 0.047 | -3.953/-9.953/-19.953 | -9.105 | A;C合并代数段 |
| 3/3 | flow:amesim_pnor001_2.port_2 | R185,R231 | 4,5 | 1792 | 0.0421 | 0.023 | 0 | 纯代数/alias | 2 | 0.047 | -3.953/-9.953/-19.953 | -9.105 | A;C合并代数段 |
| 182/198 | connection:b118 | R490 | 24,25 | 1792 | 0.0411 | 0.023 | 0 | 纯代数/alias | 2 | 0.046 | -3.954/-9.954/-19.954 | -9.106 | A;C合并代数段 |
| 297/329 | connection:q[161] | R457 | 10,11 | 1792 | 0.0405 | 0.023 | 0 | 纯代数/alias | 2 | 0.045 | -3.955/-9.955/-19.955 | -9.106 | A;C合并代数段 |
| 300/332 | connection:q[166] | R457 | 10,11 | 1792 | 0.0377 | 0.021 | 0 | 纯代数/alias | 2 | 0.042 | -3.958/-9.958/-19.958 | -9.108 | A;C合并代数段 |
| 456/464 | alias:amesim_pnpl01_13.port_1 | R492 | 24,25 | 1792 | 0.0371 | 0.021 | 0 | 纯代数/alias | 2 | 0.041 | -3.959/-9.959/-19.959 | -9.108 | A;C合并代数段 |
| 457/465 | alias:amesim_pnpl01_14.port_1 | R492 | 24,25 | 1792 | 0.0367 | 0.020 | 0 | 纯代数/alias | 2 | 0.041 | -3.959/-9.959/-19.959 | -9.108 | A;C合并代数段 |
| 181/197 | connection:b117 | R490 | 24,25 | 1792 | 0.0363 | 0.020 | 0 | 纯代数/alias | 2 | 0.041 | -3.959/-9.959/-19.959 | -9.109 | A;C合并代数段 |
@@ -0,0 +1,219 @@
# Context fallback:盈利条件与下一步决策
日期:2026-09-17。范围:既有八路模型 0–10 s、896 个 Jacobian 的历史诊断数据。本轮只做数据归并和预算分析,没有构建新 worker、修改求解器、扩展 semantic replay 或重跑性能实验。
## 结论
**停止按单个 operation 推进 semantic replay。下一次只值得先测 `position379 / PNVO001_1` 中 `state_valve` 数值尾部的 kernel memo。** 原 context 查询、物性写入、valid 演变和 native 调用仍照常发生;这与跳过整个 operation 是不同实验。
- 当前 343 个 fallback operation 的跨组单次均值最高只有 **1.315 µs**;即使拆到 5,157 个 `(group, position)` 组合,最高均值也只有 **1.642 µs**。没有一个能承担当前 **7.109 µs** 的单 operation replay 开销,尚未计 baseline。
- 较贵 interval 是多个廉价 operation 的集合。不能把整个 interval 的预算发给其中每个 operation。
- whole-context guard 成功的 **20,608 次**继续使用原机制;失败的 **139,776 次**不能直接恢复 baseline 全 context,其真实出口均与 baseline context 不同。
- 不继续优化 R288,不启动 position52、R475 或其他区间的 semantic replay 实现。R490 等长区间只保留为预算上的备选,尚不具备立即实验的收益与语义证据。
## 1. 数据覆盖与计时口径
| 数据层 | 覆盖 / 结果 | 本报告用途 |
|---|---|---|
| 全量 census | 115 个失败 interval;156 个 group/interval;343 个 position;5,157 个 group/position | 次数、归属和摊销上限 |
| fallback 原计算 | 139,776 次 interval;4,620,672 次 operation 执行 | 去重后的成本分母 |
| 分层 profiling,扣空标记估计 | 全轨迹 context fallback **613.201 ms** | 总量交叉检查 |
| interval 独立计时 | **682.630 ms** | 原区间预算;含区间调度及诊断 hook,不含失败前的 guard |
| operation 独立计时 | **660.431 ms** | 分离 native 语句与纯代数/alias;operation 预算 |
| function 独立计时 | exclusive 合计 **879.118 ms** | 函数排序,不能加到以上时间或视为净收益 |
interval、operation、function 各三轮;每轮分层抽取 112/896 个 Jacobian,按频率折算全轨迹后取三轮算术平均。这里的“µs/次”是累计折算时间除以全量次数,不是单次延迟分位数。完整表另外保留三轮值和 min/max。
这些是**带插桩的预算估计**。极短 operation 的计时可能明显高估;函数 exclusive 仅扣除已插桩子调用。不同模式的差值不能直接当作 dispatch 成本,更不能叠加。R288 最新实验的机器负载与旧 profiling 不同,跨批代入只用于成本量级情景,不能预测加速。
### 可查验的完整交付物
- [115 个 interval、156 个 group/interval 的全部成本和预算,以及 196 个纯代数连续段](fallback_profitability_intervals.md)。按 `次数 × 单次成本` 排序,含 operation/native 数量、纯代数时间、主要函数、共享上限。
- [343 个 operation 的全部热点和预算](fallback_profitability_operations.md)。含 interval、group、原始 operation ID、原生调用、次数、时间、摊销边界与建议。
- [完整 JSON](../../test/fallback-profitability-20260917/analysis.json):另含全部 5,157 条 group/position 映射、原代码、逐区间函数数据、三轮折算原始数值、输入 SHA-256、预算情景和检查结果。
- [复现脚本](analyze_fallback_profitability.py):只读取历史产物。运行 `.venv-win/Scripts/python.exe -B tests/manual/analyze_fallback_profitability.py`。
原始依据:[fallback 诊断](../../test/context-fallback-20260917/report.md)、[原始统计](../../test/context-fallback-20260917/analysis.json)、[代码及依赖计划](../../test/context-fallback-20260917/plan.json)、[分层 profiling](local_probe_profile.md)、[R288 typed 实验](r288_typed_replay_report.md)。当前工作区其他改动未作为这份历史测量的重新验收对象。
## 2. 热点:大区间与廉价 operation 必须分开
### 累计成本最高的区间及重要对照
“纯代数 ms”来自 operation 模式,只计完全不含 native 调用的语句;不包含 native 内部的代数部分,后者尚无独立测量。
| interval / 范围 | group | 次数 | 原区间累计 ms | 原区间 µs/次 | ops / native ops | 纯代数 ms |
|---|---|---:|---:|---:|---:|---:|
| R490 [49,184) | 24,25 | 1,792 | 33.807 | 18.865 | 135 / 15 | 4.139 |
| R485 [50,181) | 22,23 | 1,792 | 31.667 | 17.671 | 131 / 14 | 4.072 |
| R480 [51,178) | 20,21 | 1,792 | 30.269 | 16.891 | 127 / 13 | 3.954 |
| R475 [52,175) | 18,19 | 1,792 | 27.257 | 15.211 | 123 / 12 | 3.841 |
| R470 [53,172) | 16,17 | 1,792 | 25.381 | 14.164 | 119 / 11 | 见完整表 |
| R465 [54,169) | 14,15 | 1,792 | 22.936 | 12.799 | 115 / 10 | 见完整表 |
| R460 [55,166) | 12,13 | 1,792 | 22.117 | 12.342 | 111 / 9 | 见完整表 |
| R3 [30,48) | 0 | 896 | 21.393 | 23.876 | 18 / 17 | 0.033 |
| R455 [56,161) | 10,11 | 1,792 | 20.735 | 11.571 | 105 / 8 | 见完整表 |
| R457 [297,444) | 10,11 | 1,792 | 15.880 | 8.862 | 147 / 4 | 5.653 |
| R49 [373,444) | 0 | 896 | 15.782 | 17.614 | 71 / 4 | 1.814 |
| R288 [16,17) | 6 | 896 | 0.918 | 1.024 | 1 / 1 | 0 |
R490–R455 前半区域主要是 `native_pipe_flow_cached_context`,包含 PH context 获取和 `state_valve` 等链路;R457/R49 的四个 native 是 `native_medium_orifice_context`。R3 的 17 个 native 集中在管路/节流流量,因而短区间也可能更贵。
**R49 暂不作首选**:区间模式是 17.614 µs/次,operation 模式总和却只有 7.283 µs/次。三轮区间值都偏高,说明不是简单挑掉一轮就能解决;尚未分离具体原因。不能把两者差额承诺为可消除的调度成本。R3 也存在 23.876 vs 20.471 µs 的模式差异,预算应同时参考两种口径。
group 0–9 合计 **361.823 ms**,group 10–25 合计 **320.807 ms**,group26 为 0。大区间并未占据全部 fallback。
### 累计最贵的几个 operation
| position / 原ID | operation | 失败执行次数 | 原计算累计 ms | µs/次 | 每 Jacobian 结构复用上限 |
|---|---|---:|---:|---:|---:|
| 379 / 24 | PNVO001_1.port_2 | 19,712 | 25.926 | 1.315 | 22 |
| 405 / 28 | PNVO001_3.port_2 | 19,712 | 24.907 | 1.264 | 22 |
| 392 / 26 | PNVO001_2.port_2 | 19,712 | 24.721 | 1.254 | 22 |
| 80 / 88 | PNL00R_4.port_1 | 19,712 | 24.410 | 1.238 | 22 |
| 82 / 90 | PNL00R_5.port_1 | 19,712 | 23.721 | 1.203 | 22 |
| 418 / 30 | PNVO001_4.port_2 | 19,712 | 23.021 | 1.168 | 22 |
重复 22 次让 baseline 有更好的摊销机会,**不能让每次 1.315 µs 的计算承担 7 µs 的 probe 成本**。同一 position 的 baseline 可以在结构上被这些组引用,但查询分支、消费字段、kernel 参数是否相同,仍须单独验证。现有“显式 operation 输入/输出相同”不是这种证明。
## 3. Break-even:必须给 baseline 与 probe 共用一个预算
令 `C` 为每次可省的原计算,`P` 为 validation + patch + commit + 新增调度等 probe 开销,`H` 为每个 Jacobian 新增 baseline 捕获成本,`m` 为这份记录可成功服务的 probe 数。单位均为 µs:
```text
net_per_J = m × (C − P) − H
P < C − H/m
H < m × (C − P)
```
不能同时把 `C` 分别全部分配给 H 和 P。表中 H 上限以 P=0 或指定值计算;只有严格低于上限才盈利。暂用全部成功、无新增失败成本的乐观条件。
若仅有 `k` 次成功、其余尝试付出失败 guard 成本 `F`:
```text
net_per_J = k × (C_success − P_success) − H − (attempts − k) × F
```
同一区间跨组最多共享一次 baseline 的结构上限通常是 1 或 2;operation 跨重叠区间可达到 22。**不能把同一 group/position 在多个 interval 中重复计价**,本分析按 census 的唯一归属去重。若动态 schema 无法共享,m 必须减小;如果要按组捕获 H,则不能继续除以全部组数。
### 用当前 R288 typed 成本作量级筛选
同批实测中位数:原 operation **1.775 µs**,typed 三阶段 **7.109 µs**,完整路径 **7.575 µs**,baseline 新增 **18.258 µs/J**。计入 baseline 的配对净收益为 **−20.415 ms/896 次**;所有轮次均为负。旧批的 1.143/22.521 µs 不能和最新批混为一组对照。
以下把 `H=18.258` 仅作为预算情景;P 也必须覆盖新增调度/统计等成本,使用三阶段 7.109 已比完整路径更乐观。
| 候选 | C µs | m 上限 | Hmax,P=0 | Hmax,P=5 | Pmax,H=18.258 | P≈7.109 的判断 |
|---|---:|---:|---:|---:|---:|---|
| R288,最新同批 native | 1.775 | 1 | 1.775 | −3.225 | −16.483 | 即使H=0也不行;停止 |
| position379,原 operation | 1.315 | 22 | 28.935 | −81.065 | 0.485 | 单operation replay不行 |
| R490,整个区间 | 18.865 | 2 | 37.731 | 27.731 | 9.736 | 一次融合replay账面可行;未验证 |
| R485,整个区间 | 17.671 | 2 | 35.342 | 25.342 | 8.542 | 同上 |
| R480,整个区间 | 16.891 | 2 | 33.783 | 23.783 | 7.763 | 余量很小 |
| R475,整个区间 | 15.211 | 2 | 30.421 | 20.421 | 6.082 | 当前量级不行;不扩展 |
| R3,整个区间 | 23.876 | 1 | 23.876 | 18.876 | 5.619 | 需低于约5µs;op口径仅允许2.213µs |
| R457,整个区间 | 8.862 | 2 | 17.724 | 7.724 | −0.267 | baseline本身已超预算 |
R490 包含 15 个 native operation,若逐个使用 7.109 µs replay,单 probe 仅 replay 就要约 **106.636 µs**,远超整个区间 18.865 µs。表中可行性只适用于**整个区间一次融合处理**。不能假设 15 个 operation 的查询、字段保护与副作用能以一个 R288 的成本处理。
若要求每个长区间 `P<2/5/10 µs`,各自允许的 baseline 成本已在完整表逐项列出;并非只给一个统一的 7 µs 门槛。所有单 operation 的原测量均值都低于 2 µs,甚至 `<2 µs` 本身也不足以证明它们盈利。
## 4. 按真正的函数成本选择方法
以下 exclusive 时间来自函数模式,只作排名,不与 operation 模式相加。`state_valve` exclusive 包括未插桩的数学函数和包装等,**不是已经单独测出的纯数值尾部成本**。
| 函数 / 工作 | 调用数 | exclusive ms | 平均 exclusive µs | 建议 |
|---|---:|---:|---:|---|
| state_valve | 495,790 | 388.070 | 0.783 | E:先测数值尾部;保留所有context访问 |
| property_pt | 1,225,458 | 114.822 | 0.094 | F:若未来优化查找,必须保持有序first-match;不能承担µs级额外guard |
| local_isentropic | 989,704 | 92.872 | 0.094 | 现有valid命中已跳过部分计算;需先分开hit与真实重算,不能整体memo掉observe/valid写入 |
| native_jacobian_scalar_get | 1,439,304 | 62.124 | 0.043 | 既有memo查询成本;避免再叠通用查表/完整key解释器 |
| property_density | 1,399,544 | 52.772 | 0.038 | 主要是cache/memo路径,保持原机制 |
| native_pipe_flow_context | 379,904 | 50.607 | 0.133 | inclusive约601.523ms包含下游;不能再加到state_valve |
| native_temperature_ph_context | 320,836 | 43.470 | 0.135 | 查询/包装仍发生,实际PH反算为0 |
| native_viscosity | 307,709 | 25.702 | 0.084 | A;单独加memo通常预算太小,尚无盈利证据 |
| native_medium_orifice_context | 125,440 | 17.183 | 0.137 | 包装本身廉价,关注其state_valve数学部分 |
| native_pipe_flow_cached_context | 379,904 | 16.638 | 0.044 | 保留pipe语义;不要为省wrapper引入replay |
`native_temperature_ph`、`native_density`、`native_pipe_resistance` 在这批 fallback 中实际执行次数都为 **0**。它们的昂贵求解已被 Jacobian memo 覆盖,不能再次把这些计算算成新优化的可省部分。
源码依据:归档 [properties.c](../../test/local-probe-20260917/worker/properties.c) 中 `state_valve` 先执行 `isentropic` 与 `property_density`,之后才进行 `pow/sqrt/log/tanh` 等数学计算;[orifice.c](../../test/local-probe-20260917/worker/orifice.c) 的 PNVO 调用链还会获取 PH/PT、更新context并进行有限性返回检查。整个 `state_valve` 并不是无副作用的纯函数。
### A–F 明确决策
| 类型 | 适用对象 | 决策 |
|---|---|---|
| A 直接原计算 | R288/position16;所有当前需要约7µs replay的单operation;廉价lookup/viscosity/alias | 停止逐operation semantic replay;默认重算 |
| B 原whole-context restore | 现有guard成功的20,608次context复用 | 保留原guard与restore;不扩大到guard失败路径 |
| C 切分interval | 196个完全不含native的连续段;例R457 [297,379) | 只考虑批量段和低成本输出恢复;不逐alias加guard |
| D 局部context replay | R490/R485/R480的融合区间是假设候选 | 仅预算保留;没有全区间读写契约,也无足够实测收益,暂不实施 |
| E 昂贵纯数值kernel memo | 首选position379中的state_valve数值尾部 | 下一次唯一建议实验;先测小于µs的真实成本与key复用 |
| F 更便宜方案 | property_pt有序查询、现有memo定位、静态代数段恢复 | 先证明确有热点且额外成本在几十至数百ns预算内;不作为本次扩展任务 |
B 的全部成功区间为 R0、R135、R378、R454、R459、R464、R469、R474、R479、R484、R489、R494。只沿用已经满足原 guard 的 group/interval 对,不由区间ID推导新的复用范围。
## 5. 候选排序:只推荐启动一个
### 第一名:position379 / PNVO001_1 的 state_valve 数值尾部
- 当前完整 operation 为 **1.315 µs/次**,19,712 次,累计 **25.926 ms**。三轮累计 25.631–26.268 ms。组为 0–5、10–25,共22组。完整 group→interval 映射在JSON中。
- 主要工作是 `native_medium_orifice_context → medium_valve → state_valve`。保留 PH/PT、等熵字段计算/valid写入、density读取及所有context副作用,**只考虑在这些步骤之后**复用数学尾部的 `cm/velocity`。
- 为什么优于 R288 replay:不是因为单次 operation 更贵,而是换成只比较少量最终数值输入的 kernel 机制,避免有序条目重放;baseline 在结构上最多可摊到22组,而 R288 的目标只有1组。候选还是累计最贵的单个 operation,且数值尾部边界可明确核对。
- **可省上界**:25.926 ms 是整个 operation 零成本消失的宽松上界;数值尾部只占其中一部分,实际可省必须更小。没有该position的尾部独立计时,不能把全局0.783µs当作它的实测尾部成本,更不能承诺25.926ms收益。
- 令尾部实测每次成本为 `K`、memo probe成本为 `G`、baseline新增为 `H`,全部22组可命中时必须满足 `G + H/22 < K < 1.315 µs`。一轮净收益是 `19712×(K−G)−896×H` µs;命中不足时另扣miss开销。
- 初始工程目标可设 `G≤0.25 µs`、`H≤1 µs/J`,其成本门槛约 **0.295 µs/命中**;这是待测目标,不是已有实现速度。若尾部K≤该值或实际开销不达标,则停止。若仍沿用18.258µs的baseline新增,则仅H/22就要0.830µs,很可能再次失败。
**下一次实验的边界**:仍用独立 worker,只选这个 position;先采集数学尾部的实际输入位与成本,确认同一Jacobian跨组key重复比例,再实现可拒绝的最小kernel memo。候选key应依据尾部真实使用的 `p/T/pd/g/rho` 等值确定,不拿operation显式输入相同代替。必须核对分支、非有限值、errno/浮点状态、memo生命周期;数学库也可能有副作用,不能默认忽略。
数值尾部不再触碰property context有利于验证,但不是免验。要求所有矩阵、求解输出、context/pipe/memo、warning和计数逐位一致;成本版无shadow。测量命中/拒绝、尾部原成本、key检查、命中返回和baseline新增成本,只有完整机制净收益为正才考虑其他position。**本轮没有实施这个实验。**
### 第二名(备选,不立即启动):R457 的纯代数连续段
R457/group10,11 整体 8.862µs、15.880ms;主要native仍是4个节流口。真正切分对象为 **[297,379)** 的82个纯代数operation,平均段成本 **1.846µs**,1,792次合计 **3.308ms**。这里不省任何native物理计算,保留全部context操作,可验证性较高。
选择C而非D:每段额外guard/restore加baseline摊销必须 `<1.846µs`,实际应显著低于1µs以留下余量。由于82个短语句分别计时,3.308ms可能含明显marker成本,先用段级低扰动测量核实;不能把该数字当作可兑现收益。全部代数段都低于2µs,逐条加机制会失败。它比R288 replay更简单,但单点总上界较小,排在kernel之后。
### 第三名(预算备选,暂不做):R490 的融合局部context机制
R490/group24,25 整体 **18.865µs**,1,792次 **33.807ms**;其中15个native语句,主要是pipe flow、state_valve及查询。两个组可结构共享一次baseline,预算明显比R288宽。
若H仍为18.258µs,**全部验证、所有patch和commit合计必须低于9.736µs/整个interval**;要有可靠余量,目标至少应低于5µs。若P=7.109、100%成功且两组共享记录,账面仅剩约 **4.708ms**,尚未扣完整路径额外开销。未知读写覆盖、15个native的effects以及guard扩张很容易用完预算,因此不建议现在开始完整semantic实现。
相比之下,先把state_valve里的数学部分分离,能检验是否存在更低成本方法,再决定是否需要复杂的融合局部context。R485/R480也是同类预算备选,不扩展实现。
## 6. 还剩多少空间:条件上界,不是加速承诺
### 以完整interval只付一次开销的乐观情景
每行只统计满足预算的interval,并扣除该行假设的P/H;各行是不同情景,不能相加。H每个Jacobian捕获一次并跨所有失败组共享,100%接受,无新增失败开销。跨批typed数字只用作量级参照。
| 假设每interval的P / baseline H,µs | 过预算interval数 | 涉及原fallback ms | 占原区间fallback | 情景剩余净空间 ms |
|---|---:|---:|---:|---:|
| 2 / 0 | 72 | 613.980 | 89.94% | 438.364 |
| 5 / 0 | 35 | 496.359 | 72.71% | 249.959 |
| 7.109 / 0 | 24 | 415.138 | 60.81% | 160.350 |
| 10 / 0 | 13 | 284.669 | 41.70% | 96.509 |
| 2 / 18.258 | 9 | 235.564 | 34.51% | 57.870 |
| 5 / 18.258 | 6 | 169.775 | 24.87% | 22.341 |
| 7.109 / 18.258 | 3 | 95.742 | 14.03% | 8.448 |
| 10 / 18.258 | 0 | 0 | 0% | 0 |
因此,“高成本interval占60.81%”只对应零baseline、整个interval固定7.109µs的理想条件。计入当前baseline量级后缩到14.03%,而这14.03%也只是**原计算覆盖量**,扣成本后仅8.448ms。三个区间在独立operation模式下也过相同账面门槛,但仍未证明融合实现可以达到该成本。
若每个group都要独立捕获,H不能被2组摊薄;在P=7.109/H=18.258情景下,所有interval都不再盈利。当前没有任何一个新interval被证明具有实际semantic replay净收益。
### 换方法后的空间
- **单operation semantic replay,维持当前成本量级:可盈利覆盖为0%。** 这不是证明所有更便宜的semantic方法都不可能,只是否定当前这条逐operation路径。
- **native语句工作池:577.486ms,占operation模式87.44%。** 这是55个pipe/orifice语句全部计算的宽松上界,里面有必须保留的context访问和现有memo开销,不能全部归给kernel优化。
- **四个PNVO候选position379/392/405/418:98.575ms的整个operation上界。** 下一次只测379,其25.926ms上界之外不先承诺扩展。kernel尾部占比和实际命中率尚未测出,所以不能给出“已值得优化的kernel占fallback百分比”。
- **纯代数/alias:82.944ms,占operation模式12.56%。** 分布在196个连续段,最大段均值也只有1.846µs。只能考虑少数长段的廉价批量恢复;全量消除的82.944ms是含短语句插桩的宽松上界。
- 全部fallback原工作约 **0.61–0.68s/轨迹** 只是所有相关工作免费消失的极宽上界,不能当成可实现的净节省,也不能由此推算最终solver加速比例。其余Jacobian/积分成本仍然存在。
**方向调整**:R288负收益证明的是低成本operation不适合当前这种replay机制,不是semantic replay原理不可行。眼下数据并未发现单次昂贵的fallback operation;发现的是大量重复的小计算,以及由它们组成的大interval。下一步应验证**低成本的kernel复用能否赚回自己的开销**,而不是继续扩大context重放范围。
## 7. 本轮验证与未确定项
分析脚本重新从三轮原始ticks/frequency/采样数还原interval和operation时间,与原analysis逐项核对;检查全部计数、唯一group/position归属、native分类一致性、代数段和各层汇总守恒。生成的JSON保留输入哈希和检查清单。没有用容差修改仿真结果,也没有新增数值正确性声明。
仍未测量:候选379的数学尾部专属成本与key命中率、长区间融合metadata成本、跨组动态契约共享率、切分后低扰动段成本,以及R49跨模式计时差异的成因。这些缺口决定了目前可以提出可证伪的性能实验,不能宣布新的优化已经盈利。
**最终选择:保留whole-context机制和廉价原计算;停止R288 replay;下一次只做position379的state_valve数值尾部kernel memo成本/正确性实验。**
+40
View File
@@ -0,0 +1,40 @@
**局部 Jacobian probe 实验的运行说明**
这是独立 Windows native worker 实验,不改变生产默认求解路径。输入默认为八路模型 `tests/data/test-mql-8-corrected.json`。生成源码、可执行文件、计数、二进制结果及报告保存在 `test/local-probe-20260917/`。
先构建验证版本,再按 group 26 → group 18–26 → 全部 group 验证;发现任意 dy/w 差异时程序立即终止。
```powershell
& .venv-win/Scripts/python.exe -B tests/manual/local_probe_experiment.py prepare --audit
& .venv-win/Scripts/python.exe -B tests/manual/local_probe_experiment.py run --audit --label reference-audit --mask 0
& .venv-win/Scripts/python.exe -B tests/manual/local_probe_experiment.py run --audit --label group26-final-audit --mask 0x4000000
& .venv-win/Scripts/python.exe -B tests/manual/local_probe_experiment.py run --audit --label small-final-audit --mask 0x7fc0000
& .venv-win/Scripts/python.exe -B tests/manual/local_probe_experiment.py run --audit --label all-audit --mask 0x7ffffff
& .venv-win/Scripts/python.exe -B tests/manual/local_probe_experiment.py guard
& .venv-win/Scripts/python.exe -B -m unittest tests.test_local_probe_experiment
```
再构建不含影子计算、矩阵落盘和内核入口计数的性能版本。先预热,然后交替运行五对;测量期间不要并行编译或启动其他仿真。
```powershell
& .venv-win/Scripts/python.exe -B tests/manual/local_probe_experiment.py prepare
& .venv-win/Scripts/python.exe -B tests/manual/local_probe_experiment.py run --label reference-warmup --mask 0
& .venv-win/Scripts/python.exe -B tests/manual/local_probe_experiment.py run --label all-warmup --mask 0x7ffffff
for ($probePair = 0; $probePair -lt 5; $probePair++) {
if ($probePair % 2 -eq 0) {
& .venv-win/Scripts/python.exe -B tests/manual/local_probe_experiment.py run --label "reference-run-$probePair" --mask 0
& .venv-win/Scripts/python.exe -B tests/manual/local_probe_experiment.py run --label "all-run-$probePair" --mask 0x7ffffff
} else {
& .venv-win/Scripts/python.exe -B tests/manual/local_probe_experiment.py run --label "all-run-$probePair" --mask 0x7ffffff
& .venv-win/Scripts/python.exe -B tests/manual/local_probe_experiment.py run --label "reference-run-$probePair" --mask 0
}
}
& .venv-win/Scripts/python.exe -B tests/manual/local_probe_experiment.py cold
& .venv-win/Scripts/python.exe -B tests/manual/compare_local_probe.py
```
重复使用相同 label 会覆盖该实验目录下的同名运行证据。比较器针对默认八路、0–10 s 的验收集,其他模型或终止时间应使用独立输出目录和对应验收配置,不能混入当前比较集。
`plan.json` 保存每组扰动状态、保守依赖范围、执行区间、上下文检查点及操作输入输出;`comparison.json` 包含逐组实际执行/跳过计数、所有矩阵比对结果和全部计时样本;`report.md` 为中文分析。
实验限制:只接受已有 canonical Jacobian reuse 且无循环调度块的生成模型;支持最多63组。property context 和 pipe cache 采用逐位保护,时间或组外状态变化会失效。运行期对象属于独立单次 worker,不能直接将该代码嵌入同进程并行求解。audit额外计算不进入求解器nfev,单独计入auditComparisons;性能测量必须使用未开启audit的worker。
+292
View File
@@ -0,0 +1,292 @@
"""Isolated Jacobian-only experiment. Never installs a production build.
prepare builds a reference worker and an opt-in local-probe worker. The latter
keeps the original model_eval_internal verbatim and adds a canonical clone.
Only compiler-owned, acyclic schedule operations are eligible for skipping.
Exact live property-context and pipe-cache guards preserve graph-external
cache semantics. An audit compares every probe's complete dy/w to the original.
"""
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor
import argparse, hashlib, json, os, re, shutil, subprocess, sys, time
ROOT=Path(__file__).resolve().parents[2]
sys.path.insert(0,str(ROOT))
from app.main import compile_system_xml_network
from app.simulation.native_codegen.input import load_input
from app.simulation.native_codegen.compiler import compile_native_program
from app.simulation.native_codegen.jacobian import StateDependencies
from app.simulation.native_codegen.schedule import EvaluationSchedule
from app.simulation.native_codegen import build as builder
OUT=ROOT/'test/local-probe-20260917'
TEMPLATE=Path(__file__).with_name('local_probe_support.c')
KERNELS=[('properties','native_medium_gas'),('properties','native_temperature_ph'),
('properties','native_density'),('properties','native_viscosity'),('properties','state_valve'),
('properties','property_pt'),('properties','local_isentropic'),
('pipe','native_pipe_resistance'),('pipe','native_pipe_flow_context'),
('pipe','native_pipe_diagnostics_context'),('orifice','native_medium_orifice_context')]
def write(path,value):path.write_text(json.dumps(value,ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
def replace(s,old,new):
assert s.count(old)==1,(old,s.count(old));return s.replace(old,new)
def function_span(s,name):
m=re.search(r'(?m)^(?:static\s+|NATIVE_COMPONENT_INTERNAL\s+)?(?:int|double|void|NativePropertyState\s*\*)\s*'+re.escape(name)+r'\([^;{}]*\)\s*\{',s)
assert m,name
# Generated/native reviewed functions have no braces in string literals in
# the functions selected below. Mask comments and strings for brace matching.
masked=re.sub(r'/\*[\s\S]*?\*/|//[^\n]*|"(?:\\.|[^"\\])*"',lambda x:' '*len(x[0]),s)
b=masked.index('{',m.start());depth=1;e=b+1
while depth:depth+=(masked[e]=='{')-(masked[e]=='}');e+=1
return m.start(),b,e
def dependency_masks(d):
keys=set(d.seeds)|set(d.inputs)|set().union(*d.inputs.values())
unknown=keys-set(d.seeds)-set(d.inputs);full=(1<<d.state_count)-1
masks={k:full if k in unknown else d.seeds.get(k,0) for k in keys}
for _ in range(len(keys)+1):
changed=False
for k,inputs in d.inputs.items():
value=masks[k]
for ref in inputs:value|=masks[ref]
if value!=masks[k]:masks[k]=value;changed=True
if not changed:return masks,unknown
raise AssertionError('dependency closure did not converge')
def capture(model):
saved={};old_build=StateDependencies.build;old_emit=EvaluationSchedule.emit
def dep(self):saved['dependencies']=self;return old_build(self)
def emit(self):
saved['schedule']=self;result=old_emit(self);saved['schedule_lines']=result[1];return result
StateDependencies.build=dep;EvaluationSchedule.emit=emit
try:
_,doc=load_input(model);network=compile_system_xml_network(doc);program=compile_native_program(network)
finally:StateDependencies.build=old_build;EvaluationSchedule.emit=old_emit
return program,saved
def generate(program,saved):
schedule=saved['schedule'];d=saved['dependencies'];structure=d.build()
if not structure.enabled or not program.manifest()['jacobianStructure']['reuse']['enabled']:
raise ValueError('Experiment requires eligible canonical Jacobian reuse')
if any(b.cyclic for b in schedule.blocks):
raise ValueError('Experimental local schedule rejects SCCs; use the unmodified worker')
if structure.color_count>63:raise ValueError('Experimental group mask supports at most 63 groups')
order=[b.members[0] for b in schedule.blocks];ops=[schedule.computations[i] for i in order]
masks,unknown=dependency_masks(d);full=(1<<d.state_count)-1
opmask=[0]*len(ops)
for i,op in enumerate(ops):
for ref in op.inputs:opmask[i]|=masks.get(ref,full)
if any(not re.fullmatch(r'(?:p|h|q|w|fb)\[\d+\]',v) for v in op.outputs):
raise ValueError('Unsupported local operation outputs')
regions=[];plans=[];groups=[]
for color in range(structure.color_count):
states=[j for j,c in enumerate(structure.colors) if c==color]
mask=sum(1<<j for j in states);affected=[bool(v&mask) for v in opmask]
plan=[-1]*len(ops);i=0
while i<len(ops):
if affected[i]:i+=1;continue
start=i
while i<len(ops) and not affected[i]:i+=1
region=(start,i)
if region not in regions:regions.append(region)
plan[start]=regions.index(region)
plans.append(plan)
groups.append(dict(color=color,stateIndices=states,states=[program.state_keys[i] for i in states],
affectedOperations=[order[i] for i,v in enumerate(affected) if v],
regions=[list(regions[x]) for x in plan if x>=0]))
boundaries=sorted({x for pair in regions for x in pair})
# Pure assignments cannot mutate either context. Share their checkpoint
# slot, retaining a new checkpoint after every unreviewed/native call.
pure_calls={'if','for','sizeof','fmax','fmin','fabs','sqrt','copysign','pow'}
versions=[0]
for op in ops:
calls=set(re.findall(r'\b([A-Za-z_]\w*)\s*\(', '\n'.join(op.code)))
versions.append(versions[-1]+bool(calls-pure_calls))
checkpoints=sorted({versions[x] for x in boundaries})
boundary={x:checkpoints.index(versions[x]) for x in boundaries}
source=program.source
dims={name:int(re.search(pattern,source)[1]) for name,pattern in {
'NP':r'double p\[(\d+)\]', 'NQ':r'h\[(\d+)\]', 'NFB':r'double fb\[(\d+)\]',
'NPC':r'NativePipeCache pipe_cache\[(\d+)\]', 'NPS':r'NativePropertyState property_states\[(\d+)\]'}.items()}
outputs=[];offsets=[0]
arraynames=['p','h','q','w','fb']
for op in ops:
for target in op.outputs:
a,n=re.fullmatch(r'(p|h|q|w|fb)\[(\d+)\]',target).groups();outputs.append((arraynames.index(a),int(n)))
offsets.append(len(outputs))
macros={'NC':structure.color_count,'NO':len(ops),'NR':len(regions),'NB':len(checkpoints),'NK':len(KERNELS),**dims}
header='''#ifndef LOCAL_PROBE_EXPERIMENT_H
#define LOCAL_PROBE_EXPERIMENT_H
#include "model.h"
#include <stdint.h>
'''+''.join(f'#define LP_{k} {v}\n' for k,v in macros.items())+'''
extern int lp_color,lp_active,lp_capture,lp_observe;
extern uint64_t lp_mask;
extern const int lp_plan[LP_NC][LP_NO],lp_end[LP_NR];
void lp_start(void);void lp_finish(void);void lp_begin(double,const double*);void lp_ready(int);
void lp_snapshot(int,NativePropertyCache*,NativePipeCache*);
void lp_save(double*,double*,double*,double*,double*);
int lp_reuse(int,NativePropertyCache*,NativePipeCache*,double*,double*,double*,double*,double*);
#if LP_OBSERVE
void lp_operation(int);
#else
#define lp_operation(position) ((void)(position))
#endif
void lp_kernel(int);int lp_valid(double,const double*);
void lp_note_eval(int);
void lp_compare(int,const double*,const double*,int,const double*,const double*);
void lp_matrix(double,const double*,const double*);void lp_event(double,const double*);
uint64_t lp_tick(void);void lp_jac_time(uint64_t);void lp_newton(long,long);
int lp_eval(double,const double*,double*,double*,ModelJacobianWorkspace*);
#endif
'''
declarations='const int lp_end[LP_NR]={'+','.join(str(b) for a,b in regions)+'};\n'
declarations+='const int lp_plan[LP_NC][LP_NO]={'+','.join('{'+','.join(map(str,p))+'}' for p in plans)+'};\n'
declarations+='static const int lp_before[LP_NR]={'+','.join(str(boundary[a]) for a,b in regions)+'};\n'
declarations+='static const int lp_after[LP_NR]={'+','.join(str(boundary[b]) for a,b in regions)+'};\n'
declarations+='static const int lp_first_output[LP_NR]={'+','.join(str(offsets[a]) for a,b in regions)+'};\n'
declarations+='static const int lp_last_output[LP_NR]={'+','.join(str(offsets[b]) for a,b in regions)+'};\n'
declarations+='static const int lp_start_op[LP_NR]={'+','.join(str(a) for a,b in regions)+'};\n'
declarations+='static const int lp_output_map[][2]={'+','.join('{'+str(a)+','+str(i)+'}' for a,i in outputs)+'};\n'
declarations+='static const int lp_operation_id[LP_NO]={'+','.join(map(str,order))+'};\n'
declarations+='static const int lp_colors[NSTATES]={'+','.join(map(str,structure.colors))+'};\n'
declarations+='static const char *lp_kernel_names[LP_NK]={'+','.join(json.dumps(f) for _,f in KERNELS)+'};\n'
# Keep the original entire model evaluator byte-for-byte. Add an opt-in clone
# whose only numerical statement change is replacement of the schedule.
a,b,e=function_span(source,'model_eval_internal');original=source[a:e]
schedule_text='\n'.join(saved['schedule_lines']);assert original.count(schedule_text)==1
capture_lines=[]
for pos,op in enumerate(ops):
if pos in boundary:capture_lines.append(f'lp_snapshot({boundary[pos]},properties,pipe_cache);')
capture_lines.extend(op.code)
if len(ops) in boundary:capture_lines.append(f'lp_snapshot({boundary[len(ops)]},properties,pipe_cache);')
capture_lines.append('lp_save(p,h,q,w,fb);')
local=['if(lp_capture){',*capture_lines,'}else{','for(int pos=0;pos<LP_NO;){',
'int region=lp_plan[lp_color][pos];',
'if(region>=0 && lp_reuse(region,properties,pipe_cache,p,h,q,w,fb)){pos=lp_end[region];continue;}',
'lp_operation(pos);','switch(pos){']
for pos,op in enumerate(ops):local += [f'case {pos}:{{',*op.code,'break;}']
local += ['default:return 0;}','pos++;','}}']
clone=original.replace('model_eval_internal(', 'model_eval_local_internal(',1).replace(schedule_text,'\n'.join(local))
wrapper='''
int lp_eval(double t,const double *y,double *dy,double *w,ModelJacobianWorkspace *workspace){
static ModelJacobianWorkspace shadow;
int active=lp_color+1;lp_active=active;
int eligible=lp_mask && workspace && (lp_color<0 || (lp_mask&(UINT64_C(1)<<lp_color)));
if(lp_color<0){lp_begin(t,y);if(lp_observe)model_jacobian_begin(&shadow);}
int valid=lp_color<0 || lp_valid(t,y);
lp_capture=lp_color<0;
lp_note_eval(eligible && valid);
int result=eligible && valid ? model_eval_local_internal(t,y,dy,w,1,NULL,NULL,workspace) : model_eval_jacobian_reuse(t,y,dy,w,workspace);
if(eligible && valid && workspace)workspace->scalars.recording=0;
if(lp_color<0)lp_ready(eligible && result);
if(lp_observe){
double expected_dy[NSTATES],expected_w[NOUTPUTS];lp_active=-1;
int expected=model_eval_jacobian_reuse(t,y,expected_dy,expected_w,&shadow);
lp_compare(result,dy,w,expected,expected_dy,expected_w);
}
lp_active=-1;return result;
}
'''
local_source=source+'\n'+clone+'\n'+wrapper
# The reference original is never replaced in the timing worker. Observation
# counts reference operations analytically in support code, not by modifying it.
assert original in local_source
metadata=dict(stateKeys=program.state_keys,groups=groups,order=order,regions=regions,boundaries=boundaries,contextCheckpointSlots=boundary,
operations=[dict(id=order[i],key=op.key,inputs=sorted(op.inputs),outputs=list(op.outputs),stateIndices=[j for j in range(d.state_count) if opmask[i]>>j&1]) for i,op in enumerate(ops)],
unknownDependencyLeaves=sorted(unknown),macros=macros,originalEvaluatorSha256=hashlib.sha256(original.encode()).hexdigest())
return '#include "local_probe.h"\n'+local_source,header,declarations,metadata
def kernel_counter(s,fn,index):
a,b,e=function_span(s,fn)
return s[:b+1]+f'if(lp_active>=0)lp_kernel({index});'+s[b+1:]
def prepare(model,observe=False):
OUT.mkdir(parents=True,exist_ok=True);started=time.perf_counter();program,saved=capture(model)
local,header,tables,meta=generate(program,saved)
write(OUT/'plan.json',meta);(OUT/'original-model.c').write_text(program.source,encoding='utf-8')
work=OUT/('audit' if observe else 'worker');work.mkdir(exist_ok=True)
(work/'model.h').write_text(program.header,encoding='utf-8');(work/'local_probe.h').write_text(header,encoding='utf-8')
sources={p.relative_to(builder.NATIVE).as_posix():p.read_text(encoding='utf-8') for p in builder._runtime_sources(program)}
source_hashes={key:hashlib.sha256(value.encode()).hexdigest() for key,value in sources.items()}
sources['model.c']=local
common=sources['runtime/common.c']
common=replace(common,'int ok=r->options.bdf ? native_bdf(r) : native_rk45(r);','lp_start();int ok=r->options.bdf ? native_bdf(r) : native_rk45(r);')
common=replace(common,'r->solve_cpu_seconds=native_cpu_time()-r->cpu_start;','r->solve_cpu_seconds=native_cpu_time()-r->cpu_start;lp_finish();')
common=replace(common,'r->events++;','lp_event(stop,accepted_state);r->events++;')
sources['runtime/common.c']=common
cv=sources['runtime/cvode_solver.c']
cv=replace(cv,'model_eval_jacobian_reuse(t,N_VGetArrayPointer(y),N_VGetArrayPointer(f),outputs,workspace)',
'lp_eval(t,N_VGetArrayPointer(y),N_VGetArrayPointer(f),outputs,workspace)')
cv=replace(cv,'for (int color=0;color<MODEL_JACOBIAN_COLOR_COUNT;color++) {\n memcpy(test,state,NSTATES*sizeof(double));',
'for (int color=0;color<MODEL_JACOBIAN_COLOR_COUNT;color++) {\n lp_color=color;memcpy(test,state,NSTATES*sizeof(double));')
a,b,e=function_span(cv,'cv_jacobian');sig=cv[a:b].strip()
impl=cv[a:e].replace('cv_jacobian(', 'cv_jacobian_original(',1)
wrapper=sig+'{lp_color=-1;uint64_t start=lp_tick();int result=cv_jacobian_original(t,y,fy,matrix,user,tmp1,tmp2,tmp3);lp_jac_time(start);if(!result)lp_matrix(t,N_VGetArrayPointer(y),SUNDenseMatrix_Data(matrix));return result;}'
cv=cv[:a]+impl+'\n'+wrapper+cv[e:]
cv=replace(cv,'long int value=0;', 'long int value=0,nni=0,ncf=0;CVodeGetNumNonlinSolvIters(solver,&nni);CVodeGetNumNonlinSolvConvFails(solver,&ncf);lp_newton(nni,ncf);')
sources['runtime/cvode_solver.c']=cv
if observe:
for i,(module,fn) in enumerate(KERNELS):
key=f'components/modules/{module}.c';sources[key]=kernel_counter(sources[key],fn,i)
sources={key:'#include "local_probe.h"\n'+src for key,src in sources.items()}
sources['local_probe_support.c']=TEMPLATE.read_text(encoding='utf-8').replace('/* GENERATED_TABLES */',tables)
cc,sun,_=builder.toolchain();flags,libs,dlls,exe=builder.platform_build_inputs(sun);flags += [f'-DLP_OBSERVE={int(observe)}']
generated=time.perf_counter()
def compile_one(item):
i,(key,source)=item;path=work/Path(key).name;path.write_text(source,encoding='utf-8',newline='\n');obj=work/f'unit-{i}.o';log=[]
builder._command([cc,*flags,'-I',str(work),'-I',str(builder.NATIVE/'include'),'-I',str(sun/'include'),'-c',str(path),'-o',str(obj)],log=log,timeout=180)
return obj,log
with ThreadPoolExecutor(max_workers=4) as pool:objects=list(pool.map(compile_one,enumerate(sources.items())))
log=[];builder._command([cc,*flags,*[str(x) for x,_ in objects],*builder.link_library_arguments(libs),'-lm','-o',str(work/exe)],log=log)
for dll in dlls:shutil.copyfile(dll,work/dll.name)
(work/'build.log').write_text('\n'.join([*sum([x for _,x in objects],[]),*log]),encoding='utf-8')
write(work/'build-metadata.json',dict(commit=subprocess.check_output(['git','rev-parse','HEAD'],cwd=ROOT,text=True).strip(),
inputSha256=hashlib.sha256(model.read_bytes()).hexdigest(),sourceHashes=source_hashes,
codegenSeconds=generated-started,compileSeconds=time.perf_counter()-generated,totalSeconds=time.perf_counter()-started))
print('BUILT',work.name,'seconds',time.perf_counter()-started,'regions',len(meta['regions']),'boundaries',len(meta['boundaries']),flush=True)
def run(label,mask,observe=False,stop=10):
work=OUT/label;work.mkdir(exist_ok=True);exe=OUT/('audit' if observe else 'worker')/'model.exe'
env=os.environ.copy();env['LOCAL_PROBE_MASK']=str(mask)
args=[str(exe),'--method','BDF','--start','0','--stop',str(stop),'--sample-step','.01','--max-step','1e30','--rtol','1e-8','--timeout','300',
'--sample-file',str(work/'states.bin'),'--output-block-file',str(work/'outputs.bin'),'--output',str(work/'result.json')]
start=time.perf_counter()
with (work/'stderr.log').open('wb') as f:p=subprocess.run(args,cwd=work,env=env,stdout=subprocess.PIPE,stderr=f,timeout=330,creationflags=subprocess.CREATE_NO_WINDOW)
elapsed=time.perf_counter()-start
if p.returncode:raise RuntimeError((label,p.returncode,(work/'stderr.log').read_text(encoding='utf-8')[-5000:]))
r=json.loads((work/'result.json').read_text(encoding='utf-8'));diag=json.loads((work/'probe.json').read_text())
record={k:r[k] for k in ('success','finalState','final','propertyWarnings','acceptedSteps','rejectedSteps','stateTransitions','solverStarts','nfev','njev','nlu','solveSeconds','solveCpuSeconds')}
for name in ('states','outputs','events','jacobians'):
path=work/f'{name}.bin'
if path.exists():
with path.open('rb') as f:record[name+'Sha256']=hashlib.file_digest(f,'sha256').hexdigest()
record[name+'Bytes']=path.stat().st_size
record.update(processSeconds=elapsed,mask=mask,diagnostic=diag)
write(work/'measurement.json',record)
print('RUN',label,{k:record[k] for k in ('solveSeconds','solveCpuSeconds','processSeconds','acceptedSteps','nfev','njev','nlu')},'newton',diag['newtonIterations'],'audit',diag['auditComparisons'],diag['auditDifferences'],flush=True)
def cold_runs(model):
for name,mask in [('reference',0),('all',0x7ffffff)]:
start=time.perf_counter();prepare(model,False);prepared=time.perf_counter()-start
run('cold-'+name,mask);elapsed=time.perf_counter()-start
record=json.loads((OUT/('cold-'+name)/'measurement.json').read_text(encoding='utf-8'))
write(OUT/('cold-'+name+'-pipeline.json'),dict(mode=name,prepareSeconds=prepared,
workerSeconds=record['processSeconds'],pipelineSeconds=elapsed))
def guard_test():
work=OUT/'audit';cc,sun,_=builder.toolchain();flags,_,_,_=builder.platform_build_inputs(sun);log=[]
executable=work/'guard-test.exe'
builder._command([cc,*flags,'-DLP_OBSERVE=1','-I',str(work),'-I',str(builder.NATIVE/'include'),
str(Path(__file__).with_name('local_probe_guard_test.c')),'-lm','-o',str(executable)],log=log)
target=OUT/'guard-selftest';target.mkdir(exist_ok=True);env=os.environ.copy();env['LOCAL_PROBE_MASK']=str(1<<26)
subprocess.run([str(executable)],cwd=target,env=env,check=True,creationflags=subprocess.CREATE_NO_WINDOW)
if __name__=='__main__':
p=argparse.ArgumentParser();p.add_argument('action',choices=['prepare','run','cold','guard']);p.add_argument('--audit',action='store_true')
p.add_argument('--model',type=Path,default=ROOT/'tests/data/test-mql-8-corrected.json');p.add_argument('--label',default='run');p.add_argument('--mask',type=lambda v:int(v,0),default=0);p.add_argument('--stop',type=float,default=10)
a=p.parse_args()
if a.action=='prepare':prepare(a.model,a.audit)
elif a.action=='cold':cold_runs(a.model)
elif a.action=='guard':guard_test()
else:run(a.label,a.mask,a.audit,a.stop)
+39
View File
@@ -0,0 +1,39 @@
/* Compile against an experiment's generated model/support, not production. */
#include <local_probe_support.c>
#define CHECK(x) do{if(!(x)){fprintf(stderr,"guard self-test failed line %d\n",__LINE__);return 1;}}while(0)
int main(void){
lp_start();CHECK(saved && lp_mask);
int group=-1,region=-1;
for(int g=0;g<LP_NC && group<0;g++)if(lp_mask&(UINT64_C(1)<<g))for(int p=0;p<LP_NO;p++)if(lp_plan[g][p]>=0){group=g;region=lp_plan[g][p];break;}
CHECK(group>=0);lp_color=group;lp_active=group+1;
double y[NSTATES]={0},p[LP_NP]={0},h[LP_NQ]={0},q[LP_NQ]={0},w[NOUTPUTS]={0},fb[LP_NFB]={0};
NativePropertyState states[LP_NPS]={0};
NativePipeCache pipes[LP_NPC]={0};NativeJacobianScalars jacobian={0};
NativePropertyCache context={.states=states,.count=1,.capacity=LP_NPS,.jacobian=&jacobian};
states[0].p=123;states[0].T=456;states[0].valid=NATIVE_PROPERTY_PT;states[0].jacobian=&jacobian;
lp_begin(.5,y);lp_snapshot(lp_before[region],&context,pipes);
states[0].rho=42;states[0].valid|=NATIVE_PROPERTY_RHO;
lp_snapshot(lp_after[region],&context,pipes);
lp_save(p,h,q,w,fb);lp_ready(1);
CHECK(lp_valid(.5,y));CHECK(!lp_valid(nextafter(.5,1),y));
int own=-1,other=-1;
for(int i=0;i<NSTATES;i++){if(lp_colors[i]==group)own=i;else other=i;}
CHECK(own>=0 && other>=0);y[own]=1;CHECK(lp_valid(.5,y));y[other]=1;CHECK(!lp_valid(.5,y));y[other]=0;
Snapshot *before=&saved->snapshots[lp_before[region]],*after=&saved->snapshots[lp_after[region]];
context.count=before->count;memcpy(states,before->states,before->count*sizeof(*states));memcpy(pipes,before->pipes,sizeof(pipes));
CHECK(lp_reuse(region,&context,pipes,p,h,q,w,fb));
CHECK(context.count==after->count && !memcmp(states,after->states,after->count*sizeof(*states)));
memcpy(states,before->states,before->count*sizeof(*states));context.count=before->count;
states[0].rho=nextafter(states[0].rho,1);CHECK(!lp_reuse(region,&context,pipes,p,h,q,w,fb));
memcpy(states,before->states,before->count*sizeof(*states));pipes[0].valid=1;CHECK(!lp_reuse(region,&context,pipes,p,h,q,w,fb));
memcpy(pipes,before->pipes,sizeof(pipes));context.count++;CHECK(!lp_reuse(region,&context,pipes,p,h,q,w,fb));
context.count=before->count;context.jacobian=NULL;CHECK(!lp_reuse(region,&context,pipes,p,h,q,w,fb));
int pure=-1;for(int r=0;r<LP_NR;r++)if(lp_before[r]==lp_after[r]){pure=r;break;}
CHECK(pure>=0);NativePropertyState held=states[0];NativePipeCache held_pipe=pipes[0];
size_t held_count=context.count;
CHECK(lp_reuse(pure,&context,pipes,p,h,q,w,fb));
CHECK(context.count==held_count && context.jacobian==NULL);
CHECK(!memcmp(&held,states,sizeof(held)) && !memcmp(&held_pipe,pipes,sizeof(held_pipe)));
lp_begin(.5,y);CHECK(!lp_valid(.5,y));
lp_finish();puts("exact context, pipe-cache, time, state and lifetime guards passed");return 0;
}
+86
View File
@@ -0,0 +1,86 @@
/* No numerical work lives here. Runtime selection is outside the hot model. */
#include "local_probe_profile.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* PROFILE_TABLES */
PfBucket pf_buckets[LP_NC+2][PF_CATEGORIES],*pf_current;
uint64_t pf_previous_tick;
unsigned pf_row;
int pf_coarse;
static uint64_t evaluations[LP_NC+2];
uint64_t pf_operations[LP_NC][2];
static uint64_t calls,sampled,sampled_ticks,frequency,total_events;
static uint64_t sampled_qpc_ticks,tsc_start,tsc_end,clock_t0;
static double clock_q0;
static unsigned stride=16,offset,seed=1;
static FILE *matrix;
static const char *names[PF_CATEGORIES]={"other","perturbation_amount","state_copy_perturb",
"matrix_zero","difference_matrix_write","baseline_compute","initialization","gas_state_preparation",
"schedule_retained","schedule_context_fallback","context_compare","snapshot_capture_save",
"context_output_restore","node_energy","port_outputs","mechanical_equations",
"gas_mass_energy","remaining_outputs","pipe_diagnostics","finite_check","schedule_dispatch","whole_probe_fallback"};
void pf_initialize(void){
LARGE_INTEGER f;QueryPerformanceFrequency(&f);frequency=(uint64_t)f.QuadPart;
unsigned a,b,c,d;
if(!__get_cpuid(0x80000007,&a,&b,&c,&d) || !(d&(1u<<8))){fprintf(stderr,"invariant TSC required for diagnostic clock\n");abort();}
uint64_t q0=lp_tick();clock_t0=pf_clock();uint64_t q1=lp_tick();clock_q0=((double)q0+(double)q1)/2;
const char *s=getenv("PROBE_PROFILE_STRIDE"),*o=getenv("PROBE_PROFILE_OFFSET"),*v=getenv("PROBE_PROFILE_MATRICES");
const char *coarse=getenv("PROBE_PROFILE_COARSE");pf_coarse=coarse?atoi(coarse):0;
if(s)stride=(unsigned)strtoul(s,NULL,0);
if(o)offset=(unsigned)strtoul(o,NULL,0);
seed=offset+1;
if(v && atoi(v)){matrix=fopen("jacobians.bin","wb");if(!matrix)abort();setvbuf(matrix,NULL,_IOFBF,1024*1024);}
}
int pf_select(void){
/* One randomly positioned callback per consecutive stratum. The offset is
reproducible, and avoids locking onto a recurring Newton/event pattern. */
unsigned pos=(unsigned)(calls++% (stride?stride:1));
if(!stride)return 0;
if(!pos){seed^=seed<<13;seed^=seed>>17;seed^=seed<<5;offset=seed%stride;}
if(pos!=offset)return 0;
pf_row=0;return 1;
}
void pf_eval_count(void){evaluations[pf_row]++;}
void pf_open(void){pf_current=&pf_buckets[0][PF_OTHER];tsc_start=pf_clock();pf_previous_tick=tsc_start;}
void pf_close(void){tsc_end=pf_clock();pf_current->ticks+=tsc_end-pf_previous_tick;}
void pf_flush(uint64_t start,uint64_t end){
sampled++;sampled_qpc_ticks+=end-start;sampled_ticks+=tsc_end-tsc_start;
}
void pf_validate_matrix(double t,const double *y,const double *m){
if(matrix){fwrite(&t,8,1,matrix);fwrite(y,8,NSTATES,matrix);fwrite(m,8,NSTATES*NSTATES,matrix);}
}
static void array(FILE *f,const uint64_t *a,int n){fputc('[',f);for(int i=0;i<n;i++)fprintf(f,"%s%llu",i?",":"",(unsigned long long)a[i]);fputc(']',f);}
void pf_finish(void){
if(matrix){fclose(matrix);matrix=NULL;}
uint64_t q0=lp_tick(),t1=pf_clock(),q1=lp_tick();
double tsc_frequency=(double)(t1-clock_t0)/((((double)q0+(double)q1)/2-clock_q0)/frequency);
/* Calibration after integration. Same fenced clock and bucket accounting;
no correction is applied to raw ticks. This cannot remove serialization,
compiler-layout, cache, or other workload-dependent indirect effects. */
double calibration[9];PfBucket saved_buckets[LP_NC+2][PF_CATEGORIES];
memcpy(saved_buckets,pf_buckets,sizeof(saved_buckets));
for(int k=0;k<9;k++){
pf_row=0;pf_current=&pf_buckets[0][PF_OTHER];uint64_t begin=pf_clock();pf_previous_tick=begin;
for(int i=0;i<24576;i++)PF_MARK(i&1?PF_OTHER:PF_DISPATCH);
calibration[k]=(double)(pf_clock()-begin)/24576;
}
memcpy(pf_buckets,saved_buckets,sizeof(saved_buckets));
for(int r=0;r<LP_NC+2;r++)for(int c=0;c<PF_CATEGORIES;c++)total_events+=pf_buckets[r][c].intervals;
FILE *f=fopen("profile.json","wb");if(!f)abort();
fprintf(f,"{\"frequency\":%.9f,\"qpcFrequency\":%llu,\"sampledCallbackQpcTicks\":%llu,\"stride\":%u,\"callbacks\":%llu,\"sampledCallbacks\":%llu,\"sampledCallbackTicks\":%llu,\"markerCount\":%llu,\"calibrationTicksPerMarker\":[",
tsc_frequency,(unsigned long long)frequency,(unsigned long long)sampled_qpc_ticks,stride,(unsigned long long)calls,(unsigned long long)sampled,(unsigned long long)sampled_ticks,(unsigned long long)total_events);
for(int i=0;i<9;i++)fprintf(f,"%s%.9f",i?",":"",calibration[i]);
fprintf(f,"],\"categories\":[");for(int i=0;i<PF_CATEGORIES;i++)fprintf(f,"%s\"%s\"",i?",":"",names[i]);
fprintf(f,"],\"rows\":[");uint64_t sum=0;
for(int r=0;r<LP_NC+2;r++){
uint64_t rt[PF_CATEGORIES],ri[PF_CATEGORIES];
for(int c=0;c<PF_CATEGORIES;c++){rt[c]=pf_buckets[r][c].ticks;ri[c]=pf_buckets[r][c].intervals;}
fprintf(f,"%s{\"group\":%d,\"evaluations\":%llu,\"ticks\":",r?",":"",r-2,(unsigned long long)evaluations[r]);array(f,rt,PF_CATEGORIES);
fprintf(f,",\"intervals\":");array(f,ri,PF_CATEGORIES);
if(r>=2){fprintf(f,",\"operations\":");array(f,pf_operations[r-2],2);}fputc('}',f);
for(int c=0;c<PF_CATEGORIES;c++)sum+=rt[c];
}
fprintf(f,"],\"ledgerSumTicks\":%llu}\n",(unsigned long long)sum);fclose(f);
if(sum!=sampled_ticks)abort();
}
+53
View File
@@ -0,0 +1,53 @@
/* Diagnostic-only exclusive buckets; included only by the copied worker. */
#ifndef LOCAL_PROBE_PROFILE_H
#define LOCAL_PROBE_PROFILE_H
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0600
#endif
#include <windows.h>
#include <stdint.h>
#include <cpuid.h>
#include "local_probe.h"
enum { PF_OTHER, PF_INCREMENT, PF_PERTURB, PF_ZERO, PF_ASSEMBLY,
PF_BASELINE, PF_INIT, PF_GAS_PREP, PF_RETAINED, PF_FALLBACK,
PF_CONTEXT, PF_SNAPSHOT, PF_RESTORE, PF_NODE, PF_PORT,
PF_MECHANICAL, PF_GAS_EQUATIONS, PF_OUTPUTS, PF_PIPE,
PF_FINITE, PF_DISPATCH, PF_FULL_FALLBACK, PF_CATEGORIES };
/* row 0 = outer callback, row 1 = baseline, row 2+g = probe group g. */
typedef struct { uint64_t ticks,intervals; } PfBucket;
extern PfBucket pf_buckets[LP_NC+2][PF_CATEGORIES],*pf_current;
extern uint64_t pf_previous_tick;
extern unsigned pf_row;
extern int pf_coarse;
extern const unsigned char pf_affected[LP_NC][LP_NO];
extern uint64_t pf_operations[LP_NC][2];
static inline uint64_t pf_clock(void){
unsigned lo,hi;
__asm__ __volatile__("lfence\n\trdtsc\n\tlfence" : "=a"(lo),"=d"(hi) :: "memory");
return ((uint64_t)hi<<32)|lo;
}
static inline void pf_mark(unsigned category) {
uint64_t t=pf_clock();
pf_current->ticks+=t-pf_previous_tick;
pf_current->intervals++;
pf_previous_tick=t;pf_current=&pf_buckets[pf_row][category];
}
#define PF_MARK(category) pf_mark(category)
#define PF_SCOPE(row,category) do {pf_row=(row);pf_mark(category);} while(0)
#define PF_PROBE(category) do {if(lp_color>=0)pf_mark(category);} while(0)
void pf_initialize(void);void pf_finish(void);
int pf_select(void);void pf_flush(uint64_t,uint64_t);
void pf_eval_count(void);
static inline void pf_operation(int position){
int retained=pf_affected[lp_color][position];
pf_operations[lp_color][retained?0:1]++;
PF_MARK(retained?PF_RETAINED:PF_FALLBACK);
}
void pf_open(void);void pf_close(void);
void pf_snapshot(int,NativePropertyCache*,NativePipeCache*);
void pf_save(double*,double*,double*,double*,double*);
int pf_reuse(int,NativePropertyCache*,NativePipeCache*,double*,double*,double*,double*,double*);
int pf_lp_eval(double,const double*,double*,double*,ModelJacobianWorkspace*);
void lp_jac_time_end(uint64_t,uint64_t);
void pf_validate_matrix(double,const double*,const double*);
#endif
+34
View File
@@ -0,0 +1,34 @@
**局部 probe 的独立诊断计时工具**
本工具只读取上一轮 `test/local-probe-20260917/worker`,在
`test/local-probe-profile-direct-20260917` 构建带计时副本的独立 worker。
不编辑旧实验实现或生产文件,不改变求解容差、物性算法或执行范围。
仅适用于本次已核验的132状态、27组、484个schedule操作的八路模型。
```powershell
& .venv-win/Scripts/python.exe -B tests/manual/profile_local_probe.py prepare
& .venv-win/Scripts/python.exe -B tests/manual/profile_local_probe.py run --label validate-all --stride 1 --matrices
& .venv-win/Scripts/python.exe -B tests/manual/profile_local_probe.py batch
& .venv-win/Scripts/python.exe -B tests/manual/profile_local_probe.py prepare --coarse
& .venv-win/Scripts/python.exe -B tests/manual/profile_local_probe.py run --label coarse-0 --stride 1 --coarse
& .venv-win/Scripts/python.exe -B tests/manual/profile_local_probe.py run --label coarse-1 --stride 1 --coarse
& .venv-win/Scripts/python.exe -B tests/manual/profile_local_probe.py run --label coarse-2 --stride 1 --coarse
& .venv-win/Scripts/python.exe -B tests/manual/analyze_local_probe_profile.py
```
相同label会覆盖同名诊断运行。请串行测量,期间不要并行编译或运行其他仿真。
完整数值核验保留所有矩阵;主性能测量不保存矩阵、不做shadow求值。
每个run都会比较states/outputs/events、warning、求解器计数以及context保护计数。
正常callback走未改写的原函数;分层随机抽中的callback才进入细分计时副本。
粗粒度worker进入原模型函数,只测完整baseline/probe及Jacobian外层。
分类定义、全部27组均值、原始/扣空标记值和测量误差见生成的report.md;
每组全部分类数据见comparison.json的main.rows[group+2]。
外层时钟为QPC,内部使用带lfence的invariant TSC,以整轮QPC/TSC读数校准频率。
内部计数桶逐tick闭合;内外计时边界差单独归入outer other。
扣空标记只是估计,不能去除计时屏障、编译布局与缓存造成的间接扰动。
本次估计总量仍高于未插桩约7%,小group的偏差更大;不得把细分百分比
直接用作优化收益承诺。原QPC记录版及TSC记录版分别保存在
`test/local-probe-profile-20260917`、`test/local-probe-profile-tsc-20260917`,
用于保留计时方法改进的证据,不与最终主样本混用。
+133
View File
@@ -0,0 +1,133 @@
/* Standalone experiment support; never linked by the production builder. */
#define _WIN32_WINNT 0x0600
#include "local_probe.h"
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
/* GENERATED_TABLES */
int lp_color=-1,lp_active=-1,lp_capture=0,lp_observe=LP_OBSERVE;
uint64_t lp_mask;
typedef struct {
size_t count,capacity;
NativePropertyTemperatures *temperatures;
NativeJacobianScalars *jacobian;
NativePropertyState states[LP_NPS];
NativePipeCache pipes[LP_NPC];
} Snapshot;
typedef struct {
Snapshot snapshots[LP_NB];
double p[LP_NP],h[LP_NQ],q[LP_NQ],w[NOUTPUTS],fb[LP_NFB];
double t,y[NSTATES];int ready;
} Workspace;
static Workspace *saved;
static unsigned char needed[LP_NB],captured[LP_NB];
static FILE *matrix_file,*event_file;
static uint64_t frequency,jac_ticks,jac_calls,newton_iterations,newton_failures,audit_count,audit_diff;
static uint64_t executed[LP_NC][LP_NO],skipped[LP_NC][LP_NO],kernel_calls[LP_NC+1][LP_NK];
static uint64_t attempts[LP_NC],hits[LP_NC],context_misses[LP_NC],invalid_trials[LP_NC];
static uint64_t compared_bytes,copied_bytes,model_calls[LP_NC+1];
uint64_t lp_tick(void){LARGE_INTEGER t;QueryPerformanceCounter(&t);return (uint64_t)t.QuadPart;}
void lp_jac_time(uint64_t start){jac_ticks+=lp_tick()-start;jac_calls++;}
void lp_newton(long iterations,long failures){newton_iterations+=(uint64_t)iterations;newton_failures+=(uint64_t)failures;}
void lp_start(void){
LARGE_INTEGER f;QueryPerformanceFrequency(&f);frequency=(uint64_t)f.QuadPart;
const char *mask=getenv("LOCAL_PROBE_MASK");lp_mask=mask?strtoull(mask,NULL,0):0;
if(lp_mask){saved=calloc(1,sizeof(*saved));if(!saved){fprintf(stderr,"local probe workspace allocation failed\n");abort();}}
for(int g=0;g<LP_NC;g++)if(lp_mask&(UINT64_C(1)<<g))for(int pos=0;pos<LP_NO;pos++){
int r=lp_plan[g][pos];if(r>=0){needed[lp_before[r]]=1;needed[lp_after[r]]=1;}
}
event_file=fopen("events.bin","wb");if(!event_file)abort();
if(lp_observe){matrix_file=fopen("jacobians.bin","wb");if(!matrix_file)abort();setvbuf(matrix_file,NULL,_IOFBF,1024*1024);}
}
void lp_begin(double t,const double *y){
if(saved){saved->ready=0;saved->t=t;memcpy(saved->y,y,sizeof(saved->y));memset(captured,0,sizeof(captured));}
}
void lp_ready(int ok){if(saved)saved->ready=ok;}
int lp_valid(double t,const double *y){
if(!saved || !saved->ready || memcmp(&t,&saved->t,sizeof(double)))return 0;
for(int i=0;i<NSTATES;i++)if(lp_colors[i]!=lp_color && memcmp(y+i,saved->y+i,sizeof(double))){invalid_trials[lp_color]++;return 0;}
return 1;
}
void lp_snapshot(int index,NativePropertyCache *properties,NativePipeCache *pipes){
if(!needed[index] || captured[index])return;
captured[index]=1;
Snapshot *s=&saved->snapshots[index];
if(properties->count>LP_NPS || properties->capacity!=LP_NPS || properties->temperatures){fprintf(stderr,"unsupported local property context\n");abort();}
s->count=properties->count;s->capacity=properties->capacity;s->temperatures=properties->temperatures;s->jacobian=properties->jacobian;
memcpy(s->states,properties->states,s->count*sizeof(*s->states));memcpy(s->pipes,pipes,sizeof(s->pipes));
copied_bytes+=s->count*sizeof(*s->states)+sizeof(s->pipes);
}
void lp_save(double *p,double *h,double *q,double *w,double *fb){
memcpy(saved->p,p,sizeof(saved->p));memcpy(saved->h,h,sizeof(saved->h));memcpy(saved->q,q,sizeof(saved->q));
memcpy(saved->w,w,sizeof(saved->w));memcpy(saved->fb,fb,sizeof(saved->fb));
}
int lp_reuse(int region,NativePropertyCache *properties,NativePipeCache *pipes,double *p,double *h,double *q,double *w,double *fb){
Snapshot *before=&saved->snapshots[lp_before[region]],*after=&saved->snapshots[lp_after[region]];
attempts[lp_color]++;
int contextual=lp_before[region]!=lp_after[region];
if(contextual && (properties->count!=before->count || properties->capacity!=before->capacity ||
properties->temperatures!=before->temperatures || properties->jacobian!=before->jacobian)){context_misses[lp_color]++;return 0;}
size_t n=before->count*sizeof(*before->states);
if(contextual){compared_bytes+=n+sizeof(before->pipes);
if(memcmp(properties->states,before->states,n) || memcmp(pipes,before->pipes,sizeof(before->pipes))){context_misses[lp_color]++;return 0;}
}
/* Preserve the exact baseline cache mutations, including seeded rho/h and
computed isentropic fields; never synthesize a context from (p,T) alone.
Jacobian scalar entries are immutable after baseline; only their telemetry
counters differ when actual kernel requests are avoided. */
if(contextual){properties->count=after->count;
memcpy(properties->states,after->states,after->count*sizeof(*after->states));memcpy(pipes,after->pipes,sizeof(after->pipes));
copied_bytes+=after->count*sizeof(*after->states)+sizeof(after->pipes);
}
double *dst[]={p,h,q,w,fb};double *src[]={saved->p,saved->h,saved->q,saved->w,saved->fb};
for(int i=lp_first_output[region];i<lp_last_output[region];i++)dst[lp_output_map[i][0]][lp_output_map[i][1]]=src[lp_output_map[i][0]][lp_output_map[i][1]];
hits[lp_color]++;
if(LP_OBSERVE)for(int i=lp_start_op[region];i<lp_end[region];i++)skipped[lp_color][lp_operation_id[i]]++;
return 1;
}
#if LP_OBSERVE
void lp_operation(int position){if(lp_active>0)executed[lp_active-1][lp_operation_id[position]]++;}
#endif
void lp_kernel(int kind){if(lp_active>=0 && lp_active<=LP_NC)kernel_calls[lp_active][kind]++;}
void lp_compare(int ok,const double *dy,const double *w,int expected_ok,const double *expected_dy,const double *expected_w){
audit_count++;
if(ok!=expected_ok || (ok && (memcmp(dy,expected_dy,NSTATES*sizeof(double)) || memcmp(w,expected_w,NOUTPUTS*sizeof(double))))){
audit_diff++;fprintf(stderr,"probe exact mismatch color=%d comparison=%llu\n",lp_color,(unsigned long long)audit_count);
for(int i=0;i<NSTATES;i++)if(memcmp(dy+i,expected_dy+i,8))fprintf(stderr,"dy[%d] %.17g != %.17g\n",i,dy[i],expected_dy[i]);
for(int i=0;i<NOUTPUTS;i++)if(memcmp(w+i,expected_w+i,8))fprintf(stderr,"w[%d] %.17g != %.17g\n",i,w[i],expected_w[i]);
abort();
}
}
void lp_matrix(double t,const double *y,const double *matrix){
if(matrix_file){fwrite(&t,8,1,matrix_file);fwrite(y,8,NSTATES,matrix_file);fwrite(matrix,8,NSTATES*NSTATES,matrix_file);}
}
void lp_event(double t,const double *y){if(event_file){fwrite(&t,8,1,event_file);fwrite(y,8,NSTATES,event_file);}}
void lp_note_eval(int local){
model_calls[lp_color+1]++;
/* Reference/disabled/invalid evaluations run every original operation. */
if(LP_OBSERVE && lp_color>=0 && !local)for(int i=0;i<LP_NO;i++)executed[lp_color][i]++;
}
static void vector(FILE *f,const uint64_t *a,int n){
fputc('[',f);for(int i=0;i<n;i++){fprintf(f,"%s%llu",i?",":"",(unsigned long long)a[i]);}fputc(']',f);
}
void lp_finish(void){
if(matrix_file){fclose(matrix_file);matrix_file=NULL;}if(event_file){fclose(event_file);event_file=NULL;}
FILE *f=fopen("probe.json","wb");if(!f)abort();
fprintf(f,"{\"mask\":%llu,\"workspaceBytes\":%llu,\"jacobianCalls\":%llu,\"jacobianSeconds\":%.9f,\"newtonIterations\":%llu,\"newtonConvergenceFailures\":%llu,\"auditComparisons\":%llu,\"auditDifferences\":%llu,\"contextComparedBytes\":%llu,\"contextCopiedBytes\":%llu,\"modelCalls\":",
(unsigned long long)lp_mask,(unsigned long long)(saved?sizeof(*saved):0),(unsigned long long)jac_calls,(double)jac_ticks/frequency,
(unsigned long long)newton_iterations,(unsigned long long)newton_failures,(unsigned long long)audit_count,(unsigned long long)audit_diff,
(unsigned long long)compared_bytes,(unsigned long long)copied_bytes);
vector(f,model_calls,LP_NC+1);fprintf(f,",\"groups\":[");
for(int g=0;g<LP_NC;g++){
fprintf(f,"%s{\"color\":%d,\"attempts\":%llu,\"hits\":%llu,\"contextMisses\":%llu,\"invalidTrials\":%llu,\"executed\":",g?",":"",g,(unsigned long long)attempts[g],(unsigned long long)hits[g],(unsigned long long)context_misses[g],(unsigned long long)invalid_trials[g]);
vector(f,executed[g],LP_NO);fprintf(f,",\"skipped\":");vector(f,skipped[g],LP_NO);fprintf(f,"}");
}
fprintf(f,"],\"kernelNames\":[");for(int i=0;i<LP_NK;i++){fprintf(f,"%s\"%s\"",i?",":"",lp_kernel_names[i]);}
fprintf(f,"],\"kernelCalls\":[");for(int g=0;g<=LP_NC;g++){if(g)fputc(',',f);vector(f,kernel_calls[g],LP_NK);}fprintf(f,"]}\n");fclose(f);
free(saved);saved=NULL;lp_mask=0;
}
+145
View File
@@ -0,0 +1,145 @@
"""Isolated mechanical-event builds; never edits production numerics or cache.
Augment the generated program with local contact descriptors and an optional
read-only release-drive evaluator. The ordinary RHS, Jacobian, property reuse,
state layout, output layout, and component formulas stay byte-for-byte intact.
"""
from dataclasses import replace
import hashlib
import json
from pathlib import Path
import re
import shutil
from app.simulation.components.amesim.semantics import contact_stiffness
HERE = Path(__file__).resolve().parent
MODES = ('off', 'mass', 'lstp', 'all')
def replace_once(text, old, new):
if text.count(old) != 1:
raise ValueError(f'Experiment patch no longer matches the reviewed runtime: {old[:100]}')
return text.replace(old, new, 1)
def prepare_runtime(original, destination):
original, destination = Path(original), Path(destination)
shutil.copytree(original, destination, dirs_exist_ok=True)
shutil.copyfile(HERE/'mechanical_events_runtime.c', destination/'runtime/experimental_events.c')
common = destination/'runtime/common.c'
source = common.read_text(encoding='utf-8')
source = replace_once(source, 'int native_accept(', '#include "experimental_events.c"\n\nint native_accept(')
previous_size=('2*(NSTOPS+NFRICTIONS+NCONTACTS+1)' if '2*(NSTOPS+NFRICTIONS+NCONTACTS+1)' in source
else '2*(NSTOPS+NFRICTIONS+1)')
event_size=previous_size[:-2]+'NEXPERIMENT_CONTACTS+NEXPERIMENT_RELEASES+1)'
source = source.replace(previous_size,event_size)
for field in ('bounds','restitution','thresholds'):
declaration=field+'['+event_size+']'
if declaration+'={0}' not in source:
source=replace_once(source,declaration,declaration+'={0}')
source = replace_once(source, ' double stop=next;', ''' if (!experiment_candidates(r,t,next,old,trial,dense,context,when,indices,friction,&count)) return -1;
double stop=next;''')
if 'if(friction[i]>=0) continue;' in source:
source = replace_once(source, 'if(friction[i]>=0) continue;', 'if(friction[i]!=-1) continue;')
elif 'if(friction[i]!=-1) continue;' not in source:
raise ValueError('Unknown hard-stop event dispatch')
source = source.replace('if(friction[i]<0 &&', 'if(friction[i]==-1 &&')
source = replace_once(source, ' if (!native_append(r,stop,accepted_state)) return -1;', ''' experiment_commit(r,stop,accepted_state,when,indices,friction,count);
if (!native_append(r,stop,accepted_state)) return -1;''')
common.write_text(source, encoding='utf-8')
header = destination/'include/runtime.h'
source = header.read_text(encoding='utf-8')
source = replace_once(source, ' NativePropertyWarning property_warnings[6];', ''' double experiment_last[2*NEXPERIMENT_CONTACTS+NEXPERIMENT_RELEASES+1];
int experiment_direction[2*NEXPERIMENT_CONTACTS+NEXPERIMENT_RELEASES+1];
int experiment_pending[2*NEXPERIMENT_CONTACTS+NEXPERIMENT_RELEASES+1];
unsigned long experiment_checks, experiment_dense, experiment_roots, experiment_rhs;
unsigned long experiment_mass, experiment_lstp, experiment_clipping, experiment_release;
NativePropertyWarning property_warnings[6];''')
header.write_text(source, encoding='utf-8')
main = destination/'runtime/main.c'
source = main.read_text(encoding='utf-8')
source = replace_once(source, ' fprintf(f,",\\\"propertyWarnings\\\":"); native_property_warnings_json(r,f);', ''' fprintf(f,",\\\"experimentalEvents\\\":{\\\"checks\\\":%lu,\\\"denseCalls\\\":%lu,\\\"rootIterations\\\":%lu,\\\"releaseRhsCalls\\\":%lu,\\\"massContactTransitions\\\":%lu,\\\"lstpContactTransitions\\\":%lu,\\\"forceClipTransitions\\\":%lu,\\\"massReleaseTransitions\\\":%lu}",
r->experiment_checks,r->experiment_dense,r->experiment_roots,r->experiment_rhs,
r->experiment_mass,r->experiment_lstp,r->experiment_clipping,r->experiment_release);
fprintf(f,",\\\"propertyWarnings\\\":"); native_property_warnings_json(r,f);''')
main.write_text(source, encoding='utf-8')
def event_program(program, network, mode):
if mode not in MODES:
raise ValueError(mode)
# Reproduce opt-in experiments after production LSTP integration too:
# descriptors remain available, but only the selected experimental events
# run. This also keeps the tests portable when the frozen snapshot is absent.
program=replace(program,header=re.sub(r'#define NCONTACTS \d+','#define NCONTACTS 0',program.header))
# The off program uses the unmodified runtime and generated program.
if mode == 'off':
return program, dict(mode=mode, contacts=[], releases=[])
slots = {v.key: i for i, v in enumerate(program.variables)}
assignments = {}
for w, y in re.findall(r'w\[(\d+)\]\s*=\s*y\[(\d+)\]\s*;', program.source):
if int(w) in assignments and assignments[int(w)] != int(y):
raise ValueError('Ambiguous kinematic state alias')
assignments[int(w)] = int(y)
def velocity(key):
vi = assignments[slots[key+'.v']]
if assignments[slots[key+'.x']] != vi+1:
raise ValueError('Experiment requires the reviewed contiguous v/x state layout')
return vi
contacts = []
for c in network.components.values():
if c.model_type == 'amesim_lstp00a' and mode in ('lstp', 'all'):
contacts.append(dict(name=c.name, values=(3, velocity(c.name+'.port_1'), velocity(c.name+'.port_2'),
c.gap0, contact_stiffness(c), c.rcont, c.Pdis, int(c.discContactOption))))
if c.model_type == 'amesim_mecmas21' and int(c.stoptype) == 2 and mode in ('mass', 'all'):
for kind, side in ((1, 'min'), (2, 'max')):
contacts.append(dict(name=c.name+'.'+side, values=(kind, velocity(c.name), -1,
getattr(c,'x'+side), getattr(c,'Kb'+side), getattr(c,'Db'+side),
getattr(c,'Pd'+side), int(c.discContactOption))))
stop_count = int(re.search(r'#define NSTOPS (\d+)', program.header)[1])
releases = list(range(stop_count)) if mode in ('mass', 'all') else []
header = f'''
#define NEXPERIMENT_CONTACTS {len(contacts)}
#define NEXPERIMENT_RELEASES {len(releases)}
typedef struct {{ int kind, v1, v2; double boundary, stiffness, damping, pdis; int signed_force; }} ExperimentContact;
extern const ExperimentContact experiment_contacts[{max(1,len(contacts))}];
int model_experiment_release_drives(double t,const double *y,double *drives);
'''
data = ','.join('{'+','.join(str(v) if isinstance(v,int) else repr(float(v)) for v in c['values'])+'}' for c in contacts)
additions = f'\nconst ExperimentContact experiment_contacts[{max(1,len(contacts))}] = {{{data or "{0,0,0,0,0,0,0,0}"}}};\n'
if releases:
start = program.source.index('static int model_eval_internal(')
body_start = program.source.index('{', start)
depth, end = 1, body_start+1
while depth:
depth += (program.source[end] == '{') - (program.source[end] == '}')
end += 1
function = program.source[start:end]
function = replace_once(function, 'model_eval_internal(', 'model_experiment_release_internal(')
pos = function.index(') {')
function = function[:pos] + ',double *release_drives' + function[pos:]
found = []
def capture(match):
vi = int(re.search(r'&dy\[(\d+)\]', match[0])[1])
index = len(found)
found.append(vi)
return f'release_drives[{index}]=dy[{vi}];'
function = re.sub(r'native_stop_motion\([^;]+\);', capture, function)
if len(found) != stop_count:
raise ValueError('Release evaluator does not match generated stop count')
extended = 'ModelJacobianWorkspace' in function
args = '0,NULL,NULL,NULL,drives' if extended else 'NULL,drives'
additions += function + '\nint model_experiment_release_drives(double t,const double *y,double *drives) {double dy[NSTATES],w[NOUTPUTS];return model_experiment_release_internal(t,y,dy,w,'+args+');}\n'
else:
additions += 'int model_experiment_release_drives(double t,const double *y,double *drives) {(void)t;(void)y;(void)drives;return 1;}\n'
# Add declarations before the include guard closes, preserving all generated
# RHS and Jacobian source bytes as an exact prefix.
at = program.header.rfind('#endif')
modified = replace(program, source=program.source+additions, header=program.header[:at]+header+program.header[at:])
assert modified.source.startswith(program.source)
return modified, dict(mode=mode, contacts=contacts, releases=releases,
originalModelSourceSha256=hashlib.sha256(program.source.encode()).hexdigest(),
jacobianStructure=program.jacobian_structure)
@@ -0,0 +1,37 @@
# MASS / LSTP 独立事件实验
此实验只修改复制的原生运行库和实验生成程序,不改生产 `native/`、组件公式、生成器、工况、容差、雅可比分色或物性复用。平台默认运行不启用这里的事件。
`mechanical_event_variant.py` 在原生成源码后追加只读事件描述与限位释放驱动力求值入口;原 RHS 和雅可比源码保持为完全相同的前缀,状态/输出布局、求值调度、稀疏结构元数据一致。`prepare_runtime` 仅在指定的独立目标目录增加事件定位、提交和诊断;严格检查替换位置,生产代码变化后不静默套用未知补丁。
四种构建:
- `off`:使用未修改的原生源码和生成程序。
- `mass`:弹性上下限接触/脱离;非负力模式的原始力过零;理想/恢复限位保持后的释放。已有碰撞、恢复系数和摩擦事件保持原处理。
- `lstp`:LSTP 间隙过零;非负力模式的原始力过零。允许负力时不注册力截断事件。
- `all`:两类同时启用。
弹性事件不重置位置或速度;限位释放从约束位置、零速度继续。已接受事件才更新防重复触发记录;试算、雅可比和结果重放不写物理模式。接触力仍由原来的时间、状态、参数纯函数求值,事件记录不改变力公式。相同浮点时刻的事件一次提交和重启,保留已有输出语义。
定位使用已接受步的密集插值及二分,浮点区间不可再分时停止。相对速度换向时分两段查找间隙根,覆盖端点同号但中间接触/脱离的情况;孤立切触不制造来回事件。本实验与原有事件机制一样依赖积分步已经解析运动,未证明可捕获一个接受步内任意多次高频振荡。根定位误差不等于积分状态误差,必须做步长与容差核验。
MASS 释放检查只在已贴近限位且速度接近零时执行。为隔离已有优化,本实验采用独立只读整模型求值取得未施加限位约束的加速度,不修改正常 RHS 的数据通路;这会产生可单独计数的 `releaseRhsCalls`。因此其耗时是本实现的代价,不能当成所有限位释放算法必然的开销。
运行独立解析解、非负力、指数阻尼、无接触/初始贴边、密集插值换向及已有 Amesim 参考测试:
```powershell
.\.venv-win\Scripts\python.exe -X utf8 -m unittest tests.test_mechanical_event_experiment -v
```
八路对照依赖本工作站已有 Amesim 2404、当前循环 AME 和之前保存的非循环 AME,以及上轮审计对齐的工程副本:
```powershell
.\.venv-win\Scripts\python.exe -X utf8 tests/manual/evaluate_mechanical_events.py --output test/mechanical-events-new-run --repeats 5
.\.venv-win\Scripts\python.exe -X utf8 tests/manual/summarize_mechanical_events.py test/mechanical-events-new-run
```
主目录必须新建;`--resume` 仅用于未变更实验源码情况下,保留已经完成的准确性/计时结果并续跑。因 Windows 偶发缓存目录重命名拒绝,构建可在独立新缓存重试。若编译子进程启动不稳定,可追加 `--serial-build`,只串行编译实验程序,不修改求解配置或生产构建逻辑。
每个工况重新运行一次 Amesim 作为四种配置共同的参照。每种配置保留一次完整输出用于同阶段对照;再轮换配置顺序做五次纯求解计时。编译和输出序列化不计入纯求解耗时,单独的完整运行数据仍保存。每个比较点同时核验十个信号阶段,所有曲线共用同一对真实保存行;不能选择误差最小的力值来配对。
完整指标与限制见本次 `docs/other/` 的独立实验报告。
+178
View File
@@ -0,0 +1,178 @@
/* Included only by an isolated runtime copy. No production RHS mode, force
* formula, state dimension, Jacobian coloring or property cache is changed. */
#include <float.h>
#if NEXPERIMENT_CONTACTS
static double experiment_penetration(int j,const double *y) {
ExperimentContact c=experiment_contacts[j];
if(c.kind==1) return c.boundary-y[c.v1+1];
if(c.kind==2) return y[c.v1+1]-c.boundary;
/* Preserve exactly the production gap expression and operation order. */
return -(c.boundary+y[c.v2+1]-y[c.v1+1]);
}
static double experiment_velocity(int j,const double *y) {
ExperimentContact c=experiment_contacts[j];
if(c.kind==1) return -y[c.v1];
if(c.kind==2) return y[c.v1];
return y[c.v1]-y[c.v2];
}
static double experiment_value(int j,int force,const double *y) {
double p=experiment_penetration(j,y);
if(force==2) return experiment_velocity(j,y);
if(!force) return p;
ExperimentContact c=experiment_contacts[j];
double fraction=c.pdis>0 ? -expm1(-fmax(p,0)/c.pdis) : 1;
return c.stiffness*p+fraction*c.damping*experiment_velocity(j,y);
}
static double experiment_locate(NativeRun *r,int j,int force,double left,double right,
double sign,NativeDense dense,void *context) {
double y[NSTATES];
for(int k=0;k<60;k++) {
double mid=left+.5*(right-left);
if(mid<=left || mid>=right) break;
r->experiment_dense++;r->experiment_roots++;
if(!dense(context,mid,y)) return NAN;
double g=experiment_value(j,force,y);
if(!isfinite(g)) return NAN;
if(sign<0 ? g>=0 : g<=0) right=mid; else left=mid;
}
return right;
}
static double experiment_bracket(NativeRun *r,int j,int force,double left,double right,
double a,double b,NativeDense dense,void *context,int *direction) {
int slot=2*j+force;
if(r->experiment_direction[slot] && r->experiment_last[slot]==left)
a=r->experiment_direction[slot]*DBL_MIN;
if(a==0 || (a>0 ? b>0 : b<0)) return INFINITY;
*direction=a<0 ? 1 : -1;
return experiment_locate(r,j,force,left,right,a,dense,context);
}
static double experiment_contact_candidate(NativeRun *r,int j,int force,double t,double next,
const double *old,const double *trial,
NativeDense dense,void *context,int *direction) {
double a=experiment_value(j,force,old), b=experiment_value(j,force,trial);
if(!isfinite(a) || !isfinite(b)) return NAN;
/* Detect a gap excursion and return through the same boundary even when
* the endpoint gaps have the same sign: split at the velocity reversal.
* Like the existing event locator, this relies on resolved accepted steps;
* arbitrarily many unresolved oscillations in one step are not certified. */
if(!force) {
double va=experiment_velocity(j,old),vb=experiment_velocity(j,trial);
if((va<0 && vb>0) || (va>0 && vb<0)) {
double turn=experiment_locate(r,j,2,t,next,va,dense,context),y[NSTATES];
if(!isfinite(turn)) return NAN;
r->experiment_dense++;
if(!dense(context,turn,y)) return NAN;
double g=experiment_value(j,force,y);
if(g==0 && ((a<0 && b<0) || (a>0 && b>0))) return INFINITY;
double found=experiment_bracket(r,j,force,t,turn,a,g,dense,context,direction);
if(isfinite(found) || isnan(found)) return found;
return experiment_bracket(r,j,force,turn,next,g,b,dense,context,direction);
}
}
return experiment_bracket(r,j,force,t,next,a,b,dense,context,direction);
}
#endif
#if NEXPERIMENT_RELEASES
static int experiment_drives(NativeRun *r,double t,const double *y,double *drives) {
r->nfev++;r->experiment_rhs++;
return model_experiment_release_drives(t,y,drives);
}
static double experiment_release_root(NativeRun *r,int j,int lower,double left,double right,
NativeDense dense,void *context) {
double y[NSTATES],drives[NEXPERIMENT_RELEASES];
for(int k=0;k<60;k++) {
double mid=left+.5*(right-left);
if(mid<=left || mid>=right) break;
r->experiment_dense++;r->experiment_roots++;
if(!dense(context,mid,y) || !experiment_drives(r,mid,y,drives)) return NAN;
if(lower ? drives[j]>0 : drives[j]<0) right=mid; else left=mid;
}
return right;
}
#endif
static int experiment_candidates(NativeRun *r,double t,double next,const double *old,const double *trial,
NativeDense dense,void *context,double *when,int *indices,int *kinds,int *count) {
(void)r;(void)t;(void)next;(void)old;(void)trial;(void)dense;(void)context;
(void)when;(void)indices;(void)kinds;(void)count;
#if NEXPERIMENT_CONTACTS
for(int j=0;j<NEXPERIMENT_CONTACTS;j++) {
r->experiment_checks++;
for(int force=0;force<2;force++) {
if(force && (experiment_contacts[j].signed_force==1 ||
experiment_penetration(j,old)<=0)) continue;
int direction=0;
double at=experiment_contact_candidate(r,j,force,t,next,old,trial,dense,context,&direction);
if(isnan(at)) return 0;
if(isfinite(at)) {
if(force) {
double y[NSTATES];r->experiment_dense++;
if(!dense(context,at,y)) return 0;
if(experiment_penetration(j,y)<=0) continue;
}
int n=(*count)++;
when[n]=at;indices[n]=j;kinds[n]=force?-3:-2;
r->experiment_pending[2*j+force]=direction;
}
}
}
#endif
#if NEXPERIMENT_RELEASES
double before[NEXPERIMENT_RELEASES],after[NEXPERIMENT_RELEASES];
int evaluated=0;
for(int j=0;j<NEXPERIMENT_RELEASES;j++) {
NativeStop s=model_stops[j];int v=s.velocity_index, x=v+1;
if(fabs(old[v])>1e-12*fmax(fabs(old[v]),1)) continue;
if(r->experiment_direction[2*NEXPERIMENT_CONTACTS+j] &&
r->experiment_last[2*NEXPERIMENT_CONTACTS+j]==t) continue;
for(int lower=0;lower<2;lower++) {
double bound=lower?s.lower:s.upper,tol=1e-12*fmax(fabs(bound),1);
if(lower ? old[x]>bound+tol : old[x]<bound-tol) continue;
if(!evaluated) {
if(!experiment_drives(r,t,old,before) || !experiment_drives(r,next,trial,after)) return 0;
evaluated=1;
}
r->experiment_checks++;
if(lower ? !(before[j]<=0 && after[j]>0) : !(before[j]>=0 && after[j]<0)) continue;
double at=experiment_release_root(r,j,lower,t,next,dense,context);
if(!isfinite(at)) return 0;
int n=(*count)++;when[n]=at;indices[n]=j;kinds[n]=lower?-4:-5;
}
}
#endif
return 1;
}
static void experiment_commit(NativeRun *r,double t,double *y,const double *when,const int *indices,const int *kinds,int count) {
(void)y;
for(int i=0;i<count;i++) {
if(kinds[i]>-2 || when[i]!=t) continue;
int j=indices[i];
(void)j;
#if NEXPERIMENT_CONTACTS
if(kinds[i]==-2 || kinds[i]==-3) {
int force=kinds[i]==-3,slot=2*j+force;
r->experiment_last[slot]=t;
r->experiment_direction[slot]=r->experiment_pending[slot];
if(force) r->experiment_clipping++;
else if(experiment_contacts[j].kind==3) r->experiment_lstp++;
else r->experiment_mass++;
fprintf(stderr,"{\"phase\":\"mechanical-event\",\"kind\":\"%s\",\"index\":%d,\"time\":%.17g,\"direction\":%d,\"penetration\":%.17g,\"relativeVelocity\":%.17g}\n",
force?"force-clip":experiment_contacts[j].kind==3?"lstp-contact":"mass-contact",j,t,
r->experiment_direction[slot],experiment_penetration(j,y),experiment_velocity(j,y));
}
#endif
#if NEXPERIMENT_RELEASES
if(kinds[i]==-4 || kinds[i]==-5) {
NativeStop s=model_stops[j];int slot=2*NEXPERIMENT_CONTACTS+j;
y[s.velocity_index]=0;y[s.velocity_index+1]=kinds[i]==-4?s.lower:s.upper;
r->experiment_last[slot]=t;r->experiment_direction[slot]=1;r->experiment_release++;
fprintf(stderr,"{\"phase\":\"mechanical-event\",\"kind\":\"mass-release\",\"index\":%d,\"time\":%.17g,\"side\":\"%s\"}\n",
j,t,kinds[i]==-4?"lower":"upper");
}
#endif
}
(void)r;
}
+118
View File
@@ -0,0 +1,118 @@
"""Eight-branch SI mapping, carried forward from the audited 2026-09-14 comparison.
The caller supplies a fresh topology audit and network; no historical curves are loaded.
"""
import numpy as np
from app.simulation.reporting.amesim_results import _parse_variable_line, parse_amesim_results_bytes
def configure(current_audit, current_network):
global audit, network, aliases, nodes, adj
audit, network = current_audit, current_network
aliases = {row['component']: row['ameAlias'] for row in audit['mapping']}
nodes = {c.name: c for c in network.components.values()
if c.model_type in ('amesim_pn3node2', 'amesim_p4node2')}
adj = {}
for edge in network.connections:
if edge.kind == 'physical':
a, b = (e.key for e in edge.endpoints)
adj[a], adj[b] = b, a
def native_curves(series):
values = {k: np.asarray(v) for k, v in series.items()}
def energy(name):
key = name + '.reference_enthalpy_flow'
if key not in values:
result = np.zeros_like(values['time'])
for port in nodes[name].active_port_definitions:
if port.name == 'port_2':
continue
other = adj[name, port.name]
if other[0] in nodes and other[1] == 'port_2':
result += energy(other[0])
else:
q = values[name + '.' + port.name + '.m_flow']
result += q * np.where(q > 0, values['.'.join(other) + '.h_outflow'],
values[name + '.' + port.name + '.h_outflow'])
values[key] = result
return values[key]
for name in nodes:
energy(name)
values[name + '.reference_mass_flow'] = -values[name + '.port_2.m_flow']
for component in network.components.values():
if component.model_type == 'amesim_pnch012':
name = component.name
values[name + '.volume_work'] = -values[name + '.p'] * values[name + '.dvol']
return values
def read_ame(directory):
variables = tuple(_parse_variable_line(i, line) for i,line in enumerate(
(directory/'test_mql_.var').read_text(encoding='latin1').splitlines()))
return parse_amesim_results_bytes((directory/'test_mql_.results').read_bytes(), variables)
def curve_mapping():
result = []
for row in audit['mapping']:
kind, name, alias = row['type'],row['component'],row['ameAlias']
fields = []
if kind == 'amesim_mecmas21':
fields = [('x','x1','displacement','m',1,0),('v','v1','velocity','m/s',1,0)]
elif kind == 'amesim_lstp00a':
fields = [('force','f1','force','N',1,0),('gap','gap','gap','m',.001,0)]
elif kind in ('amesim_pnch023','amesim_pnch012'):
fields = [('p','press','pressure','Pa',1,101300),('T','temp','temperature','K',1,0)]
if kind == 'amesim_pnch012':
fields += [('vol','vol','volume','m3',1e-6,0),('m','mgas1','mass','kg',1e-3,0)]
elif kind in ('amesim_pnl0001','amesim_pnl0002'):
s = '2' if kind.endswith('1') else 'ctr'
fields = [('p','p'+s,'pressure','Pa',1,101300),('T','t'+s,'temperature','K',1,0)]
elif kind == 'amesim_pnl0003':
fields = [(k+str(i), k.lower()+str(i), 'pressure' if k=='p' else 'temperature',
'Pa' if k=='p' else 'K',1,101300 if k=='p' else 0) for k in ('p','T') for i in (1,2)]
elif kind in ('amesim_ud00','amesim_step0'):
fields = [('out.signal','output' if kind=='amesim_ud00' else 'out','signal','1',1,0)]
elif kind in ('amesim_pn3node2','amesim_p4node2'):
fields = [('reference_enthalpy_flow','dh2','enthalpy_flow','W',1,0),
('reference_mass_flow','dm2','mass_flow','kg/s',1e-3,0)]
elif kind == 'amesim_pnrp17':
fields = [('volume','vol1','piston_volume','m3',1e-6,0),
('volume_flow','vvol1','volume_rate','m3/s',1e-3/60,0)]
for prop, field, quantity, unit, scale, offset in fields:
result.append(dict(key=name+'.'+prop, amePath=field+'@'+alias,
quantity=quantity, unit=unit, scale=scale, offset=offset))
if kind == 'amesim_pnch012':
sources = []
volumes = []
for p in network.components[name].active_port_definitions:
other = adj[name,p.name]
component = network.components[other[0]]
if component.model_type == 'amesim_pnrp17':
sources.append('vvol1@'+aliases[other[0]])
volumes.append('vol1@'+aliases[other[0]])
elif other[0] in nodes and other[1] == 'port_2':
sources.append('dvol2@'+aliases[other[0]])
volumes.append('vol2@'+aliases[other[0]])
chamber = network.components[name]
effective_volume = dict(volumePaths=volumes, deadVolume=chamber.cvol0,
prescribedVolume=sum(chamber.external_volumes.values()))
result.append(dict(key=name+'.dvol',amePaths=sources, quantity='chamber_volume_rate',unit='m3/s', **effective_volume))
result.append(dict(key=name+'.volume_work',amePaths=sources,pressurePath='press@'+alias,
quantity='volume_work',unit='W', **effective_volume))
return result
def ame_curve(ame, m):
if 'amePath' in m:
return np.array(ame.series(m['amePath']))*m['scale']+m['offset']
rate = sum((np.array(ame.series(p)) for p in m['amePaths']), np.zeros(len(ame.times)))*(1e-3/60)
volume = sum((np.array(ame.series(p)) for p in m['volumePaths']), np.zeros(len(ame.times)))*1e-6
volume += m['deadVolume']+m['prescribedVolume']
# PNCH012.c sets its internal dvol to zero while the volume is limited.
# Summing raw piston rates without this condition invents enormous work
# that the original Amesim chamber does not actually apply.
limit = m['deadVolume']/100
rate = np.where((volume<limit) | ((volume<=limit) & (rate<0)), 0, rate)
if m['quantity'] == 'volume_work':
return -(np.array(ame.series(m['pressurePath']))+101300)*rate
return rate
+112
View File
@@ -0,0 +1,112 @@
"""Create shareable plots from evaluate_mql8_correctness.py evidence (Matplotlib)."""
import argparse
import json
from pathlib import Path
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from matplotlib import font_manager
import numpy as np
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('directory', type=Path)
args = parser.parse_args()
out = args.directory
font = Path('C:/Windows/Fonts/msyh.ttc')
if font.exists():
font_manager.fontManager.addfont(str(font))
plt.rcParams['font.family'] = font_manager.FontProperties(fname=str(font)).get_name()
plt.rcParams.update({'font.size': 9, 'axes.spines.top': False, 'axes.spines.right': False,
'axes.unicode_minus': False, 'svg.fonttype': 'none', 'figure.facecolor': 'white'})
summary = json.loads((out / 'summary.json').read_bytes())
full = np.load(out / 'full/curves.npz')
startup = np.load(out / 'startup/curves.npz')
fig, axes = plt.subplots(4, 2, figsize=(13, 13), constrained_layout=True)
selected = [
(startup, summary['startup']['groups']['pressure']['worstAbsolute']['key'], '绝对压力', 'kPa', 1e-3, .002, 1000),
(startup, summary['startup']['groups']['temperature']['worstAbsolute']['key'], '温度', 'K', 1, .002, 1000),
(full, 'amesim_mecmas21_1.x', '1 号质量块位移', 'mm', 1000, 50, 1),
(full, summary['full']['groups']['force']['worstAbsolute']['key'], '接触力(最差曲线)', 'kN', .001, 50, 1),
]
for (data, key, title, unit, scale, end, ts), (left, right) in zip(selected, axes):
mask = data['time'] <= end
t = data['time'][mask] * ts
native, ame = data['platform|' + key][mask] * scale, data['amesim|' + key][mask] * scale
left.plot(t, native, color='#1368a8', linewidth=1.6, label='当前平台')
left.plot(t, ame, color='#e07832', linestyle='--', linewidth=1.2, label='本次 Amesim')
left.set_title(title + ' · ' + key, fontsize=9)
left.legend(fontsize=8)
right.plot(t, native - ame, color='#925034', linewidth=1.1)
right.axhline(0, color='#888888', linewidth=.6)
right.set_title('平台 − Amesim;切换点保留', fontsize=9)
for ax in (left, right):
ax.set_xlabel('时间 / ' + ('ms' if ts == 1000 else 's'))
ax.set_ylabel(unit)
ax.grid(alpha=.18)
fig.suptitle('八路模型正确性初评 · 当前代码与本次 Amesim 执行\n启动段采样 0.1 ms;循环全程采样 10 ms;曲线重合不等于事件输出一致', fontsize=13)
fig.savefig(out / 'comparison.png', dpi=160)
fig.savefig(out / 'comparison.svg')
plt.close(fig)
fig, axes = plt.subplots(3, 2, figsize=(13, 10), constrained_layout=True)
# Show each of the eight physical contacts; full curve metrics still include
# the large event difference. The second column localizes away from events.
for i in range(1, 9):
key = f'amesim_lstp00a_{i}.force'
t = full['time']
error = full['platform|' + key] - full['amesim|' + key]
axes[0, 0].plot(t, error / 1000, linewidth=.8, label=str(i))
quiet = (t >= .1)
for event in summary['full']['signalEvents']:
quiet &= np.abs(t - event) > .0200001
visible = np.where(quiet, error, np.nan)
axes[0, 1].plot(t, visible, linewidth=.8, label=str(i))
axes[0, 0].set(title='8 路接触力差:全部共同采样点', ylabel='差值 / kN')
axes[0, 1].set(title='诊断视图:t≥0.1 s,避开切换前后 0.02 s', ylabel='差值 / N')
axes[0, 0].legend(title='支路', ncol=4, fontsize=7)
for row, (quantity, label) in enumerate([('enthalpy_flow', '节点焓流 / W'), ('mass_flow', '节点质量流 / kg/s')], 1):
key = summary['default']['groups'][quantity]['worstRelative']['key']
for column in range(2):
ax = axes[row, column]
t = full['time']
mask = (t >= .1) & (t <= .5)
n, a = full['platform|' + key], full['amesim|' + key]
if column == 0:
ax.plot(t[mask], n[mask], color='#1368a8', label='当前平台')
ax.plot(t[mask], a[mask], '--', color='#e07832', label='本次 Amesim')
ax.legend(fontsize=8)
ax.set_ylabel(label)
ax.set_title(key, fontsize=9)
else:
ax.plot(t[mask], (n-a)[mask], color='#925034')
ax.set_ylabel('绝对差,沿用左侧单位')
ax.set_title('小量差异单列,避免峰值归一化掩盖')
for ax in axes.flat:
ax.grid(alpha=.18)
ax.set_xlabel('时间 / s')
fig.suptitle('问题定位 · 切换点误差与小流量差异分开评价\n右上筛选只用于诊断;全部采样点仍进入主报告统计', fontsize=13)
fig.savefig(out / 'diagnostics.png', dpi=160)
fig.savefig(out / 'diagnostics.svg')
plt.close(fig)
data = np.load(out / 'volume-startup/curves.npz')
fig, axes = plt.subplots(1, 2, figsize=(12, 4), constrained_layout=True)
for ax, key, label, scale in [(axes[0], 'amesim_pnch012_15.vol', '有效气室容积 / L', 1000),
(axes[1], 'amesim_pnch012_15.volume_work', '气室体积功率 / TW', 1e-12)]:
mask = data['time'] <= 8e-8
for prefix, name, color, style in [('platform', '当前平台', '#1368a8', '-'), ('amesim', '本次 Amesim', '#e07832', '--')]:
ax.plot(data['time'][mask]*1e9, data[prefix+'|'+key][mask]*scale, style, color=color, label=name)
ax.set(xlabel='时间 / ns', ylabel=label)
ax.grid(alpha=.18)
ax.legend()
fig.suptitle('最初 80 ns:容积下限与体积功率 · 采样间隔 2 ns', fontsize=13)
fig.savefig(out / 'volume-startup.png', dpi=160)
fig.savefig(out / 'volume-startup.svg')
plt.close(fig)
if __name__ == '__main__':
main()
@@ -0,0 +1,51 @@
"""Read-only numerical probe of current UD00 event/evaluation consistency."""
import argparse
from pathlib import Path
import subprocess
import sys
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from app.simulation.native_codegen.build import _command, toolchain
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--output', type=Path, required=True)
args = parser.parse_args()
out = args.output.resolve()
out.mkdir(parents=True, exist_ok=True)
source = out / 'signal-boundary-probe.c'
code = r'''
#include "SIGNAL_SOURCE"
#include <stdio.h>
int main(void) {
double d[24]={0};
d[0]=1e17; d[8]=1e17; d[1]=49000; d[9]=49000; d[16]=.8; d[17]=10;
double t=0;
puts("boundary,left,at,right,expected_after,at_matches_after");
for(int i=0;i<10;i++) {
t=native_signal_break(t,60,0,2,1,d);
double expected=i%2==0?49000:1e17;
printf("%.17g,%.17g,%.17g,%.17g,%.17g,%d\n",t,
native_signal(nextafter(t,-INFINITY),0,2,1,d),native_signal(t,0,2,1,d),
native_signal(nextafter(t,INFINITY),0,2,1,d),expected,
native_signal(t,0,2,1,d)==expected);
}
return 0;
}
'''
source.write_text(code.replace('SIGNAL_SOURCE', (ROOT / 'native/components/modules/signal.c').as_posix()), encoding='ascii')
compiler, _, version = toolchain()
exe = out / ('signal-boundary-probe.exe' if sys.platform == 'win32' else 'signal-boundary-probe')
command = [compiler, '-std=c11', '-O2', '-ffp-contract=off', '-fno-fast-math',
'-I' + str(ROOT / 'native/include'), str(source), '-lm', '-o', str(exe)]
_command(command, log=[], timeout=60)
data = subprocess.check_output([str(exe)], timeout=10)
(out / 'signal-boundaries.csv').write_bytes(data)
print(version)
print(data.decode('ascii'))
if __name__ == '__main__':
main()
+218
View File
@@ -0,0 +1,218 @@
"""Sampled, exclusive timing of a COPY of the local-probe performance worker.
Original numerical functions remain in the executable verbatim. Only sampled
Jacobian callbacks use timing clones; ordinary residuals never reach a clone.
No production source or existing experiment implementation is edited.
"""
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor
import argparse, hashlib, json, os, re, shutil, statistics, subprocess, time
import local_probe_experiment as experiment
ROOT=experiment.ROOT
SOURCE=ROOT/'test/local-probe-20260917/worker'
OUT=ROOT/'test/local-probe-profile-direct-20260917'
HERE=Path(__file__).parent
replace=experiment.replace
span=experiment.function_span
write=experiment.write
def function(source,name):
a,b,e=span(source,name)
return source[a:e]
def annotate_tail(tail):
"""Fail closed for the reviewed generated model, not a general C parser.
Stage ownership follows extended.py; declarations remain in original scope.
Pipe argument PH inversion and accumulation belong to diagnostics; writes
of diagnostic outputs belong to remaining_outputs.
"""
phase='PF_NODE';mode='node';pipe=False;result=['PF_PROBE(PF_NODE);']
counts={};transitions=[]
for i,line in enumerate(tail.splitlines()):
category=phase
if mode=='node' and re.match(r'w\[\d+\] = [phq]\[\d+\];$',line):mode='port'
if mode=='port' and not re.match(r'w\[\d+\] = [phq]\[\d+\];$',line):mode='mechanical'
if mode=='mechanical' and line.startswith('dy[0]='):mode='gas'
if line.startswith('for(int i=0;i<NSTATES;i++) if(!isfinite'):mode='finite'
if mode=='node':category='PF_NODE'
elif mode=='port':category='PF_PORT'
elif mode=='mechanical':category='PF_MECHANICAL'
elif mode=='finite':category='PF_FINITE'
elif mode=='gas':
if line.startswith('{ double d[4],acc[4]={0};'):pipe=True;category='PF_PIPE'
elif pipe:
if re.match(r'w\[\d+\] = ',line):category='PF_OUTPUTS'
elif line=='}':pipe=False
else:category='PF_PIPE'
elif line.startswith('dy['):category='PF_GAS_EQUATIONS'
elif re.match(r'w\[\d+\] = ',line):category='PF_OUTPUTS'
else:raise AssertionError(('unclassified gas-tail statement',i,line))
if category!=phase:
result.append(f'PF_PROBE({category});');transitions.append((i,category));phase=category
counts[category]=counts.get(category,0)+1;result.append(line)
assert mode=='finite' and not pipe
assert all(counts.get(x,0) for x in ['PF_NODE','PF_PORT','PF_MECHANICAL','PF_GAS_EQUATIONS','PF_OUTPUTS','PF_PIPE','PF_FINITE']),counts
return '\n'.join(result),dict(lineCounts=counts,transitions=transitions)
def instrument_model(source,coarse=False):
clone=function(source,'model_eval_local_internal')
# Keep the original function and append an instrumented duplicate.
clone=clone.replace('model_eval_local_internal(', 'pf_model_eval_local_internal(',1)
begin=clone.index('{')+1;clone=clone[:begin]+'\nPF_PROBE(PF_INIT);'+clone[begin:]
gas=clone.index('if(!(jacobian ? native_jacobian_gas(')
clone=clone[:gas]+'PF_PROBE(PF_GAS_PREP);\n'+clone[gas:]
clone=replace(clone,'if(lp_capture){','PF_PROBE(PF_DISPATCH);\nif(lp_capture){')
clone=clone.replace('lp_snapshot(','pf_snapshot(').replace('lp_save(','pf_save(').replace('lp_reuse(','pf_reuse(')
n=0
def case(m):
nonlocal n;n+=1
return m[0]+f'pf_operation({m[1]});'
clone=re.sub(r'case (\d+):\{',case,clone)
assert n==484 and clone.count('break;}')==484
clone=clone.replace('break;}','PF_MARK(PF_DISPATCH);break;}')
start=clone.index('double node_energy[')
# Leave return 1 and function closing outside stage classification.
end=clone.rindex('return 1;')
tail,meta=annotate_tail(clone[start:end].rstrip())
clone=clone[:start]+tail+'\nPF_PROBE(PF_OTHER);\n'+clone[end:]
wrapper=function(source,'lp_eval').replace('int lp_eval(','int pf_lp_eval(',1)
begin=wrapper.index('{')+1
wrapper=wrapper[:begin]+'\nPF_SCOPE(lp_color+2,lp_color<0?PF_BASELINE:PF_OTHER);pf_eval_count();'+wrapper[begin:]
wrapper=wrapper.replace('lp_begin(t,y);','PF_MARK(PF_SNAPSHOT);lp_begin(t,y);PF_MARK(PF_BASELINE);')
wrapper=wrapper.replace('model_eval_local_internal(t,y,dy,w,1,NULL,NULL,workspace)',
'(pf_coarse?model_eval_local_internal(t,y,dy,w,1,NULL,NULL,workspace):pf_model_eval_local_internal(t,y,dy,w,1,NULL,NULL,workspace))' if coarse
else 'pf_model_eval_local_internal(t,y,dy,w,1,NULL,NULL,workspace)')
# This diagnostic is limited to the all-groups valid local path. Count an
# unexpected full-probe fallback explicitly, rather than misattribute it.
wrapper=replace(wrapper,'int result=eligible && valid ?',
'if(lp_color>=0 && !(eligible && valid))PF_MARK(PF_FULL_FALLBACK);\n int result=eligible && valid ?')
return source+'\n'+clone+'\n'+wrapper+'\n',meta
def instrument_support(source):
reuse=function(source,'lp_reuse').replace('int lp_reuse(','int pf_reuse(',1)
reuse=replace(reuse,'int contextual=lp_before[region]!=lp_after[region];',
'int contextual=lp_before[region]!=lp_after[region];\n if(contextual)PF_MARK(PF_CONTEXT);')
assert reuse.count('{context_misses[lp_color]++;return 0;}')==2
reuse=reuse.replace('{context_misses[lp_color]++;return 0;}',
'{context_misses[lp_color]++;PF_MARK(PF_DISPATCH);return 0;}')
reuse=replace(reuse,'if(contextual){properties->count=after->count;',
'PF_MARK(PF_RESTORE);\n if(contextual){properties->count=after->count;')
reuse=replace(reuse,'return 1;','PF_MARK(PF_DISPATCH);return 1;')
wrappers='''
void pf_snapshot(int index,NativePropertyCache *p,NativePipeCache *c){
if(!needed[index] || captured[index]){lp_snapshot(index,p,c);return;}
PF_MARK(PF_SNAPSHOT);lp_snapshot(index,p,c);PF_MARK(PF_BASELINE);
}
void pf_save(double *p,double *h,double *q,double *w,double *fb){
PF_MARK(PF_SNAPSHOT);lp_save(p,h,q,w,fb);PF_MARK(PF_BASELINE);
}
void lp_jac_time_end(uint64_t start,uint64_t end){jac_ticks+=end-start;jac_calls++;}
'''
return source+'\n'+reuse+'\n'+wrappers
def instrument_cv(source):
rhs=function(source,'jac_rhs_reuse').replace('jac_rhs_reuse(', 'pf_jac_rhs_reuse(',1).replace('lp_eval(', 'pf_lp_eval(')
colored=function(source,'colored_difference').replace('colored_difference(', 'pf_colored_difference(',1).replace('jac_rhs_reuse(', 'pf_jac_rhs_reuse(')
colored=replace(colored,'if (SUNMatZero(matrix)) return -1;',
'PF_SCOPE(0,PF_ZERO);if (SUNMatZero(matrix)) return -1;PF_MARK(PF_OTHER);')
colored=replace(colored,'lp_color=color;memcpy(test,state,NSTATES*sizeof(double));',
'PF_SCOPE(0,PF_PERTURB);lp_color=color;memcpy(test,state,NSTATES*sizeof(double));')
colored=replace(colored,'#if MODEL_JACOBIAN_GAS_REUSE', 'PF_MARK(PF_OTHER);\n#if MODEL_JACOBIAN_GAS_REUSE')
colored=replace(colored,'if (flag) return flag;','PF_SCOPE(0,PF_ASSEMBLY);if (flag) return flag;')
colored=replace(colored,' }\n context->run->jacobian_colored_evals++;',
' PF_MARK(PF_OTHER);\n }\n context->run->jacobian_colored_evals++;')
jac=function(source,'cv_jacobian_original').replace('cv_jacobian_original(', 'pf_cv_jacobian_original(',1).replace('jac_rhs_reuse(', 'pf_jac_rhs_reuse(').replace('colored_difference(', 'pf_colored_difference(')
jac=replace(jac,'int flag=jac_increments(context,y,fy,increments);',
'PF_SCOPE(0,PF_INCREMENT);int flag=jac_increments(context,y,fy,increments);PF_MARK(PF_OTHER);')
jac=replace(jac,'fy=tmp3;', 'PF_SCOPE(0,PF_OTHER);fy=tmp3;')
a,b,e=span(source,'cv_jacobian');sig=source[a:b]
wrapper=sig+'''{
lp_color=-1;uint64_t start=lp_tick();int selected=pf_select();
if(selected)pf_open();
int result=selected?pf_cv_jacobian_original(t,y,fy,matrix,user,tmp1,tmp2,tmp3):cv_jacobian_original(t,y,fy,matrix,user,tmp1,tmp2,tmp3);
if(selected)pf_close();
uint64_t finish=lp_tick();lp_jac_time_end(start,finish);
if(selected)pf_flush(start,finish);
if(!result){lp_matrix(t,N_VGetArrayPointer(y),SUNDenseMatrix_Data(matrix));pf_validate_matrix(t,N_VGetArrayPointer(y),SUNDenseMatrix_Data(matrix));}
return result;
}'''
return source[:a]+rhs+'\n'+colored+'\n'+jac+'\n'+wrapper+source[e:]
def prepare(coarse=False):
OUT.mkdir(exist_ok=True);work=OUT/('coarse-worker' if coarse else 'worker');work.mkdir(exist_ok=True)
sources={p.name:p.read_text(encoding='utf-8') for p in SOURCE.glob('*.c')}
before={k:hashlib.sha256(v.encode()).hexdigest() for k,v in sources.items()}
sources['model.c'],boundaries=instrument_model(sources['model.c'],coarse)
sources['local_probe_support.c']=instrument_support(sources['local_probe_support.c'])
sources['cvode_solver.c']=instrument_cv(sources['cvode_solver.c'])
sources['common.c']=replace(sources['common.c'],'lp_start();','lp_start();pf_initialize();')
sources['common.c']=replace(sources['common.c'],'lp_finish();','lp_finish();pf_finish();')
plan=json.loads((SOURCE.parent/'plan.json').read_text(encoding='utf-8'))
affected=[[int(i in set(g['affectedOperations'])) for i in plan['order']] for g in plan['groups']]
tables='const unsigned char pf_affected[LP_NC][LP_NO]={'+','.join('{'+','.join(map(str,a))+'}' for a in affected)+'};'
sources['local_probe_profile.c']=(HERE/'local_probe_profile.c').read_text(encoding='utf-8').replace('/* PROFILE_TABLES */',tables)
for name in ('model.h','local_probe.h'):shutil.copyfile(SOURCE/name,work/name)
shutil.copyfile(HERE/'local_probe_profile.h',work/'local_probe_profile.h')
cc,sun,_=experiment.builder.toolchain();flags,libs,dlls,exe=experiment.builder.platform_build_inputs(sun);flags+=['-DLP_OBSERVE=0']
started=time.perf_counter()
def compile_one(item):
i,(name,src)=item;path=work/name;path.write_text('#include "local_probe_profile.h"\n'+src,encoding='utf-8',newline='\n');obj=work/f'profile-{i}.o';log=[]
experiment.builder._command([cc,*flags,'-I',str(work),'-I',str(experiment.builder.NATIVE/'include'),'-I',str(sun/'include'),'-c',str(path),'-o',str(obj)],log=log,timeout=180)
return obj,log
with ThreadPoolExecutor(max_workers=4) as pool:objects=list(pool.map(compile_one,enumerate(sources.items())))
log=[];experiment.builder._command([cc,*flags,*[str(o) for o,_ in objects],*experiment.builder.link_library_arguments(libs),'-lm','-o',str(work/exe)],log=log)
for dll in dlls:shutil.copyfile(dll,work/dll.name)
(work/'build.log').write_text('\n'.join(sum([x for _,x in objects],[])+log),encoding='utf-8')
write(OUT/('build-coarse.json' if coarse else 'build.json'),dict(sourceHashes=before,sourceDirectory=str(SOURCE),boundaries=boundaries,seconds=time.perf_counter()-started))
print('BUILT',time.perf_counter()-started,flush=True)
def run(label,stride=16,offset=0,control=False,matrices=False,coarse=False):
work=OUT/label;work.mkdir(exist_ok=True)
executable=(SOURCE if control else OUT/('coarse-worker' if coarse else 'worker'))/'model.exe'
env=os.environ.copy();env.update(LOCAL_PROBE_MASK='0x7ffffff',PROBE_PROFILE_STRIDE=str(stride),PROBE_PROFILE_OFFSET=str(offset),PROBE_PROFILE_MATRICES=str(int(matrices)),PROBE_PROFILE_COARSE=str(int(coarse)))
args=[str(executable),'--method','BDF','--start','0','--stop','10','--sample-step','.01','--max-step','1e30','--rtol','1e-8','--timeout','300',
'--sample-file',str(work/'states.bin'),'--output-block-file',str(work/'outputs.bin'),'--output',str(work/'result.json')]
start=time.perf_counter()
with (work/'stderr.log').open('wb') as f:
p=subprocess.run(args,cwd=work,env=env,stdout=subprocess.PIPE,stderr=f,timeout=330,creationflags=subprocess.CREATE_NO_WINDOW)
elapsed=time.perf_counter()-start
if p.returncode:raise RuntimeError((label,p.returncode,(work/'stderr.log').read_text()[-4000:]))
r=json.loads((work/'result.json').read_text());diag=json.loads((work/'probe.json').read_text())
fields=('success','finalState','final','propertyWarnings','acceptedSteps','rejectedSteps','stateTransitions','solverStarts','nfev','njev','nlu','solveSeconds','solveCpuSeconds')
record={k:r[k] for k in fields}
for name in ('states','outputs','events','jacobians'):
path=work/f'{name}.bin'
if path.exists():
with path.open('rb') as f:record[name+'Sha256']=hashlib.file_digest(f,'sha256').hexdigest()
record.update(label=label,stride=stride,offset=offset,control=control,matrices=matrices,coarse=coarse,processSeconds=elapsed,diagnostic=diag)
write(work/'measurement.json',record)
# Numerical gate on every run, including the sampling-density controls.
reference=json.loads((SOURCE.parent/'all-run-0/measurement.json').read_text(encoding='utf-8'))
exact=['statesSha256','outputsSha256','eventsSha256','finalState','final','propertyWarnings','acceptedSteps','rejectedSteps','stateTransitions','solverStarts','nfev','njev','nlu']
differences=[k for k in exact if record[k]!=reference[k]]
differences += [k for k in ('newtonIterations','newtonConvergenceFailures','contextComparedBytes','contextCopiedBytes','modelCalls','groups') if diag[k]!=reference['diagnostic'][k]]
assert not differences,(label,differences)
if matrices:
ref=SOURCE.parent/'all-audit/jacobians.bin'
with ref.open('rb') as f:assert record['jacobiansSha256']==hashlib.file_digest(f,'sha256').hexdigest()
print('RUN',label,'Jacobian',diag['jacobianSeconds'],'solve',r['solveSeconds'],'CPU',r['solveCpuSeconds'],'process',elapsed,'exact OK',flush=True)
return record
def batch():
run('control-warmup',control=True)
run('profile-warmup',16,101)
for i in range(5):
jobs=[(f'control-{i}',0,0,True),(f'profile-{i}',16,i+17,False)]
if i%2:jobs.reverse()
for label,stride,offset,control in jobs:run(label,stride,offset,control)
for i in range(2):
run(f'disabled-{i}',0,i)
run(f'density8-{i}',8,i+37)
run(f'density32-{i}',32,i+71)
if __name__=='__main__':
p=argparse.ArgumentParser();p.add_argument('action',choices=['prepare','run','batch']);p.add_argument('--label',default='sample');p.add_argument('--stride',type=int,default=16);p.add_argument('--offset',type=int,default=0);p.add_argument('--control',action='store_true');p.add_argument('--matrices',action='store_true');p.add_argument('--coarse',action='store_true');a=p.parse_args()
if a.action=='prepare':prepare(a.coarse)
elif a.action=='batch':batch()
else:run(a.label,a.stride,a.offset,a.control,a.matrices,a.coarse)
+137
View File
@@ -0,0 +1,137 @@
# R288 / position16 最小 real skip 实验
## 结论
1. **完整求解结果逐位一致。** 0–10 s 全轨迹,896 个 132×132 Jacobian(15,611,904 个元素)及每个 t/y、states、outputs、事件、最终状态与既有基线一致;没有使用数值容差。
2. **896/896 次真实 skip;自然 reject/fallback 为 0。** commit 896 次,原目标 native operation 实际执行 0 次,Reference 双路执行 0 次。
3. **当前 replay 更贵。** 7 轮中位数:原 operation 1.143 µs/次,validation + overlay/patch + commit 22.521 µs/次(19.70 倍)。目标完整路径 control 1.186 µs/次,real skip 22.913 µs/次。
4. **暂不把这一实现直接扩展到 position52。** 正确性门槛已满足,净收益门槛未满足。应先降低 metadata 采集、事务复制和解释执行成本;本轮不能据此推断 position52 或其他 interval 的收益。
## 范围与实现
- 全部改动仅在独立生成的实验 worker 及 `tests/manual`;生产路径、默认开关、property cache 语义、原 whole-context guard 未改动。
- 入口仅为 `lp_color == 6 && region == 288` 的 `case 16`,位于原 `lp_reuse` 失败之后。其他位置继续原执行。
- baseline position16 用单独命名空间的 kernels 采集必需的有序事件 metadata。其他 operation 使用原 kernels,没有全局访问插桩。
- guard 来自已验证的 shadow 源码,构建时校验其 SHA-256,并断言 guard 函数保持一致;唯一变量替换是将原事务入口 count 改为当前真实 probe 入口 count。
- 复制当前 probe 到私有 overlay;按当前 entries 验证 first-match/miss,重定位逻辑 slot,从当前 count 追加。通过后构造地址已转换的有序 write set,再 commit。已有 entries、未写字段和其他 pipe slots 保持原值。
- Observer、capacity/scratch、memo lifetime/value、消费字段、valid、pipe branch、未知副作用/非有限值等原保护条件保留;失败只回退原 operation。
- 性能版不执行 Reference、不导出访问日志或 Jacobian、不逐次比较完整 context。保留运行所需 metadata、严格 guard、overlay/patch、计时和累计计数。
## 正确性证据
| 检查 | 结果 |
|---|---:|
| Jacobian、t/y 与既有 all-audit 文件逐字节比较 | 896/896 一致 |
| 每个 Jacobian 的 baseline + 27 group evaluator 出口 | 25,088/25,088 一致 |
| 目标 operation 入口 / 出口 | 896 / 896 一致 |
| 返回状态、dy/w、active property entries 有序字段/valid、全部 pipe slots | 逐位一致 |
| memo 每次 Jacobian 的表内容、owner 绑定、recording 生命周期 | 一致 |
| 所有 probe 的 memo entries 只读检查 | 通过 |
| gas memo 内容、kernel 绑定及计数 | 一致 |
| errno、x87/SSE flags/rounding、warning/observer | 一致 |
| count 差异 / slot relocation 的目标 probe | 638 / 571 |
| 顺序 append | 1,658 |
| PT miss → PT miss / 近零流量无查询 | 829 / 67 |
Audit 对照来自**独立 control 进程真实执行当前 probe**的出口,约 1.88 GB 二进制数据。跨进程地址比较采用 owner/function 绑定身份,数值字段保持原始位模式;不把 C padding 当成数值。memo 表逐 Jacobian 比较,后续每个 probe 同时检查整表未变。目标入口、出口与所有 group 的完整 evaluator 出口均参与比较。audit 的 I/O 保留并恢复 errno、x87 和 SSE 环境。
| 求解器计数 | control / real skip |
|---|---:|
| accepted / rejected | 10840 / 918 |
| Newton iterations / convergence failures | 19371 / 798 |
| nfev / njev / nlu | 44467 / 896 / 3106 |
| solverStarts / stateTransitions | 4 / 1 |
额外负例:每 128 个目标 probe 注入一次晚期 nonfinite-output reject,发生在 overlay 已执行有序更新之后。7 次 reject 均完成无污染检查,fallback/native 原执行各 7 次,commit/skip 各 889 次;全轨迹仍与同一 control 和既有基线逐位一致。没有放宽 guard,也未改变容差。
## 性能方法与原始数据
Windows / MinGW GCC,原构建优化选项(`-O3 -ffp-contract=off -fno-fast-math`)。完成正确性和回退测试后单独编译性能 worker;control、skip 各预热一次,再交替串行运行 7 组。表中顺序就是实际顺序。每轮 states/outputs/events 指纹和全部指定求解器计数保持一致,skip 每轮均为 896、reject 0、原执行 0。
QPC 粗粒度计时;累计整数 tick,积分过程中不做浮点时间换算。validation 包括语义校验所必需的临时有序更新;overlay/patch 包括当前 context 复制及提交 write set 构造;commit 是真实字段写回。完整 target 路径另计,包含调度、计数和额外计时开销。未减去计时器自身成本,未使用 shadow/audit 时间推断性能。
| 运行顺序 | 原执行 µs/次 | validation µs/次 | overlay/patch µs/次 | commit µs/次 | fallback ms | target 总 ms | baseline pos16 总 ms | Jacobian s | 积分 CPU s | 积分 wall s | skip/reject |
|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
| pair-1-control | 1.562 | 0.000 | 0.000 | 0.000 | 0.000 | 1.439 | 1.760 | 2.190858 | 6.375000 | 6.564619 | 0/0 |
| pair-1-skip | 0.000 | 6.172 | 15.011 | 0.434 | 0.000 | 19.785 | 19.239 | 2.007014 | 5.953125 | 6.076049 | 896/0 |
| pair-2-skip | 0.000 | 6.451 | 14.713 | 0.435 | 0.000 | 19.716 | 18.944 | 1.971936 | 5.921875 | 5.910387 | 896/0 |
| pair-2-control | 1.170 | 0.000 | 0.000 | 0.000 | 0.000 | 1.088 | 1.533 | 2.000030 | 6.046875 | 6.140446 | 0/0 |
| pair-3-control | 1.143 | 0.000 | 0.000 | 0.000 | 0.000 | 1.063 | 1.540 | 2.018777 | 6.015625 | 6.213678 | 0/0 |
| pair-3-skip | 0.000 | 6.831 | 15.331 | 0.436 | 0.000 | 20.563 | 19.928 | 2.014426 | 6.031250 | 6.120732 | 896/0 |
| pair-4-skip | 0.000 | 7.101 | 15.141 | 0.434 | 0.000 | 20.651 | 19.911 | 2.035451 | 6.015625 | 6.135601 | 896/0 |
| pair-4-control | 1.093 | 0.000 | 0.000 | 0.000 | 0.000 | 1.017 | 1.508 | 1.933127 | 5.921875 | 5.940958 | 0/0 |
| pair-5-control | 1.080 | 0.000 | 0.000 | 0.000 | 0.000 | 1.009 | 1.676 | 1.935641 | 5.906250 | 5.900561 | 0/0 |
| pair-5-skip | 0.000 | 6.334 | 15.975 | 0.451 | 0.000 | 20.720 | 19.466 | 2.038743 | 5.984375 | 6.166865 | 896/0 |
| pair-6-skip | 0.000 | 6.508 | 14.442 | 0.444 | 0.000 | 19.496 | 19.729 | 2.016491 | 5.906250 | 6.033967 | 896/0 |
| pair-6-control | 1.177 | 0.000 | 0.000 | 0.000 | 0.000 | 1.093 | 1.512 | 2.030009 | 6.250000 | 6.418474 | 0/0 |
| pair-7-control | 1.119 | 0.000 | 0.000 | 0.000 | 0.000 | 1.039 | 1.499 | 1.970795 | 5.953125 | 6.030361 | 0/0 |
| pair-7-skip | 0.000 | 6.166 | 15.893 | 0.462 | 0.000 | 20.530 | 19.112 | 2.008419 | 6.078125 | 6.126521 | 896/0 |
baseline pos16:control 为原 baseline operation;skip 包含原 baseline operation + 本次 replay 必需 metadata 采集,不能漏算这部分成本。性能各轮没有自然 reject,因此 fallback 总时间为 0;这不代表一次 fallback 的成本为零,本轮未估计该分支的单次性能。
### 中位数及 min/max
| 项目 | control 中位数 [min, max] | real skip 中位数 [min, max] |
|---|---:|---:|
| originalSeconds(s,896 次累计) | 0.001024400 [0.000967700, 0.001399500] | 0.000000000 [0.000000000, 0.000000000] |
| validationSeconds(s,896 次累计) | 0.000000000 [0.000000000, 0.000000000] | 0.005780000 [0.005525000, 0.006362100] |
| overlayPatchSeconds(s,896 次累计) | 0.000000000 [0.000000000, 0.000000000] | 0.013566200 [0.012939600, 0.014314000] |
| commitSeconds(s,896 次累计) | 0.000000000 [0.000000000, 0.000000000] | 0.000390700 [0.000388500, 0.000414100] |
| fallbackSeconds(s,896 次累计) | 0.000000000 [0.000000000, 0.000000000] | 0.000000000 [0.000000000, 0.000000000] |
| pathSeconds(s,896 次累计) | 0.001062600 [0.001009100, 0.001439000] | 0.020530400 [0.019496400, 0.020720200] |
| baselineRecordSeconds(s,896 次累计) | 0.001533400 [0.001499200, 0.001760000] | 0.019465900 [0.018944100, 0.019927500] |
| jacobianSeconds(s,896 次累计) | 2.000029500 [1.933127300, 2.190857900] | 2.014426400 [1.971935900, 2.038742900] |
| solveCpuSeconds(s,896 次累计) | 6.015625000 [5.906250000, 6.375000000] | 5.984375000 [5.906250000, 6.078125000] |
| solveSeconds(s,896 次累计) | 6.140445900 [5.900561100, 6.564618800] | 6.120731700 [5.910387000, 6.166865500] |
### 配对差值
| 组 | 原计算 − replay 三阶段 ms | target 完整路径净节省 ms | 新增 metadata 采集 ms | Jacobian Δ s | 积分 CPU Δ s | 积分 wall Δ s |
|---|---:|---:|---:|---:|---:|---:|
| 1 | -17.969 | -18.346 | 17.479 | -0.183844 | -0.421875 | -0.488570 |
| 2 | -18.304 | -18.628 | 17.411 | -0.028094 | -0.125000 | -0.230059 |
| 3 | -19.224 | -19.501 | 18.387 | -0.004351 | 0.015625 | -0.092946 |
| 4 | -19.338 | -19.633 | 18.403 | 0.102324 | 0.093750 | 0.194642 |
| 5 | -19.425 | -19.711 | 17.790 | 0.103102 | 0.078125 | 0.266304 |
| 6 | -18.114 | -18.404 | 18.217 | -0.013517 | -0.343750 | -0.384508 |
| 7 | -19.176 | -19.492 | 17.613 | 0.037623 | 0.125000 | 0.096160 |
净节省为正表示节省,Δ = skip − control。
- 原计算成本 − replay 三阶段成本:配对中位数 **-19.176 ms / 896 次**。
- target 完整路径净节省:配对中位数 **-19.492 ms / 896 次**;范围 [-19.711, -18.346] ms。
- 此外 baseline metadata 采集增加:配对中位数 **17.790 ms**。
- Jacobian callback 配对 Δ:中位数 -0.004351 s,范围 [-0.183844, 0.103102] s。
- 积分 wall 配对 Δ:中位数 -0.092946 s,范围 [-0.488570, 0.266304] s。
全局时间受调频、调度和系统负载影响,不能把某轮 Jacobian/积分变快归因于这个 operation。目标路径在全部配对中均更慢,已经足以否定当前实现的局部净收益;本轮不预测整个 local probe 的最终加速比例。
## 成本解释及下一阶段条件
原 operation 在现有 Jacobian memo 的只读复用环境下已经很便宜;当前严格 replay 仍要初始化/复制 156,816 B 的 overlay(含完整 memo),解释事件并构造 write set。运行所需有效 metadata 为 2,224–23,344 B,Plan 固定预留 90,224 B,patch 预留 16,384 B。这些是当前隔离实现的实际成本,不是机制理论上的下限。
因此,本轮证明了目标路径可以安全真实跳过,但没有证明性能优化成立。下一步应先针对事务存储和 metadata 表达做最小化,再重复同样的正确性与交替性能验收;不因 position16 正确就直接扩大到 position52/整个 R475/全部 reuse interval。
## 复现与证据位置
运行目录:`test/r288-real-skip-20260917/`。依赖上一轮生成的 local-probe worker、context-access worker 和 shadow-certified 源码;工具链复用原实验配置,不安装依赖。
```powershell
.venv-win/Scripts/python.exe tests/manual/real_skip_experiment.py prepare --audit
.venv-win/Scripts/python.exe tests/manual/real_skip_experiment.py prepare --audit --skip
.venv-win/Scripts/python.exe tests/manual/real_skip_experiment.py run --audit
.venv-win/Scripts/python.exe tests/manual/real_skip_experiment.py run --audit --skip
.venv-win/Scripts/python.exe tests/manual/real_skip_experiment.py run --audit --skip --label audit-forced-reject --force 128
.venv-win/Scripts/python.exe tests/manual/real_skip_experiment.py prepare
.venv-win/Scripts/python.exe tests/manual/real_skip_experiment.py prepare --skip
.venv-win/Scripts/python.exe tests/manual/real_skip_experiment.py benchmark --pairs 7
.venv-win/Scripts/python.exe tests/manual/analyze_real_skip.py
```
- `audit-{control,skip}-run/validation.json`:正确性、独立执行计数、完整求解器计数及文件指纹。
- `audit-forced-reject/validation.json`:真实回退及 rollback 检查。
- `byte-comparison.json`:全部 Jacobian/t/y、states、outputs、events 与原始基线的逐字节比较。
- `audit-control-run/audit.bin`:所有 group 和目标 operation 的实际执行对照出口。
- `performance.json`:14 次按实际顺序记录的原始数据;各轮目录保留独立日志和结果。
- `performance-summary.json`:中位数、min/max 和逐对差值。
- `{audit,perf}-{control,skip}/build.json`:源文件哈希、guard 一致性和构建记录。
+212
View File
@@ -0,0 +1,212 @@
# R288 / position16:typed semantic replay 成本下限实验
## 三个问题的答案
1. 去掉完整 overlay 与通用事件解释器后,三阶段 replay 的每轮均值中位数为 **7.109 µs/次**,各轮范围 **4.082–13.011 µs/次**。包含调度、计时及计数的完整目标路径中位数为 **7.575 µs/次**。这是本实现、工具链和机器的实测结果,不是理论最低成本。
2. 同批 A 的原 operation 为 **1.775 µs/次**;专用 replay **仍高于原计算**。与历史 1.143 µs 不直接跨批比较。
3. 计入 baseline 捕获新增成本,配对完整机制净节省中位数为 **-20.415 ms / 896 次**;含完整路径计时/统计开销的口径为 **-21.082 ms**。**当前实现对 R288 没有净收益;这种低成本 operation 应直接原计算。**
## 范围与事务语义
所有实验仅针对 group6 / R288 / position16,在独立 worker 中、原 whole-context guard 失败之后启用。没有扩展 position52、R475 或其他 interval,没有接入生产默认路径。已有工作区改动不属于本实验的修改范围。
- `fp_validate` 只读当前真实 probe;保留输入位比较、有限值、FP/errno、observer、count/capacity、memo owner/recording、全部 active entry 绑定、pipe branch 等保护。
- 两次 PT 查询依次按当前有序 entries 验证 miss;第二次查询还显式检查第一个 pending entry 的虚拟匹配。append 位置由当前 count 决定,不使用 baseline slot。
- memo key 的 hash 在 baseline 捕获时计算;probe 仍按相同容量、同一 bounded linear-probe 顺序查找,并逐位比较完整 key 和 value。没有复制 memo,也没有绕过 memo 验证。
- 只支持已验证的两个固定 schema:67 次近零流量无查询、829 次 PT miss → PT miss。不支持的路径直接 reject。
- 这两个 schema 中,物性计算消费的条目均是本 operation 新建的条目。初始化、字段写入、valid 测试和读取之间的条件由固定 schema 保证,并在正确性版对每个 baseline 的所有原始访问逐条证明;不是把这些 guard 删除。
- `fp_prepare` 最多构造两个 pending property entries、一个 pipe[0]、新 count、q[45] 和 density/pipe memo reuse 增量。所有真实写入仅在 `fp_commit` 发生。未被写入的 probe 数据保持原值。
专用版是固定 R288 schema 的 typed 原型,不是通用生产 codegen。静态 schema 的核对表从全轨迹真实 generic 记录自动生成;原 native 源文件受既有 SHA-256 约束,未知源代码变化会停止构建。
## Metadata 与临时存储
| 项目 | generic | specialized |
|---|---:|---:|
| 每次 baseline 有效 metadata | 2,224–23,344 B | 240 B |
| 持久 metadata 固定预留 | 90,224 B | 240 B |
| 捕获临时区 | 通用事件记录器 | 96 B |
| context/memo/pipe 完整 overlay | 156,816 B | 0 B |
| pending patch | 16,384 B write-set,另有 overlay | 456 B,总计且不重复计算 |
- **静态可确定**:medium 常量、字段布局、读取/写入顺序、valid 位演变、最多两个追加条目、memo 类型、pipe[0]、q[45]。
- **每个 baseline 动态捕获**:输入、输出、U/D 物性字段、pipe memo key/value、三个 memo hash、入口 count、owner/Jacobian 生命周期及 FP/errno 条件,共 240 B。
- **probe 才解析**:当前有序 entries、query miss、当前 count/capacity、memo 实际位置和值、owner/observer/pipe 条件;验证成功后构造 456 B pending。
## 正确性验收
C 验收版:同一次 baseline 真实执行同时生成 generic 事件与小型 metadata;核对 896 份完整访问 schema、896 次 generic/typed accept/reject 和最终 patch,包括逻辑 slot 映射。
D 验收版:使用性能版相同的最小 native 捕获 hooks,逐位核对先前真实 generic baseline 记录(跨进程 memo 地址转换为 owner 绑定身份),然后重复全部求解验收。该离线记录只用于 audit;性能 worker 不读取这些记录。
| 检查 | 结果 |
|---|---:|
| Jacobian 全元素与 t/y | 896/896 逐字节一致,15,611,904 个矩阵元素 |
| evaluator 返回状态、dy/w 和完整 context 出口 | 25,088/25,088 一致 |
| 目标 operation 入口/出口、property count/有序字段/valid、pipe cache | 全部一致 |
| memo entries、只读生命周期、owner/kernel 绑定及计数 | 全部一致 |
| states/outputs/events、最终状态、warning、FP/errno | 全部一致 |
| 正常目标 attempts / skip / reject / 原执行 | 896 / 896 / 0 / 0 |
| 负例 guard 判定及无写入检查 | C、D 各 20 项通过 |
| 晚期 forced reject | C、D 各 7 次,无污染;889 skip、7 原执行 |
| count 差异 / relocation / append | 638 / 571 / 1,658 |
负例包含 observer、无容量/第二次追加不足、pipe hit、memo recording/miss/value、输入改变、晚期非有限输出、过期生命周期、FP flags/rounding、entry owner、U/D 查询转 hit,以及已拒绝 metadata 的 consumed/valid/已有条目更新/未知副作用标记。后四项是错误记录传播检查;逐字段读写与 valid 演变的实质检查来自 896 份完整访问 contract 对照。
| accepted | rejected | Newton iterations | convergence failures | nfev | njev | nlu | solverStarts | stateTransitions |
|---:|---:|---:|---:|---:|---:|---:|---:|---:|
|10840|918|19371|798|44467|896|3106|4|1|
全部使用位比较和逐字节比较,没有更改容差。性能版二进制符号检查确认无 generic interpreter、完整 overlay 或 audit oracle。
## Generic 成本归因
单独归因 worker 的总 replay 阶段为 42.544 µs/次;该数值含细粒度插桩,不与历史 22.521 µs 强行逐项相加。空 QPC bracket 均值 0.01622 µs,细粒度嵌套计时会额外放大短事件成本。
以下分类均为每个目标 probe 的平均原始计时;父项含子项,明确标记重叠,不能相加当作净成本。
| 分类 | µs/次 | 口径 |
|---|---:|---|
| baseline 原执行 + metadata 捕获 | 31.7568 | 新增成本由后面的同批 A/B 配对得出 |
| PT first-match / miss | 0.2964 | 真实顺序扫描;包含一组自身计时 |
| consumed-field 读取事件 | 5.3567 | 含 metadata 分派和嵌套字段比较 |
| 其中:字段值/绑定比较 | 1.6404 | 是上一行的子项 |
| valid 测试事件 | 0.1645 | 含事件分派 |
| memo 验证 | 0.3146 | 完整 key/value 与 bounded lookup;SCALAR 事件的子项 |
| 完整 overlay 初始化/复制/绑定 | 20.8996 | 包含 context、memo、全部 pipe |
| logical slot relocation | 0.4641 | 包含映射冲突检查;属于 ALLOCATE/QUERY 子项 |
| pending entry 字段写入 | 0.9435 | 原始 generic 实测,含自身计时 |
| pending valid 更新 | 0.2103 | 原始 generic 实测,含自身计时 |
| 追加与 count 更新 | 5.3371 | 原始 generic 实测,含自身计时 |
| pipe patch 写入 | 0.3973 | 原始 generic 实测,含自身计时 |
| write-set 构造 | 0.8175 | 原始 generic 实测,含自身计时 |
| commit | 0.5316 | 原始 generic 实测,含自身计时 |
| 公共 guard(映射初始化之前) | 3.9068 | 细分项;已经包含在对应父项内 |
| overlay 清零/poison 初始化 | 7.5767 | 细分项;已经包含在对应父项内 |
| property context/entries 复制 | 0.1152 | 细分项;已经包含在对应父项内 |
| 全部 pipe 复制 | 0.2791 | 细分项;已经包含在对应父项内 |
| memo header/entries 复制 | 12.2629 | 细分项;已经包含在对应父项内 |
| overlay 指针绑定 | 0.2096 | 细分项;已经包含在对应父项内 |
| 仅 event metadata 遍历/类型分派微基准 | 0.1790 | 每份真实 plan 重复 100 次;不做 guard/write,不能当作真实解释器的独立可加项 |
| 空计时 bracket | 0.01622 | 每组含首尾 QPC;累计统计另有开销,最终以低扰动 A/B/C 为准 |
通用解释器的实际成本分布在 metadata 访问、字段比较、slot 映射、写入分派和循环控制中,没有一个能独立相减的精确“dispatch 时间”。归因版保留原始嵌套数据,最终性能版移除这些细粒度计时。
## 低扰动 A/B/C 性能
A=原 operation,B=原 generic replay,C=typed/minimal replay。各预热一次,9 组按 ABC → BCA → CAB 循环,全部串行。无 Reference、完整 context 比较或详细访问日志;保留机制所需 metadata、guard、patch 和少量累计计时。每轮 B/C 均 skip 896、reject 0、原执行 0,并核对采样结果和全部指定计数。
### 中位数 [min, max]
| 项目 | A | B | C |
|---|---:|---:|---:|
| original (µs/次) | 1.775 [1.574, 2.779] | 0.000 [0.000, 0.000] | 0.000 [0.000, 0.000] |
| validation (µs/次) | 0.000 [0.000, 0.000] | 15.540 [11.031, 50.369] | 5.615 [3.718, 10.492] |
| patch (µs/次) | 0.000 [0.000, 0.000] | 36.240 [24.357, 60.464] | 0.308 [0.237, 1.084] |
| commit (µs/次) | 0.000 [0.000, 0.000] | 0.672 [0.548, 1.210] | 0.144 [0.128, 7.584] |
| stages (µs/次) | 0.000 [0.000, 0.000] | 50.367 [35.999, 92.380] | 7.109 [4.082, 13.011] |
| path (µs/次) | 1.829 [1.650, 2.836] | 51.429 [36.540, 93.061] | 7.575 [4.366, 13.597] |
| outsideStages (µs/次) | 0.053 [0.048, 0.151] | 0.681 [0.541, 1.062] | 0.377 [0.284, 1.001] |
| baseline (µs/次) | 3.812 [2.282, 8.872] | 46.228 [31.371, 67.965] | 22.120 [14.653, 44.772] |
| jacobian (s) | 4.657 [3.108, 5.589] | 4.564 [3.524, 5.353] | 4.275 [2.912, 4.820] |
| cpu (s) | 8.531 [7.469, 8.797] | 8.641 [7.969, 9.062] | 8.672 [7.562, 8.906] |
| wall (s) | 13.458 [9.312, 16.059] | 13.025 [10.581, 15.476] | 13.093 [8.982, 14.628] |
replay 三阶段先在每一轮内求和再取中位数,因此不必等于三个分项中位数之和。baseline 项包括该次 baseline operation 本身;捕获新增成本应减去 A 的 baseline 项。B 的 patch 项含全 overlay copy + write-set;C 的 patch 项仅小型 pending 构造。outsideStages 是完整路径减去被包围的阶段时间,包含外层计时、统计、调度与元数据 bookkeeping;它不是纯计时器成本。无自然 reject,fallback 总时间为 0,不代表单次 fallback 免费。
### 每轮原始数据(实际执行顺序)
| 轮 / 模式 | 原 op µs | validation µs | patch µs | commit µs | replay 三阶段 µs | 完整路径 µs | baseline µs | Jacobian s | CPU s | wall s |
|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
| round-1 / A | 2.779 | 0.000 | 0.000 | 0.000 | 0.000 | 2.836 | 3.812 | 5.589474 | 8.765625 | 16.059343 |
| round-1 / B | 0.000 | 11.932 | 30.241 | 0.619 | 42.792 | 43.494 | 49.403 | 4.178009 | 8.640625 | 12.130358 |
| round-1 / C | 0.000 | 4.836 | 0.301 | 0.569 | 5.706 | 6.083 | 17.992 | 4.274571 | 8.687500 | 12.886425 |
| round-2 / B | 0.000 | 15.540 | 60.464 | 0.639 | 76.643 | 77.297 | 67.965 | 4.720768 | 8.812500 | 13.688767 |
| round-2 / C | 0.000 | 6.669 | 0.319 | 0.218 | 7.206 | 7.575 | 25.742 | 4.820219 | 8.625000 | 13.576249 |
| round-2 / A | 1.745 | 0.000 | 0.000 | 0.000 | 0.000 | 1.794 | 2.880 | 5.162364 | 8.468750 | 14.128570 |
| round-3 / C | 0.000 | 7.646 | 0.291 | 0.141 | 8.079 | 9.080 | 21.240 | 4.792801 | 8.734375 | 14.628191 |
| round-3 / A | 1.682 | 0.000 | 0.000 | 0.000 | 0.000 | 1.730 | 2.282 | 3.242320 | 7.531250 | 9.505273 |
| round-3 / B | 0.000 | 13.321 | 36.411 | 0.635 | 50.367 | 51.429 | 48.727 | 4.563694 | 8.609375 | 13.024813 |
| round-4 / A | 1.574 | 0.000 | 0.000 | 0.000 | 0.000 | 1.650 | 4.330 | 5.109714 | 8.671875 | 14.935839 |
| round-4 / B | 0.000 | 20.122 | 36.240 | 0.673 | 57.035 | 57.856 | 44.720 | 5.159674 | 9.046875 | 15.093424 |
| round-4 / C | 0.000 | 5.615 | 1.084 | 0.144 | 6.843 | 7.334 | 18.844 | 4.173146 | 8.437500 | 12.573423 |
| round-5 / B | 0.000 | 11.094 | 24.357 | 0.548 | 35.999 | 36.540 | 31.371 | 3.523633 | 7.968750 | 10.581341 |
| round-5 / C | 0.000 | 10.492 | 0.325 | 0.178 | 10.995 | 11.341 | 23.241 | 4.240836 | 8.671875 | 13.092565 |
| round-5 / A | 2.121 | 0.000 | 0.000 | 0.000 | 0.000 | 2.174 | 4.388 | 4.796397 | 8.531250 | 13.405962 |
| round-6 / C | 0.000 | 5.266 | 0.294 | 0.133 | 5.694 | 6.061 | 22.120 | 3.977201 | 8.203125 | 11.476165 |
| round-6 / A | 2.652 | 0.000 | 0.000 | 0.000 | 0.000 | 2.702 | 3.862 | 4.054356 | 8.125000 | 12.824942 |
| round-6 / B | 0.000 | 19.544 | 28.453 | 1.173 | 49.170 | 49.836 | 40.725 | 3.719785 | 8.500000 | 11.533035 |
| round-7 / A | 1.775 | 0.000 | 0.000 | 0.000 | 0.000 | 1.829 | 2.834 | 4.657222 | 8.578125 | 14.247693 |
| round-7 / B | 0.000 | 16.876 | 30.680 | 1.210 | 48.766 | 49.492 | 46.228 | 5.353380 | 8.687500 | 15.475863 |
| round-7 / C | 0.000 | 5.111 | 0.317 | 7.584 | 13.011 | 13.597 | 44.772 | 4.426264 | 8.906250 | 13.147138 |
| round-8 / B | 0.000 | 50.369 | 41.295 | 0.716 | 92.380 | 93.061 | 47.970 | 5.143477 | 9.062500 | 14.801028 |
| round-8 / C | 0.000 | 3.718 | 0.237 | 0.128 | 4.082 | 4.366 | 14.653 | 2.911920 | 7.562500 | 8.981533 |
| round-8 / A | 1.624 | 0.000 | 0.000 | 0.000 | 0.000 | 1.674 | 2.293 | 3.108083 | 7.468750 | 9.311726 |
| round-9 / C | 0.000 | 6.660 | 0.308 | 0.140 | 7.109 | 8.006 | 26.959 | 4.731763 | 8.687500 | 13.629807 |
| round-9 / A | 2.412 | 0.000 | 0.000 | 0.000 | 0.000 | 2.563 | 8.872 | 4.641674 | 8.796875 | 13.458097 |
| round-9 / B | 0.000 | 11.031 | 44.264 | 0.672 | 55.967 | 56.590 | 35.306 | 3.917557 | 8.140625 | 11.486951 |
### 两种收益口径
- probe 局部净节省 = A 原 operation − validation − patch − commit。
- 完整机制净节省 = 896 × probe 局部净节省 − (baseline 捕获总耗时 − A baseline 原执行耗时)。
- 再给出包含外层调度、计时与统计的保守口径:A 完整目标路径 − replay 完整目标路径 − baseline 新增成本。
- 正数为节省,负数为额外成本。时间均使用同组配对,不拿历史 1.143 µs 作分母。
| 轮 / 模式 | probe 净节省 µs/次 | baseline 新增 µs/次 | 完整机制净节省 ms/896 次 | 含外层开销净节省 ms | Jacobian Δ s | CPU Δ s | wall Δ s |
|---|---:|---:|---:|---:|---:|---:|---:|
| 1 / B | -40.013 | 45.591 | -76.701 | -77.279 | -1.411465 | -0.125000 | -3.928985 |
| 1 / C | -2.927 | 14.180 | -15.328 | -15.614 | -1.314904 | -0.078125 | -3.172919 |
| 2 / B | -74.899 | 65.085 | -125.425 | -125.966 | -0.441596 | 0.343750 | -0.439802 |
| 2 / C | -5.461 | 22.862 | -25.378 | -25.664 | -0.342145 | 0.156250 | -0.552320 |
| 3 / B | -48.684 | 46.446 | -85.237 | -86.146 | 1.321374 | 1.078125 | 3.519541 |
| 3 / C | -6.396 | 18.959 | -22.718 | -23.572 | 1.550480 | 1.203125 | 5.122919 |
| 4 / B | -55.461 | 40.390 | -85.882 | -86.551 | 0.049960 | 0.375000 | 0.157585 |
| 4 / C | -5.269 | 14.514 | -17.726 | -18.098 | -0.936568 | -0.234375 | -2.362415 |
| 5 / B | -33.879 | 26.983 | -54.532 | -54.969 | -1.272764 | -0.562500 | -2.824622 |
| 5 / C | -8.874 | 18.853 | -24.844 | -25.106 | -0.555561 | 0.140625 | -0.313398 |
| 6 / B | -46.518 | 36.863 | -74.709 | -75.261 | -0.334571 | 0.375000 | -1.291907 |
| 6 / C | -3.041 | 18.258 | -19.084 | -19.368 | -0.077155 | 0.078125 | -1.348777 |
| 7 / B | -46.991 | 43.394 | -80.985 | -81.587 | 0.696158 | 0.109375 | 1.228170 |
| 7 / C | -11.236 | 41.938 | -47.644 | -48.121 | -0.230959 | 0.328125 | -1.100554 |
| 8 / B | -90.755 | 45.677 | -122.243 | -122.809 | 2.035395 | 1.593750 | 5.489302 |
| 8 / C | -2.458 | 12.360 | -13.277 | -13.487 | -0.196162 | 0.093750 | -0.330193 |
| 9 / B | -53.555 | 26.434 | -71.670 | -72.093 | -0.724117 | -0.656250 | -1.971146 |
| 9 / C | -4.697 | 18.087 | -20.415 | -21.082 | 0.090088 | -0.109375 | 0.171710 |
| 配对统计:中位数 [min, max] | B | C |
|---|---:|---:|
| probeSavingUs | -48.684375 [-90.755246, -33.878683] | -5.268973 [-11.236272, -2.457924] |
| metadataIncrementUs | 43.393862 [26.434040, 65.084598] | 18.257701 [12.360156, 41.938281] |
| mechanismSavingMs | -80.985100 [-125.425100, -54.532400] | -20.414600 [-47.644400, -13.277000] |
| completePathSavingMs | -81.587000 [-125.966400, -54.969100] | -21.082500 [-48.120900, -13.486700] |
| jacobianDelta | -0.334571 [-1.411465, 2.035395] | -0.230959 [-1.314904, 1.550480] |
| cpuDelta | 0.343750 [-0.656250, 1.593750] | 0.093750 [-0.234375, 1.203125] |
| wallDelta | -0.439802 [-3.928985, 5.489302] | -0.552320 [-3.172919, 5.122919] |
所有 operation 阶段使用 QPC 墙钟计时,包含调度长尾;积分 CPU 时间单独来自求解器统计。系统负载和频率使本批数据存在较大波动(包括 commit 分项的极端值),因此报告同时保留中位数、min/max 和每组配对结果,不将归因插桩或调度延迟声称为精确的算法 CPU 成本。9 组局部与完整机制净收益均为负;不从总 Jacobian/积分墙钟的正负波动推断整个 local probe 的收益,也不声称已证明理论成本下限。计时未做不可靠的逐事件扣减。
## 停止条件与交付
当前实现对 R288 没有净收益;这种低成本 operation 应直接原计算。
本轮到此停止,不继续为 R288 寻找更多微优化,也不扩展到 position52。保留生产原计算路径和原 whole-context guard。
代码入口:`tests/manual/specialized_replay_experiment.py`。核心为 `typed_replay_core.inc`、`typed_replay_runtime.inc`;`typed_contract_check.inc` 与 `typed_oracle.inc` 只用于 audit。报告生成器为 `analyze_typed_replay.py`。
复现顺序(项目根目录,使用既有工具链,不安装依赖):
```powershell
.venv-win/Scripts/python.exe tests/manual/specialized_replay_experiment.py prepare --mode P
.venv-win/Scripts/python.exe tests/manual/specialized_replay_experiment.py run --mode P
.venv-win/Scripts/python.exe tests/manual/specialized_replay_experiment.py prepare --mode C --audit
.venv-win/Scripts/python.exe tests/manual/specialized_replay_experiment.py run --mode C --audit
.venv-win/Scripts/python.exe tests/manual/specialized_replay_experiment.py run --mode C --audit --label forced-reject --force 128
.venv-win/Scripts/python.exe tests/manual/specialized_replay_experiment.py prepare --mode D --audit
.venv-win/Scripts/python.exe tests/manual/specialized_replay_experiment.py run --mode D --audit
.venv-win/Scripts/python.exe tests/manual/specialized_replay_experiment.py run --mode D --audit --label forced-reject --force 128
.venv-win/Scripts/python.exe tests/manual/specialized_replay_experiment.py prepare --mode A
.venv-win/Scripts/python.exe tests/manual/specialized_replay_experiment.py prepare --mode B
.venv-win/Scripts/python.exe tests/manual/specialized_replay_experiment.py prepare --mode C
.venv-win/Scripts/python.exe tests/manual/specialized_replay_experiment.py benchmark --rounds 9
.venv-win/Scripts/python.exe tests/manual/analyze_typed_replay.py
```
证据目录:`test/r288-typed-replay-20260917/`。`performance.json` 保存 27 次原始记录;`performance-summary.json` 保存统计和逐组净收益;`byte-comparison.json` 保存逐字节结果;C/D 的 `validation.json` 和 `typed-summary.json` 保存全部正确性计数。P 的 `attribution.json` 保存原始 tick、调用次数和计时器测量。
+28
View File
@@ -0,0 +1,28 @@
#ifndef R288_REAL_SKIP_H
#define R288_REAL_SKIP_H
#include "local_probe.h"
#include "context_shadow_replay.h"
#ifndef RR_SKIP
#define RR_SKIP 0
#endif
#ifndef RR_AUDIT
#define RR_AUDIT 0
#endif
extern int rr_original_scope,rr_replaying;
extern unsigned long long rr_native_calls;
void rr_start(void);
void rr_finish(void);
void rr_jacobian(void);
void rr_record_begin(NativePropertyCache*,NativePipeCache*,const double*);
void rr_record_end(double);
double rr_execute(NativePropertyCache*,NativePipeCache*,const double*,SROperation);
#if RR_AUDIT
void rr_eval_exit(double,const double*,const double*,const double*,int,NativePropertyCache*,NativePipeCache*,ModelJacobianWorkspace*);
void rr_matrix(double,const double*,const double*);
#else
#define rr_eval_exit(...) ((void)0)
#define rr_matrix(...) ((void)0)
#endif
double rrrec_native_pipe_flow_cached_context(NativePropertyCache*,NativePipeCache*,const NativeMedium*,double,double,double,double,double,double,int);
double rrrec_native_temperature_ph_context(NativePropertyCache*,const NativeMedium*,double,double);
#endif
+180
View File
@@ -0,0 +1,180 @@
"""Only group 6 / region 288 / position 16, independent real-skip workers."""
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
import argparse,hashlib,json,os,re,shutil,subprocess,time
import diagnose_context_shadow as shadow
ROOT=shadow.ROOT;HERE=Path(__file__).parent
BASE=ROOT/'test/local-probe-20260917/worker'
ACCESS=ROOT/'test/context-access-20260917/worker'
OUT=ROOT/'test/r288-real-skip-20260917'
ex=shadow.access.dx.ex
replace=shadow.replace
def write(path,obj):ex.write(path,obj)
def fn(source,name):
if name=='ax_test':
start=source.index('unsigned ax_test(');end=source.index('\nvoid ax_scalar',start)
return source[start:end]
if name=='event':
start=source.index('static Event *event(');end=source.index('\nstatic int state_pointer',start)
return source[start:end]
return shadow.access.dx.function(source,name)
def core(audit):
source=(HERE/'context_shadow_replay.c').read_text(encoding='utf-8')
certified=json.loads((shadow.OUT/'worker/build.json').read_text(encoding='utf-8'))['instrumentedHashes']['context_shadow_replay.c']
assert hashlib.sha256(source.encode()).hexdigest()==certified, 'Use the shadow-certified source unchanged.'
prefix=source[:source.index('typedef struct {\n unsigned long long total')]
prefix=prefix.replace('static void save_environment','static __attribute__((unused)) void save_environment').replace('static void restore_environment','static __attribute__((unused)) void restore_environment')
globals='''
static Plan plan;
static int phase,position=16,group,configured=16;
static unsigned long long jac;
static double sim_time,op_inputs[4];
static NativePropertyCache *bound;
static NativePipeCache *bound_pipes;
static SRContext overlay;
static size_t probe_entry_count;
static void fatal(const char *s){fprintf(stderr,"real-skip fatal: %s\\n",s);abort();}
static void initialize(void){}
void sr_native_enter(const char *name){(void)name;if(phase==CANDIDATE)fatal("Candidate called physics");}
'''
names=['locate','event','state_pointer','floating_field','ax_access','sr_or','ax_bind','ax_begin','ax_query','ax_match','ax_new','ax_test','ax_scalar','path_of_plan','ax_end','ax_result','from_live','same_medium_key','first_match','map_slot','scalar_lookup','state_read_equal','replay_overlay']
parts=[prefix,globals]+[fn(source,n) for n in names]
result='\n'.join(parts).replace('(size_t)slot<entry.context.count','(size_t)slot<probe_entry_count')
assert fn(result,'replay_overlay')==fn(source,'replay_overlay').replace('(size_t)slot<entry.context.count','(size_t)slot<probe_entry_count')
if audit:
start=source.index('typedef struct {const char *name;size_t offset,size;} Field;')
end=source.index('static void dump_blob(',start)
comparison=source[start:end]
a,b,e=ex.function_span(comparison,'compare_frame');comparison=comparison[:a]+comparison[e:]
result+='\n'+comparison
return result
def model_source(source,plan):
a,b,e=ex.function_span(source,'model_eval_local_internal');body=source[a:e]
op=plan['code'][16][0]
assert body.count(op)==2 and plan['regions'][288]==[16,17]
expr=op.split('=',1)[1].rstrip(';')
for old,new in [('p[3]','x[3]'),('g[4].p','x[1]'),('g[4].T','x[0]'),('h[64]','x[2]')]:expr=expr.replace(old,new)
helper=f'static double rr_original16(NativePropertyCache *properties,NativePipeCache *pipe_cache,const double *x){{return {expr};}}\n'
ins='(double[]){g[4].T,g[4].p,h[64],p[3]}'
baseline='rr_record_begin(properties,pipe_cache,'+ins+');\n#if RR_SKIP\n'+op.replace('native_pipe_flow_cached_context','rrrec_native_pipe_flow_cached_context').replace('native_temperature_ph_context','rrrec_native_temperature_ph_context')+'\n#else\n'+op+'\n#endif\nrr_record_end(q[45]);'
body=body.replace(op,baseline,1)
# The case is reachable only after the original whole-context guard failed.
start=body.index('case 16:{');end=body.index('break;}',start)
case=body[start:end]
case=replace(case,op,'if(lp_color==6 && region==288){q[45]=rr_execute(properties,pipe_cache,'+ins+',rr_original16);}else{'+op+'}')
body=body[:start]+case+body[end:]
# Audit every Jacobian baseline/probe's exit, including all other groups.
body=replace(body,'return 1;}','rr_eval_exit(t,y,dy,w,1,properties,pipe_cache,jacobian);return 1;}')
return source[:a]+helper+body+source[e:]
def prepare(audit,skip):
if not audit:
gate=json.loads((OUT/'audit-skip-run/validation.json').read_text(encoding='utf-8'))
assert gate['exact'] and gate['counters']['realSkips']==896
OUT.mkdir(exist_ok=True)
label=('audit' if audit else 'perf')+('-skip' if skip else '-control')
work=OUT/label;work.mkdir(exist_ok=True)
sources={p.name:p.read_text(encoding='utf-8') for p in BASE.glob('*.c')}
original_hashes={k:hashlib.sha256(v.encode()).hexdigest() for k,v in sources.items()}
plan=json.loads((ROOT/'test/context-fallback-20260917/plan.json').read_text(encoding='utf-8'))
sources['model.c']=model_source(sources['model.c'],plan)
sources['common.c']=replace(sources['common.c'],'lp_start();','lp_start();rr_start();')
sources['common.c']=replace(sources['common.c'],'lp_finish();','lp_finish();rr_finish();')
sources['cvode_solver.c']=replace(sources['cvode_solver.c'],'{lp_color=-1;uint64_t start=lp_tick();','{lp_color=-1;rr_jacobian();uint64_t start=lp_tick();')
sources['cvode_solver.c']=replace(sources['cvode_solver.c'],'if(!result)lp_matrix(t,N_VGetArrayPointer(y),SUNDenseMatrix_Data(matrix));','if(!result){lp_matrix(t,N_VGetArrayPointer(y),SUNDenseMatrix_Data(matrix));rr_matrix(t,N_VGetArrayPointer(y),SUNDenseMatrix_Data(matrix));}')
sources['pipe.c']=shadow.access.change_function(sources['pipe.c'],'native_pipe_flow_cached_context',lambda body:'if(rr_replaying)abort();if(rr_original_scope)rr_native_calls++;\n'+body)
# Original physics files are NOT access-instrumented. A separate namespace
# is called only to record the baseline target operation's required IR.
recording={name:(ACCESS/name).read_text(encoding='utf-8') for name in ['properties.c','pipe.c']}
names=set()
for code in recording.values():
names.update(re.findall(r'(?m)^(?:static\s+|NATIVE_COMPONENT_INTERNAL\s+)?(?:int|double|void|NativePropertyState\s*\*|NativeGas|NativeJacobianScalarEntry\s*\*)\s*(\w+)\([^;{}]*\)\s*\{',code))
names.add('helium_medium')
aliases='\n'.join('#define '+n+' rrrec_'+n for n in sorted(names))+'\n'
for name,code in recording.items():sources['record_'+name]=aliases+code
sources['real_skip_runtime.c']=(HERE/'real_skip_runtime.c').read_text(encoding='utf-8')
for name in ['model.h','local_probe.h']:shutil.copyfile(BASE/name,work/name)
shutil.copyfile(ACCESS/'context_fallback_diag.h',work/'context_fallback_diag.h')
shutil.copyfile(HERE/'context_shadow_replay.h',work/'context_shadow_replay.h')
shutil.copyfile(HERE/'real_skip.h',work/'real_skip.h')
header=(ACCESS/'context_access_diag.h').read_text(encoding='utf-8').replace('#include "kernels.h"','#include "kernels.h"\n#include "context_shadow_replay.h"')
header=replace(header,'(x)|=(v); ax_access("write",&(x),sizeof(x),#x,__func__);','(x)|=(v); sr_or(&(x),(v),#x,__func__);')
(work/'context_access_diag.h').write_text(header,encoding='utf-8')
(work/'real_skip_core.inc').write_text(core(audit),encoding='utf-8',newline='\n')
cc,sun,_=ex.builder.toolchain();flags,libs,dlls,exe=ex.builder.platform_build_inputs(sun)
flags+=['-DLP_OBSERVE=0',f'-DRR_AUDIT={int(audit)}',f'-DRR_SKIP={int(skip)}'];started=time.perf_counter()
def compile_one(item):
name,code=item;path=work/name
prefix='#ifdef _WIN32\n#ifndef _WIN32_WINNT\n#define _WIN32_WINNT 0x0600\n#endif\n#endif\n#include "real_skip.h"\n#include <stdlib.h>\n'
if name.startswith('record_'):
code=code[len(aliases):];prefix=aliases+prefix
path.write_text(prefix+code,encoding='utf-8',newline='\n');obj=path.with_suffix('.o');log=[]
ex.builder._command([cc,*flags,'-I',str(work),'-I',str(ex.builder.NATIVE/'include'),'-I',str(sun/'include'),'-c',str(path),'-o',str(obj)],log=log,timeout=240)
return obj,log
with ThreadPoolExecutor(max_workers=4) as pool:objects=list(pool.map(compile_one,sources.items()))
log=[];ex.builder._command([cc,*flags,*[str(p) for p,_ in objects],*ex.builder.link_library_arguments(libs),'-lm','-o',str(work/exe)],log=log)
for dll in dlls:shutil.copyfile(dll,work/dll.name)
(work/'build.log').write_text('\n'.join(sum([v for _,v in objects],[])+log),encoding='utf-8')
assert sources['local_probe_support.c']==(BASE/'local_probe_support.c').read_text(encoding='utf-8')
write(work/'build.json',dict(audit=audit,skip=skip,seconds=time.perf_counter()-started,originalHashes=original_hashes,guardUnchanged=True,guardMatchesShadow=True,sourceHashes={p.name:hashlib.sha256(p.read_bytes()).hexdigest() for p in work.glob('*.c')},coreSha256=hashlib.sha256((work/'real_skip_core.inc').read_bytes()).hexdigest()))
print('BUILT',label,flush=True)
def run(audit,skip,label=None,force=0):
worker=('audit' if audit else 'perf')+('-skip' if skip else '-control')
label=label or worker+'-run';folder=OUT/label;folder.mkdir(exist_ok=True)
env=os.environ.copy();env['LOCAL_PROBE_MASK']='0x7ffffff'
if audit and skip:env['RR_EXPECTED']=str(OUT/'audit-control-run/audit.bin')
if force:env['RR_FORCE_REJECT_EVERY']=str(force)
args=[str(OUT/worker/'model.exe'),'--method','BDF','--start','0','--stop','10','--sample-step','.01','--max-step','1e30','--rtol','1e-8','--timeout','300','--sample-file',str(folder/'states.bin'),'--output-block-file',str(folder/'outputs.bin'),'--output',str(folder/'result.json')]
start=time.perf_counter()
with (folder/'stderr.log').open('wb') as f:p=subprocess.run(args,cwd=folder,env=env,stdout=subprocess.PIPE,stderr=f,timeout=330,creationflags=subprocess.CREATE_NO_WINDOW)
assert p.returncode==0,(label,p.returncode,(folder/'stderr.log').read_text()[-4000:])
r=json.loads((folder/'result.json').read_text());d=json.loads((folder/'probe.json').read_text());c=json.loads((folder/'real-skip.json').read_text())
ref=json.loads((BASE.parent/'all-run-0/measurement.json').read_text(encoding='utf-8'))
keys=['success','finalState','final','propertyWarnings','acceptedSteps','rejectedSteps','stateTransitions','solverStarts','nfev','njev','nlu']
assert all(r[k]==ref[k] for k in keys),[(k,r[k],ref[k]) for k in keys if r[k]!=ref[k]]
for key in ['newtonIterations','newtonConvergenceFailures','modelCalls','groups','contextCopiedBytes','contextComparedBytes']:assert d[key]==ref['diagnostic'][key],key
hashes={}
for name in ['states','outputs','events']+(['jacobians'] if audit else []):
with (folder/(name+'.bin')).open('rb') as f:hashes[name]=hashlib.file_digest(f,'sha256').hexdigest()
if name=='jacobians':
with (BASE.parent/'all-audit/jacobians.bin').open('rb') as f:assert hashes[name]==hashlib.file_digest(f,'sha256').hexdigest()
else:assert hashes[name]==ref[name+'Sha256'],name
expected_rejects=(896+force-1)//force if force else 0
assert c['targetVisits']==896 and c['referenceExecutions']==0 and c['mismatches']==0
assert c['candidateAttempts']==(896 if skip else 0)
assert c['realSkips']==(896-expected_rejects if skip else 0)
assert c['nativeOriginalExecutions']==(expected_rejects if skip else 896)
assert c['commits']==c['semanticSuccess']==c['realSkips']
assert c['fallbackExecutions']==c['rejects']==expected_rejects
if audit:assert c['evaluationAudits']==896*28 and c['targetEntryAudits']==c['targetExitAudits']==896
record=dict(label=label,audit=audit,skip=skip,force=force,exact=True,solveSeconds=r['solveSeconds'],solveCpuSeconds=r['solveCpuSeconds'],jacobianSeconds=d['jacobianSeconds'],processSeconds=time.perf_counter()-start,counters=c,hashes=hashes,solver={k:r[k] for k in ['acceptedSteps','rejectedSteps','nfev','njev','nlu','solverStarts','stateTransitions']},newtonIterations=d['newtonIterations'],newtonConvergenceFailures=d['newtonConvergenceFailures'])
write(folder/'validation.json',record)
print('RUN',label,'exact OK','skip',c['realSkips'],'original',c['nativeOriginalExecutions'],'target us',c['pathSeconds']/896*1e6,'Jac',d['jacobianSeconds'],flush=True)
return record
def benchmark(pairs):
run(False,False,'warm-control');run(False,True,'warm-skip')
results=[]
for i in range(pairs):
for skip in ([False,True] if i%2==0 else [True,False]):results.append(run(False,skip,f'pair-{i+1}-'+('skip' if skip else 'control')))
write(OUT/'performance.json',results)
if __name__=='__main__':
p=argparse.ArgumentParser();p.add_argument('action',choices=['prepare','run','benchmark']);p.add_argument('--audit',action='store_true');p.add_argument('--skip',action='store_true');p.add_argument('--label');p.add_argument('--force',type=int,default=0);p.add_argument('--pairs',type=int,default=7);a=p.parse_args()
if a.action=='prepare':prepare(a.audit,a.skip)
elif a.action=='run':run(a.audit,a.skip,a.label,a.force)
else:benchmark(a.pairs)
+223
View File
@@ -0,0 +1,223 @@
/* Standalone R288/position16 experiment. Never linked into production. */
#include "real_skip.h"
#include <windows.h>
#include "real_skip_core.inc"
int rr_original_scope,rr_replaying;
unsigned long long rr_native_calls;
static unsigned long long visits,attempts,successes,rejects,commits,fallbacks,reject_reason[NREASONS];
static unsigned long long eval_audits,entry_audits,exit_audits,rollback_checks;
static unsigned long long appends_total,relocations,count_different,paths[4];
static uint64_t frequency,path_ticks,original_ticks,validation_ticks,overlay_ticks,commit_ticks,fallback_ticks,baseline_ticks,baseline_start;
static size_t metadata_min=(size_t)-1,metadata_max;
static int force_every;
#if RR_AUDIT
/* Pointer identities are compared as owner bindings, never as cross-process addresses.
* Padding is excluded; every declared numerical field is copied byte-for-byte. */
typedef struct {
unsigned long long jacobian;
int color,stage,status,saved_errno,flags,rounding;
unsigned sse;
double t,output,y[NSTATES],dy[NSTATES],w[NOUTPUTS];
size_t count,capacity,memo_capacity;
int recording;
unsigned long evaluations[NATIVE_JACOBIAN_SCALAR_KINDS],reuses[NATIVE_JACOBIAN_SCALAR_KINDS];
NativePropertyState states[SR_STATES];
NativePipeCache pipes[SR_PIPES];
NativeJacobianGasMemo gases[MODEL_JACOBIAN_GAS_COUNT];
NativeJacobianGasStats gas_stats;
} Audit;
static FILE *audit_file,*matrix_file;
static Audit actual,expected;
static NativeJacobianScalarEntry baseline_entries[MODEL_JACOBIAN_SCALAR_COUNT],expected_entries[MODEL_JACOBIAN_SCALAR_COUNT];
static NativeJacobianScalars *memo_owner;
static SRContext before_reject,after_reject;
static void audit_fail(const char *where,const void *a,const void *b,size_t n){
size_t offset=0;while(offset<n && ((const unsigned char*)a)[offset]==((const unsigned char*)b)[offset])offset++;
FILE *f=fopen("first-mismatch.txt","wb");
if(f){fprintf(f,"Jacobian=%llu group=%d probe=%d position=%d stage=%d field=%s byte=%llu\n",jac,lp_color,lp_color,actual.stage==3?-1:16,actual.stage,where,(unsigned long long)offset);fclose(f);}
f=fopen("first-mismatch-context.bin","wb");
if(f){fwrite(a,1,n,f);fwrite(b,1,n,f);fwrite(&plan,1,sizeof(plan),f);fclose(f);}
fprintf(stderr,"Jacobian=%llu group=%d position=%d stage=%d %s byte=%llu\n",jac,lp_color,actual.stage==3?-1:16,actual.stage,where,(unsigned long long)offset);
fatal("bitwise mismatch");
}
static const char *audit_field(size_t off){
static char name[160];
#define AF(n) if(off>=offsetof(Audit,n)&&off<offsetof(Audit,n)+sizeof(actual.n)){snprintf(name,sizeof(name),#n "[%llu]",(unsigned long long)((off-offsetof(Audit,n))/8));return name;}
/* Explicit field decoding for the large context arrays. */
if(off>=offsetof(Audit,states)&&off<offsetof(Audit,pipes)){
size_t d=off-offsetof(Audit,states),slot=d/sizeof(NativePropertyState),local=d%sizeof(NativePropertyState);
for(size_t i=0;i<sizeof(state_fields)/sizeof(*state_fields);i++)if(local>=state_fields[i].offset && local<state_fields[i].offset+state_fields[i].size){snprintf(name,sizeof(name),"property[%llu].%s",(unsigned long long)slot,state_fields[i].name);return name;}
return "property_binding";
}
if(off>=offsetof(Audit,pipes)&&off<offsetof(Audit,gases)){
size_t d=off-offsetof(Audit,pipes),slot=d/sizeof(NativePipeCache),local=d%sizeof(NativePipeCache);
for(size_t i=0;i<sizeof(pipe_fields)/sizeof(*pipe_fields);i++)if(local>=pipe_fields[i].offset && local<pipe_fields[i].offset+pipe_fields[i].size){snprintf(name,sizeof(name),"pipe[%llu].%s",(unsigned long long)slot,pipe_fields[i].name);return name;}
}
AF(y) AF(dy) AF(w) AF(t) AF(output) AF(count) AF(capacity) AF(recording) AF(evaluations) AF(reuses) AF(gases) AF(gas_stats)
#undef AF
return "audit_header_or_memo_lifecycle";
}
static void audit_context(int stage,double t,const double *y,const double *dy,const double *w,int status,double output,NativePropertyCache *p,NativePipeCache *pipes,ModelJacobianWorkspace *workspace){
int saved_errno=errno;SREnvironment env;save_environment(&env);
memset(&actual,0,sizeof(actual));actual.jacobian=jac;actual.color=lp_color;actual.stage=stage;actual.status=status;
actual.saved_errno=saved_errno;actual.flags=fetestexcept(FE_ALL_EXCEPT);actual.rounding=env.rounding;actual.sse=env.sse;
actual.t=t;actual.output=output;
if(y)memcpy(actual.y,y,NSTATES*8);
if(dy)memcpy(actual.dy,dy,NSTATES*8);
if(w)memcpy(actual.w,w,NOUTPUTS*8);
if(p->count>SR_STATES || p->capacity!=SR_STATES || p->temperatures || !p->jacobian || p->jacobian->capacity!=MODEL_JACOBIAN_SCALAR_COUNT)fatal("audit context schema");
NativeJacobianScalars *m=p->jacobian;
if(stage==3 && lp_color<0)memo_owner=m;
else if(m!=memo_owner)fatal("memo owner lifetime");
if(m->recording!=(lp_color<0))fatal("memo recording lifetime");
if(workspace && (m!=&workspace->scalars || m->entries!=workspace->scalar_entries))fatal("workspace memo binding");
actual.count=p->count;actual.capacity=p->capacity;actual.memo_capacity=m->capacity;actual.recording=m->recording;
memcpy(actual.evaluations,m->evaluations,sizeof(actual.evaluations));memcpy(actual.reuses,m->reuses,sizeof(actual.reuses));
for(size_t s=0;s<p->count;s++){
if(p->states[s].jacobian!=m || p->states[s].temperatures)fatal("entry memo/observer binding");
for(size_t k=0;k<sizeof(state_fields)/sizeof(*state_fields);k++)memcpy((char*)&actual.states[s]+state_fields[k].offset,(char*)&p->states[s]+state_fields[k].offset,state_fields[k].size);
}
for(int s=0;s<SR_PIPES;s++)for(size_t k=0;k<sizeof(pipe_fields)/sizeof(*pipe_fields);k++)memcpy((char*)&actual.pipes[s]+pipe_fields[k].offset,(char*)&pipes[s]+pipe_fields[k].offset,pipe_fields[k].size);
if(workspace){
actual.gas_stats=workspace->stats;
for(int i=0;i<MODEL_JACOBIAN_GAS_COUNT;i++){
NativeJacobianGasMemo *src=&workspace->gases[i],*dst=&actual.gases[i];
uintptr_t id=src->kernel==NULL?0:src->kernel==native_medium_gas_context?1:src->kernel==native_polytropic_gas_context?2:999;
if(id==999)fatal("unknown gas memo kernel");
memcpy(&dst->kernel,&id,sizeof(id));memcpy(dst->inputs,src->inputs,sizeof(dst->inputs));dst->value=src->value;dst->medium_kind=src->medium_kind;dst->valid=src->valid;
}
}
if(stage==3 && lp_color<0){
memcpy(baseline_entries,m->entries,sizeof(baseline_entries));
if(RR_SKIP){if(fread(expected_entries,1,sizeof(expected_entries),audit_file)!=sizeof(expected_entries))fatal("memo audit EOF");if(memcmp(expected_entries,baseline_entries,sizeof(baseline_entries)))audit_fail("memo_entries",expected_entries,baseline_entries,sizeof(baseline_entries));}
else if(fwrite(baseline_entries,1,sizeof(baseline_entries),audit_file)!=sizeof(baseline_entries))fatal("memo audit write");
}else if(memcmp(baseline_entries,m->entries,sizeof(baseline_entries)))audit_fail("memo_readonly",baseline_entries,m->entries,sizeof(baseline_entries));
if(RR_SKIP){
if(fread(&expected,1,sizeof(expected),audit_file)!=sizeof(expected))fatal("audit EOF");
if(memcmp(&expected,&actual,sizeof(actual))){size_t i=0;while(((unsigned char*)&expected)[i]==((unsigned char*)&actual)[i])i++;audit_fail(audit_field(i),&expected,&actual,sizeof(actual));}
}else if(fwrite(&actual,1,sizeof(actual),audit_file)!=sizeof(actual))fatal("audit write");
restore_environment(&env);errno=saved_errno;
}
void rr_eval_exit(double t,const double *y,const double *dy,const double *w,int result,NativePropertyCache *p,NativePipeCache *pipes,ModelJacobianWorkspace *workspace){
audit_context(3,t,y,dy,w,result,0,p,pipes,workspace);eval_audits++;
}
void rr_matrix(double t,const double *y,const double *matrix){
int e=errno;SREnvironment env;save_environment(&env);
if(fwrite(&t,8,1,matrix_file)!=1 || fwrite(y,8,NSTATES,matrix_file)!=NSTATES || fwrite(matrix,8,NSTATES*NSTATES,matrix_file)!=NSTATES*NSTATES)fatal("matrix write");
restore_environment(&env);errno=e;
}
#endif
void rr_start(void){
LARGE_INTEGER f;QueryPerformanceFrequency(&f);frequency=(uint64_t)f.QuadPart;
#if RR_AUDIT
audit_file=fopen(RR_SKIP?getenv("RR_EXPECTED"):"audit.bin",RR_SKIP?"rb":"wb");matrix_file=fopen("jacobians.bin","wb");
if(!audit_file || !matrix_file)fatal("open audit files");
setvbuf(audit_file,NULL,_IOFBF,1024*1024);setvbuf(matrix_file,NULL,_IOFBF,1024*1024);
const char *s=getenv("RR_FORCE_REJECT_EVERY");force_every=s?atoi(s):0;
#endif
}
void rr_jacobian(void){jac++;plan.ready=0;}
void rr_record_begin(NativePropertyCache *p,NativePipeCache *pipes,const double *x){
baseline_start=lp_tick();
if(RR_SKIP){ax_bind(p,pipes);ax_begin(jac,-1,16,0,x,4);}
}
void rr_record_end(double output){if(RR_SKIP)ax_end(&output,1);baseline_ticks+=lp_tick()-baseline_start;}
/* The translated write set is prepared before touching live storage. */
typedef struct {void *destination;const void *source;size_t size;int kind;unsigned mask;} Patch;
static Patch patches[SR_EVENTS];
static NativeJacobianScalars *patch_owner;
static NativePropertyTemperatures *patch_observer;
static int build_patch(NativePropertyCache *p,NativePipeCache *pipes,const int *map){
int n=0;patch_owner=p->jacobian;patch_observer=NULL;
for(int i=0;i<plan.n;i++){
const Event *e=&plan.events[i];Patch item={0};
if(e->type==ALLOCATE){item.destination=&p->count;item.kind=1;}
else if(e->type==SCALAR){item.destination=&p->jacobian->reuses[e->slot];item.kind=2;}
else if(e->type==WRITE_PIPE){item.destination=(char*)&pipes[e->slot]+e->offset;item.source=e->data;item.size=e->size;}
else if(e->type==WRITE_STATE || e->type==WRITE_OR){
item.destination=(char*)&p->states[map[e->slot]]+e->offset;item.source=e->data;item.size=e->size;
if(e->type==WRITE_OR){item.kind=3;item.mask=e->mask;}
else if(e->offset==(int)offsetof(NativePropertyState,jacobian))item.source=&patch_owner;
else if(e->offset==(int)offsetof(NativePropertyState,temperatures))item.source=&patch_observer;
}else continue;
patches[n++]=item;
}
return n;
}
static void commit(int n){
for(int i=0;i<n;i++){
Patch *p=&patches[i];
if(p->kind==1)(*(size_t*)p->destination)++;
else if(p->kind==2)(*(unsigned long*)p->destination)++;
else if(p->kind==3)(*(unsigned*)p->destination)|=p->mask;
else memcpy(p->destination,p->source,p->size);
}
}
double rr_execute(NativePropertyCache *p,NativePipeCache *pipes,const double *x,SROperation original){
visits++;
#if RR_AUDIT
audit_context(1,0,NULL,NULL,NULL,1,0,p,pipes,NULL);entry_audits++;
#endif
uint64_t start=lp_tick(),tick;double output;
if(RR_SKIP){
attempts++;bound=p;bound_pipes=pipes;position=16;probe_entry_count=p->count;rr_replaying=1;
int reason=OK,mapping[SR_STATES],appends=0;
count_different+=(p->count!=plan.entry_count);
tick=lp_tick();
if(p->count>SR_STATES || p->capacity>SR_STATES || p->count>p->capacity)reason=CAPACITY;
else if(!p->jacobian || !p->jacobian->entries || p->jacobian->capacity>MODEL_JACOBIAN_SCALAR_COUNT)reason=MEMO_BINDING;
else from_live(&overlay,p,pipes);
overlay_ticks+=lp_tick()-tick;
#if RR_AUDIT
from_live(&before_reject,p,pipes);
#endif
tick=lp_tick();phase=CANDIDATE;
double saved_output=plan.output;
if(force_every && (attempts-1)%(unsigned)force_every==0)plan.output=NAN;
if(!reason)reason=replay_overlay(&overlay,x,mapping,&appends);
plan.output=saved_output;phase=OFF;validation_ticks+=lp_tick()-tick;
if(!reason){
tick=lp_tick();int n=build_patch(p,pipes,mapping);overlay_ticks+=lp_tick()-tick;
tick=lp_tick();commit(n);output=plan.output;commit_ticks+=lp_tick()-tick;
commits++;successes++;appends_total+=appends;paths[plan.path]++;
int relocated=0;for(int i=0;i<SR_STATES;i++)if(mapping[i]>=0 && mapping[i]!=i)relocated=1;
relocations+=relocated;
size_t bytes=sizeof(plan)-sizeof(plan.events)+(size_t)plan.n*sizeof(Event);
if(bytes<metadata_min)metadata_min=bytes;
if(bytes>metadata_max)metadata_max=bytes;
}else{
rejects++;reject_reason[reason]++;rr_replaying=0;
#if RR_AUDIT
from_live(&after_reject,p,pipes);
if(compare_contexts(&before_reject,&after_reject))fatal("reject altered live context");
rollback_checks++;
#endif
tick=lp_tick();rr_original_scope=1;output=original(p,pipes,x);rr_original_scope=0;fallback_ticks+=lp_tick()-tick;fallbacks++;
}
rr_replaying=0;
}else{
tick=lp_tick();rr_original_scope=1;output=original(p,pipes,x);rr_original_scope=0;original_ticks+=lp_tick()-tick;
}
path_ticks+=lp_tick()-start;
#if RR_AUDIT
audit_context(2,0,NULL,NULL,NULL,1,output,p,pipes,NULL);exit_audits++;
#endif
return output;
}
void rr_finish(void){
#if RR_AUDIT
if(RR_SKIP && fgetc(audit_file)!=EOF)fatal("unconsumed audit records");
fclose(audit_file);fclose(matrix_file);
#endif
FILE *f=fopen("real-skip.json","wb");if(!f)fatal("summary");
fprintf(f,"{\"targetVisits\":%llu,\"candidateAttempts\":%llu,\"semanticSuccess\":%llu,\"realSkips\":%llu,\"rejects\":%llu,\"fallbackExecutions\":%llu,\"nativeOriginalExecutions\":%llu,\"commits\":%llu,\"referenceExecutions\":0,\"mismatches\":0,\"evaluationAudits\":%llu,\"targetEntryAudits\":%llu,\"targetExitAudits\":%llu,\"rollbackChecks\":%llu,",visits,attempts,successes,successes,rejects,fallbacks,rr_native_calls,commits,eval_audits,entry_audits,exit_audits,rollback_checks);
fprintf(f,"\"pathSeconds\":%.12g,\"originalSeconds\":%.12g,\"validationSeconds\":%.12g,\"overlayPatchSeconds\":%.12g,\"commitSeconds\":%.12g,\"fallbackSeconds\":%.12g,\"baselineRecordSeconds\":%.12g,",(double)path_ticks/frequency,(double)original_ticks/frequency,(double)validation_ticks/frequency,(double)overlay_ticks/frequency,(double)commit_ticks/frequency,(double)fallback_ticks/frequency,(double)baseline_ticks/frequency);
fprintf(f,"\"countDifferent\":%llu,\"slotRelocationTrials\":%llu,\"appends\":%llu,\"paths\":[%llu,%llu,%llu,%llu],\"metadataMin\":%llu,\"metadataMax\":%llu,\"metadataReserved\":%llu,\"overlayBytes\":%llu,\"patchReserved\":%llu,\"rejectReasons\":{",count_different,relocations,appends_total,paths[0],paths[1],paths[2],paths[3],(unsigned long long)(metadata_min==(size_t)-1?0:metadata_min),(unsigned long long)metadata_max,(unsigned long long)sizeof(plan),(unsigned long long)sizeof(overlay),(unsigned long long)sizeof(patches));
for(int i=1;i<NREASONS;i++)fprintf(f,"%s\"%s\":%llu",i>1?",":"",reasons[i],reject_reason[i]);
fputs("}}\n",f);fclose(f);
}
@@ -0,0 +1,260 @@
"""R288-only typed replay cost-floor experiment; no production source changes."""
from pathlib import Path
import argparse,hashlib,json,os,re,shutil,subprocess,statistics,struct
import real_skip_experiment as base
ROOT=base.ROOT;HERE=Path(__file__).parent;OUT=ROOT/'test/r288-typed-replay-20260917'
OLD=base.OUT
original_core=base.core
def replace(s,a,b):return base.replace(s,a,b)
def body(s,name,change):return base.shadow.access.change_function(s,name,change)
def function_replace(s,name,new):
a,b,e=base.ex.function_span(s,name);return s[:a]+new+s[e:]
def profile_core():
s=original_core(False)
pro='''
static uint64_t prof_ticks[24],prof_calls[24];
typedef struct {uint64_t start;int category;} Prof;
static void prof_end(Prof *s){prof_ticks[s->category]+=lp_tick()-s->start;prof_calls[s->category]++;}
#define PROF(c) Prof ps __attribute__((cleanup(prof_end)))={lp_tick(),c}
'''
s=s.replace('static Plan plan;',pro+'\nstatic Plan plan;')
for name,index in [('first_match',10),('map_slot',11),('scalar_lookup',12),('state_read_equal',13),('from_live',14)]:s=body(s,name,lambda b,i=index:'PROF('+str(i)+');'+b)
s=s.replace('const Event *e=&plan.events[i];int slot=-1;','const Event *e=&plan.events[i];PROF(e->type);int slot=-1;')
s=body(s,'replay_overlay',lambda b:replace('uint64_t header_start=lp_tick();'+b,'for(int i=0;i<SR_STATES;i++)mapping[i]=-1;','prof_ticks[17]+=lp_tick()-header_start;prof_calls[17]++;\nfor(int i=0;i<SR_STATES;i++)mapping[i]=-1;'))
def split_copy(b):
portions=[
(18,'memset(dst,0,sizeof(*dst));memset(dst->states,0xa5,sizeof(dst->states));'),
(19,'dst->context=*src;memcpy(dst->states,src->states,src->count*sizeof(*src->states));'),
(20,'memcpy(dst->pipes,pipes,sizeof(dst->pipes));'),
(21,'dst->memo=*src->jacobian;memcpy(dst->entries,src->jacobian->entries,src->jacobian->capacity*sizeof(*dst->entries));')]
for tag,text in portions:b=replace(b,text,'{PROF('+str(tag)+');'+text+'}')
start=b.index('dst->memo.entries=');b=b[:start]+'{PROF(22);'+b[start:]+'}'
return b
s=body(s,'from_live',split_copy)
return s
def profile_runtime():
s=(HERE/'real_skip_runtime.c').read_text()
s=s.replace('static int force_every;','static int force_every;\nstatic FILE *plans_file;\nstatic uint64_t timer_ticks,dispatch_ticks;static volatile unsigned dispatch_sink;')
s=body(s,'rr_start',lambda b:b+'\nplans_file=fopen("plans.bin","wb");if(!plans_file)fatal("plans");\nfor(int i=0;i<100000;i++){uint64_t t=lp_tick();timer_ticks+=lp_tick()-t;}\n')
s=body(s,'rr_record_end',lambda b:b+'''
int saved=errno;SREnvironment env;save_environment(&env);
fwrite(&plan,1,sizeof(plan),plans_file);
uint64_t dispatch_start=lp_tick();unsigned checksum=0;
for(int repeat=0;repeat<100;repeat++)for(int i=0;i<plan.n;i++){
const Event *e=&plan.events[i];switch(e->type){
case QUERY:checksum+=(unsigned)e->slot;break;case SCALAR:checksum+=(unsigned)e->size;break;
case ALLOCATE:checksum++;break;case WRITE_STATE:case WRITE_PIPE:checksum+=(unsigned)e->offset;break;
case READ_STATE:case READ_PIPE:checksum+=(unsigned)e->size;break;case WRITE_OR:case VALID:checksum+=e->mask;break;default:checksum++;
}}
dispatch_ticks+=lp_tick()-dispatch_start;dispatch_sink=checksum;
restore_environment(&env);errno=saved;
''')
s=body(s,'build_patch',lambda b:'PROF(15);'+b)
s=body(s,'commit',lambda b:'PROF(16);'+b)
s=body(s,'rr_finish',lambda b:b+'''
fclose(plans_file);FILE *pf=fopen("attribution.json","wb");
fprintf(pf,"{\\"frequency\\":%llu,\\"emptyTimerTicks\\":%llu,\\"emptyTimerCalls\\":100000,\\"dispatchTicks\\":%llu,\\"dispatchRepeats\\":100,\\"categories\\":[",(unsigned long long)frequency,(unsigned long long)timer_ticks,(unsigned long long)dispatch_ticks);
for(int i=0;i<24;i++)fprintf(pf,"%s{\\"id\\":%d,\\"ticks\\":%llu,\\"calls\\":%llu}",i?",":"",i,(unsigned long long)prof_ticks[i],(unsigned long long)prof_calls[i]);
fputs("]}",pf);fclose(pf);
''')
return s
def minimal_sources(audit):
# The audit build adds the minimal capture hooks to the certified access
# source, allowing one physical baseline execution to feed BOTH schemas.
directory=base.ACCESS if audit else base.BASE
codes={n:(directory/n).read_text(encoding='utf-8') for n in ['properties.c','pipe.c']}
s=codes['properties.c']
if audit:
# Native access hooks feed the compact recorder through ax_* wrappers.
return codes
s=body(s,'property_pt',lambda b:replace(replace(b,'return s;','{fp_query(cache,m,p,T,s);return s;}'),'return property_new(cache,m,p,T,scratch);','fp_query(cache,m,p,T,NULL);return property_new(cache,m,p,T,scratch);'))
s=body(s,'property_new',lambda b:replace(b,'return s;','fp_allocate(cache,s,valid);return s;'))
s=body(s,'native_temperature_ph_context',lambda b:'fp_unknown();'+b)
s=body(s,'native_jacobian_scalar_get',lambda b:replace('fp_scalar_key(kind,medium_kind,inputs,count);'+b,'return 1;','fp_scalar_value(kind,medium_kind,inputs,count,*value);return 1;'))
s=body(s,'native_jacobian_scalar_put',lambda b:'fp_scalar_value(kind,medium_kind,inputs,count,value);'+b)
codes['properties.c']=s
return codes
def fast_core(audit):
if audit:
s=original_core(True)
s=s.replace('static SRContext overlay;','')
s=body(s,'ax_query',lambda b:b+'\nif(phase==RECORD && strcmp(kind,"PT"))fp_unknown();')
s=body(s,'ax_match',lambda b:b+'''
if(phase==RECORD && !strcmp(kind,"PT")){
Event *q=&plan.events[plan.query_index];double key[9];memcpy(key,q->data,sizeof(key));
NativeMedium m={q->offset,key[2],key[3],key[4],key[5],key[6],key[7],key[8]};fp_query(bound,&m,key[0],key[1],s);
}
''')
s=body(s,'ax_new',lambda b:'if(phase==RECORD)fp_allocate(cache,s,valid);'+b)
s=body(s,'ax_scalar',lambda b:b+'''
if(phase==RECORD){
if(!strcmp(action,"get")){fp_scalar_key(kind,medium,keys,n);if(hit && value)fp_scalar_value(kind,medium,keys,n,*value);}
else if(!strcmp(action,"put_attempt") && value)fp_scalar_value(kind,medium,keys,n,*value);
}
''')
return s
s=(HERE/'context_shadow_replay.c').read_text()
s=s[:s.index('typedef struct {\n int type,slot')]
s=s.replace('static void save_environment','static __attribute__((unused)) void save_environment').replace('static void restore_environment','static __attribute__((unused)) void restore_environment')
s+='''
static unsigned long long jac;
static NativePropertyCache *bound;
static NativePipeCache *bound_pipes;
static void fatal(const char *s){fprintf(stderr,"typed replay fatal: %s\\n",s);abort();}
'''
return s
def fast_runtime(audit,direct=False):
s=(HERE/'real_skip_runtime.c').read_text()
s=s.replace('static int force_every;','static __attribute__((unused)) int force_every;\n#include "fast_replay.inc"')
start=s.index('/* The translated write set');end=s.index('double rr_execute(',start)
s=s[:start]+s[end:]
execute=(HERE/'typed_replay_runtime.inc').read_text()
s=function_replace(s,'rr_execute',execute)
if audit:s=s.replace('void rr_start(void){','#include "typed_contract.inc"\n#include "typed_oracle.inc"\nvoid rr_start(void){')
s=function_replace(s,'rr_jacobian','void rr_jacobian(void){jac++;fast.ready=0;\n#if RR_AUDIT\nplan.ready=0;\n#endif\n}')
s=function_replace(s,'rr_record_begin','''void rr_record_begin(NativePropertyCache *p,NativePipeCache *pipes,const double *x){
baseline_start=lp_tick();bound=p;bound_pipes=pipes;fp_begin(p,pipes,x);
#if RR_AUDIT
ax_bind(p,pipes);ax_begin(jac,-1,16,0,x,4);
#endif
}''')
s=function_replace(s,'rr_record_end','''void rr_record_end(double output){
fp_end(output);
#if RR_AUDIT
ax_end(&output,1);fp_contract_check();
#endif
baseline_ticks+=lp_tick()-baseline_start;
}''')
if direct:
s=s.replace('static __attribute__((unused)) int force_every;','static FILE *archived_plans;\nstatic __attribute__((unused)) int force_every;')
s=body(s,'rr_start',lambda b:b+'\narchived_plans=fopen(getenv("RR_PLAN_ORACLE"),"rb");if(!archived_plans)fatal("archived plan oracle");\n')
s=s.replace('ax_end(&output,1);fp_contract_check();','''
int saved_errno=errno;SREnvironment saved_env;save_environment(&saved_env);
if(fread(&plan,1,sizeof(plan),archived_plans)!=sizeof(plan))fatal("archived plan EOF");
uintptr_t previous_owner=plan.memo_binding;
for(int i=0;i<plan.n;i++){
Event *e=&plan.events[i];
if((e->type==READ_STATE || e->type==WRITE_STATE) && e->offset==(int)offsetof(NativePropertyState,jacobian)){
uintptr_t pointer;memcpy(&pointer,e->data,sizeof(pointer));if(pointer!=previous_owner)fatal("archived memo binding");memcpy(e->data,&fast.owner,sizeof(fast.owner));
}}
plan.memo_binding=(uintptr_t)fast.owner;phase=OFF;
restore_environment(&saved_env);errno=saved_errno;fp_contract_check();
''')
s=body(s,'rr_finish',lambda b:b+'\nif(fgetc(archived_plans)!=EOF){fatal("remaining archived plans");}\nfclose(archived_plans);\n')
s=s.replace('(unsigned long long)sizeof(plan),(unsigned long long)sizeof(overlay),(unsigned long long)sizeof(patches)', '(unsigned long long)sizeof(fast),(unsigned long long)sizeof(Pending),(unsigned long long)sizeof(Pending)')
s=body(s,'rr_finish',lambda b:b+'''
FILE *fextra=fopen("typed-summary.json","wb");
fprintf(fextra,"{\\"metadataBytes\\":%llu,\\"captureScratchBytes\\":%llu,\\"pendingBytes\\":%llu,\\"contractChecks\\":%llu,\\"oracleChecks\\":%llu,\\"negativeChecks\\":%llu}",(unsigned long long)sizeof(fast),(unsigned long long)sizeof(capture),(unsigned long long)sizeof(Pending),
#if RR_AUDIT
contract_checks,oracle_checks,negative_checks
#else
0ULL,0ULL,0ULL
#endif
);fclose(fextra);
''')
return s
def contract_source():
data=(OUT/'P/perf-skip-run/plans.bin').read_bytes();schemas={}
for off in range(0,len(data),90224):
count=struct.unpack_from('<Q',data,off+90112+56)[0];n=struct.unpack_from('<i',data,off+90112+68)[0];path=struct.unpack_from('<i',data,off+90112+88)[0]
entries=[]
for i in range(n):
t,s,o,z,a,m=struct.unpack_from('<iiiiiI',data,off+i*176)
if t in (1,2,3,7,8):s-=count
entries.append((t,s,o,z,a,m))
if path in schemas:assert schemas[path]==entries
else:schemas[path]=entries
assert sorted(schemas)==[0,1] and len(schemas[0])==12 and len(schemas[1])==132
prefix='typedef struct {int type,slot,offset,size,aux;unsigned mask;} Contract;\n'
for path,events in schemas.items():prefix+='static const Contract schema'+str(path)+'[]={'+','.join('{'+','.join(map(str,e))+'}' for e in events)+'};\n'
return prefix+(HERE/'typed_contract_check.inc').read_text()
def prepare(mode,audit=False):
certified=json.loads((OLD/'perf-skip/build.json').read_text())['originalHashes']
for name,expected in certified.items():assert hashlib.sha256((base.BASE/name).read_text(encoding='utf-8').encode()).hexdigest()==expected,name
root=OUT/mode;root.mkdir(parents=True,exist_ok=True)
staging=root/'source';staging.mkdir(exist_ok=True)
for name in ['context_shadow_replay.c','context_shadow_replay.h','real_skip.h']:shutil.copyfile(HERE/name,staging/name)
runtime=fast_runtime(audit,mode=='D') if mode in 'CD' else profile_runtime() if mode=='P' else (HERE/'real_skip_runtime.c').read_text()
(staging/'real_skip_runtime.c').write_text(runtime,encoding='utf-8')
if mode in 'CD':
header=(staging/'real_skip.h').read_text();header+='\n'+(HERE/'typed_replay_hooks.h').read_text();(staging/'real_skip.h').write_text(header)
work=root/('audit-skip' if audit else 'perf-skip');work.mkdir(exist_ok=True)
shutil.copyfile(HERE/'typed_replay_core.inc',work/'fast_replay.inc')
if audit:
(work/'typed_contract.inc').write_text(contract_source(),encoding='utf-8')
shutil.copyfile(HERE/'typed_oracle.inc',work/'typed_oracle.inc')
access=staging/'capture';access.mkdir(exist_ok=True)
for n in ['context_access_diag.h','context_fallback_diag.h']:shutil.copyfile(base.ACCESS/n,access/n)
for n,code in minimal_sources(audit and mode!='D').items():(access/n).write_text(code,encoding='utf-8')
base.OUT=root;base.HERE=staging
if mode in 'CD':base.ACCESS=access
base.core=lambda enabled:fast_core(enabled) if mode in 'CD' else profile_core() if mode=='P' else original_core(enabled)
if not audit:
gate=root/'audit-skip-run';gate.mkdir(exist_ok=True)
if mode!='C':shutil.copyfile(OLD/'audit-skip-run/validation.json',gate/'validation.json')
else:assert json.loads((gate/'validation.json').read_text())['exact']
# original_core reads base.HERE; the source copies are byte-identical.
base.prepare(audit,mode!='A')
if mode in 'CD':
manifest=root/('audit-skip' if audit else 'perf-skip')/'build.json';info=json.loads(manifest.read_text())
info['guardMatchesShadow']=False;info['wholeContextGuardUnchanged']=True
info['typedGuardValidation']='See C/D full-trajectory contracts, generic oracle and forced-reject audits.'
base.write(manifest,info)
if mode=='C' and not audit:
cc,_,_=base.ex.builder.toolchain();nm=Path(cc).with_name('nm.exe' if os.name=='nt' else 'nm')
symbols=subprocess.check_output([str(nm),'--defined-only',str(root/'perf-skip/model.exe')],text=True)
assert not re.search(r'(?m)\b(?:plan|overlay|before_reject|after_reject|oracle_generic|oracle_typed|replay_overlay|ax_access)$',symbols)
base.write(root/'perf-skip/stripped-audit-proof.json',dict(noGenericInterpreter=True,noFullOverlay=True,noAuditOracle=True))
def run(mode,audit=False,label=None,force=0):
base.OUT=OUT/mode
if mode=='D':os.environ['RR_PLAN_ORACLE']=str(OUT/'P/perf-skip-run/plans.bin')
if audit:
expected=base.OUT/'audit-control-run';expected.mkdir(exist_ok=True)
target=expected/'audit.bin'
if not target.exists():os.link(OLD/'audit-control-run/audit.bin',target)
result=base.run(audit,mode!='A',label,force)
result['mode']=mode
if mode in 'CD':
extra=json.loads((base.OUT/result['label']/'typed-summary.json').read_text())
assert extra['metadataBytes']==240 and extra['captureScratchBytes']==96 and extra['pendingBytes']==456
assert extra['contractChecks']==extra['oracleChecks']==(896 if audit else 0)
assert extra['negativeChecks']==(20 if audit and not force else 0)
result['typed']=extra
base.write(base.OUT/result['label']/'validation.json',result)
return result
def benchmark(rounds):
for mode in 'CD':
for label in ['audit-skip-run','forced-reject']:assert json.loads((OUT/mode/label/'validation.json').read_text())['exact']
rows=[]
for mode in 'ABC':run(mode,label='warm')
for i in range(rounds):
order='ABC'[i%3:]+'ABC'[:i%3]
for mode in order:rows.append(run(mode,label=f'round-{i+1}'))
base.write(OUT/'performance.json',rows)
if __name__=='__main__':
p=argparse.ArgumentParser();p.add_argument('action',choices=['prepare','run','benchmark']);p.add_argument('--mode',choices=list('ABCPD'),default='C');p.add_argument('--audit',action='store_true');p.add_argument('--label');p.add_argument('--force',type=int,default=0);p.add_argument('--rounds',type=int,default=9);a=p.parse_args()
if a.action=='prepare':prepare(a.mode,a.audit)
elif a.action=='run':run(a.mode,a.audit,a.label,a.force)
else:benchmark(a.rounds)
+237
View File
@@ -0,0 +1,237 @@
# position379:state_valve 数值尾部诊断
日期:2026-09-17。仅针对既有八路模型 0–10 s 轨迹中,Jacobian baseline/probe 实际执行的 **position379 / PNVO001_1.port_2**。没有改生产路径,没有研究其他 position,没有实现真实 memo skip。
## 四个问题的答案
1. **尾部典型单次约0.618 µs。** 在不做前置lookup/FP采样的三轮计时对照中,逐次中位数为0.611–0.628 µs;每轮均值为0.692–0.918 µs,均值的跨轮中位数0.803 µs。均值含调度长尾,不能当作纯CPU指令时间。该数值来自本次尾部计时,不是此前0.783 µs的state_valve exclusive。
2. **完整数值key重复率84.615%。** 每个Jacobian有5种key,22个probe与baseline逐位相同;全轨迹理论命中19,712/23,296。包含完整x87状态字保护后,本次“无FP状态变化才可直接返回”的候选只允许8,734次,即37.491%。
3. **当前严格候选未达到完整机制break-even。** 五轮hit lookup均值0.152–0.475 µs,baseline新增2.088–6.619 µs/J;全部miss成本也必须计入。两轮相对平稳的配对数据要求hit成本低于0.109/0.074 µs,实测分别为0.152/0.153 µs。五轮诊断净预算全部为负,−0.367至−8.269 ms/轨迹,中位数−2.341 ms。
4. **不进入真实实现阶段,停止该方向。** 数值key确实重复,但当前候选没有证明在保留完整FP语义后能获得净收益。这不证明任何kernel memo都不可能盈利;它说明本轮没有满足启动真实实现的门槛。没有通过忽略x87状态、过滤慢样本或扩大position范围来改变结论。
## 1. 源码边界与准确输入
依据归档 [properties.c](../../test/local-probe-20260917/worker/properties.c) 第266–277行和 [orifice.c](../../test/local-probe-20260917/worker/orifice.c) 的 `native_medium_orifice_context`。使用与既有fallback诊断相同的归档worker,保证比较对象一致;这不是对工作区后来其他改动重新建模。
### A. 必须保留的context/property行为
调用链:`native_medium_orifice_context → native_temperature_ph_context → medium_valve → property_pt → state_valve`。在state_valve内,以下语句及其子调用均原样执行:
```c
double p=up->p,T=up->T;
pd=fmax(fmin(pd,p),0);
const NativeMedium *m=&up->medium;
double cp=m->cp+m->slope*(T-m->Tref);
double factor=m->real_helium?isentropic(cache,up,pd):(cp-m->R)/cp;
double g=fmax(1e-9,fmin(1-1e-9,factor));
double rho=fmax(property_density(up),1e-12);
```
这包括PH/PT查询、首次匹配、entry创建、count/valid变化、上游和下游等熵准备、density获取及温度observer行为。orifice后续的q计算、方向/opening处理、有限性检查和返回值仍执行。目标调用是orifice,没有额外跳过任何pipe逻辑;整个求解中的其他pipe/context路径保持原执行。
### B. 已有memo/valid覆盖的工作
- `local_isentropic` 先观察温度,valid命中才直接返回;不能把观察和valid语义一起memo掉。
- `property_density`、PH context获取中的已有缓存/Jacobian scalar memo继续工作。
- 先前fallback函数统计中,昂贵PH反算、密度求解、pipe resistance求根实际调用为0;本轮不把它们当作新可省工作。
### C. 本次计时的数值尾部
**起点:原源码第272行 `double r=...`;终点:第276行平滑修正完成。** `subsonic_cm` 的调用也计入该尾部。
```c
double r=fmax(pd/p,0),critical=pow(2*g/(g+1),1/(1-g)),eff;
if(r<=critical) {
eff=critical;
*cm=sqrt(2/(1+g)*rho*T/p)*pow(2*g/(g+1),g/(1-g));
*vel=sqrt(2/(1+g)*p/rho);
} else {
eff=r;
*cm=subsonic_cm(r,g,rho,T,p);
*vel=sqrt(fmax(2/(1-g)*p/rho*(1-pow(r,1-g)),0));
}
double ref=subsonic_cm(.9999,g,rho,T,p);
if(*cm>0 && ref>0) {
double smooth=tanh(fmax(12*fabs(*cm/ref)*log(eff)/log(.9999),0));
*cm*=smooth; *vel*=smooth;
}
```
- 完整数值输入:**`p, T, pd, g, rho`**,五个double的原始位,共40 B。pd已经限幅,g和rho已经完成上述准备/限幅。不能用原operation的p/h/opening作为替代key。
- 数值输出:**`cm, vel`**,两个double原始位。
- 不再读取medium、entry、valid、count或pipe,不写property context;原输出指针的既有值不是尾部输入,两条分支均先写cm/vel。
- 这只表示“数值计算不依赖context”,**不表示没有机器浮点状态副作用**。数学库和x87运算会改变部分状态字位,因此不能把整个state_valve或这个尾部无条件当作可直接缓存返回的纯函数。
## 2. 诊断方法
独立worker复制既有local probe实现,只在目标position设置诊断scope;普通residual及其他position不进入记录。原whole-context guard及其成功恢复路径保留。
- 每次目标尾部仍执行一次原数学代码。诊断lookup只把可能的缓存结果写到临时记录,不用于operation输出,**没有真实skip,也没有双路Reference执行**。
- 使用invariant TSC,`lfence/rdtsc/lfence`,全轨迹对QPC校准;原编译参数仍为O3、禁止fast-math与FP contraction。两端标记包围原尾部,没有循环重复同一个热key来替代实际轨迹。
- 所有详细记录先写预分配内存,积分结束后写盘;单记录128 B。候选baseline记录仅72 B:40 B key、16 B输出、12 B环境、4 B ready。
- 五轮完整诊断,同时直接测K、候选hit/miss lookup、baseline key/环境准备、结果捕获及每Jacobian重置。G包含外层调用、key构造、环境读取、完整比较及写回临时结果,不只测memcmp。
- 另三轮 **K-only** 对照:尾部之前只放计时标记,key/环境采样全部移到尾部之后。这三轮的环境不是入口环境,**其hits/G/H字段不参与任何命中率或盈利判断**。
- 原始单次均值、中位数和min/max全部保留,没有剔除抢占/缺页长尾。空标记中位约12–14 ns;预算只从可省K中扣除空标记,G/H保留原始计时,避免把微小收益做大。
归档初版构建与交付诊断版本的 `model.o`、`properties.o`、`valve_tail_diag.o` 字节一致;清理仅移除了未启用的预留接口,没有引入真实skip版本。
已检查两版 `state_valve` 目标码控制流:density/g准备先于起始标记,`pd/p`、critical及尾部数学调用位于计时范围内,输出写入完成后才取结束标记。汇编分别保存在构建目录的 `state-valve.asm`。
## 3. 全轨迹进入次数与尾部时间
所有轮次都完成896个Jacobian。目标物理operation进入和尾部执行次数均为 **24,192**:
| 类别 | 次数 |
|---|---:|
| baseline | 896 |
| probe group0–25 | 23,296 |
| group26 | 0:已有whole-context复用使目标operation未执行 |
| 真实skip | 0 |
分母是Jacobian内实际执行的目标probe,不含普通residual,也不把group26已有的复用计成新memo收益。
### K-only低扰动对照(每轮24,192次)
| 轮次 | 均值µs | 单次中位µs | 单次min–max µs | 原始累计ms | 扣空标记均值µs |
|---|---:|---:|---:|---:|---:|
| tail-only-0 | 0.803447 | 0.610863 | 0.377976–3595.053 | 19.436981 | 0.791542 |
| tail-only-1 | 0.691729 | 0.617559 | 0.369792–459.269 | 16.734302 | 0.679824 |
| tail-only-2 | 0.917657 | 0.627976 | 0.369047–3031.652 | 22.199961 | 0.904264 |
三轮逐次中位数的中位数 **0.617559µs**,每轮均值的中位数 **0.803447µs**,累计时间中位数 **19.436981ms**。数千µs的max明显混有系统调度长尾,不代表一次数学计算通常要这么久;这些样本没有被删除。
### 与G/H同时采集的五轮K(盈亏使用同轮配对数据)
| 轮次 | 均值µs | 单次中位µs | 单次min–max µs | 原始累计ms |
|---|---:|---:|---:|---:|
| diagnostic-0 | 0.600212 | 0.575893 | 0.360863–33.676 | 14.520341 |
| diagnostic-1 | 0.759084 | 0.680059 | 0.396577–201.298 | 18.363768 |
| diagnostic-2 | 0.843411 | 0.677083 | 0.367559–1362.520 | 20.403801 |
| diagnostic-3 | 1.176682 | 0.691964 | 0.368303–8174.892 | 28.466296 |
| diagnostic-4 | 0.642986 | 0.577381 | 0.416666–243.518 | 15.555124 |
这些结果支持尾部是亚微秒级计算,不能支持“所有测得墙钟长尾都可通过memo省掉”。K-only与完整诊断的批次/代码布局不同,不跨轮拿最贵K减最便宜G来拼净收益。
## 4. 完整数值bit-key的真实重复率
每个Jacobian的27次物理进入(1 baseline + 26 probes)均有 **5种不同key**:
- baseline与 **group0–5、10–25** 共用同一key;每Jacobian22次重复。
- group6、7、8、9各有一个不同key,彼此及baseline不同。
- 896个Jacobian全部如此;baseline-only单槽候选已经覆盖所有数值重复,增加多条probe memo不会再增加理论hit。
| 统计 | 结果 |
|---|---:|
| 理论memo hit | 19,712 |
| 理论probe miss | 3,584 |
| 数值key hit rate | 19,712 / 23,296 = **84.6153846%** |
| 相同key的cm/vel逐位不同 | **0** |
| 各组与baseline相同次数 | 上述22组分别896次 |
[每个Jacobian的baseline完整十六进制key、各组key和分组关系](../../test/position379-tail-20260917/diagnostic-0/key-groups.json)。其他四轮也各自保存清单和原始记录,并得到相同分组计数。
### 数值重复不等于可以直接返回
本次读取:errno、MXCSR全寄存器、x87 control word、x87 status word。尾部前后:
- errno、MXCSR、x87 control word变化次数均为0。
- **x87 status word变化13,374次**,其中baseline变化499/896次。
- 变化位是x87条件状态位,例如 `0x120 → 0x320` 和 `0x320 → 0x120`;本轨迹没有观察到标准浮点异常标志或舍入控制变化。不能把“标准异常标志没变”表述为“完整状态字没变”。
首版诊断候选保持保守边界:baseline key/output必须有限,所有异常被mask;baseline尾部前后上述环境完全相同,当前probe环境又与baseline一致,且完整40 B key相同,才计算为可直接返回的hit。其余情况仍原计算。没有尝试重放/修补x87状态字,也没有把条件位从比较中移除。
| 严格候选结果(每轮一致) | 次数 |
|---|---:|
| 可直接返回候选hit | **8,734 = 397 × 22** |
| 候选hit rate | **37.4914148%** |
| baseline改变x87状态,故记录不可直接复用 | 12,974次probe |
| 在可复用baseline下,probe环境不同 | 901 |
| 环境相同但key不同 | 687 |
| 总候选miss / 原执行 | 14,562 |
拒绝原因按实际检查优先级归类,环境不同和key不同可能重叠,不能将此表与3,584次纯数值miss相加。上述hit均进一步检查了真实原执行的出口环境和cm/vel:与baseline一致;但本轮没有把它接入真实skip。
这里的完整x87状态保护比仅核对C标准fenv的异常/舍入控制更严格。它明确对应本轮不放宽FP状态语义的候选;不能用其37.49%结果断言所有可能的FP兼容memo设计都只有这个命中率。
## 5. Break-even:必须收费的miss与baseline
本轮的H包括每Jacobian重置、baseline key/入口环境构造、出口环境检查、完整key/output捕获和ready判定。原baseline尾部计算本身未计为H,因为无优化时也必须执行。
```text
net = sum(K_i for accepted hits)
− sum(G_hit_i)
− sum(G_miss_i)
− sum(H_j)
```
仅用 `K > G_hit + H/22` 会出错:严格候选平均每Jacobian只有 **9.747768次hit**,同时每Jacobian有 **16.252232次miss lookup**。miss不省任何尾部工作,却仍要支付查找开销。
### 同轮测量的开销及诊断预算
G为逐次成本的均值,H为总baseline新增除以896。下表净值使用该轮可接受hit对应的原尾部时间,扣一个空标记后计算;不是根据全体K均值估算命中部分。
| 轮次 | G_hit µs | G_miss µs | H µs/J | 可省尾部ms | 全部probe lookup ms | baseline新增ms | 诊断净预算ms |
|---|---:|---:|---:|---:|---:|---:|---:|
| 0 | 0.151522 | 0.157994 | 2.161951 | 5.193716 | 3.624103 | 1.937108 | **−0.367495** |
| 1 | 0.385920 | 0.575816 | 3.331597 | 6.472028 | 11.755655 | 2.985111 | **−8.268738** |
| 2 | 0.415912 | 0.480126 | 3.563161 | 8.496013 | 10.624167 | 3.192592 | **−5.320746** |
| 3 | 0.475128 | 0.507744 | 6.618877 | 15.133467 | 11.543533 | 5.930514 | **−2.340579** |
| 4 | 0.152557 | 0.197271 | 2.087834 | 5.390453 | 4.205092 | 1.870699 | **−0.685338** |
五轮G_hit均值中位数 **0.385920µs**,H中位数 **3.331597µs/J**。H不仅是几个孤立长尾:不含重置的逐baseline成本中位数也为 **1.794–2.944µs**。没有测出H≤1µs/J。
G_hit逐次中位数较低(约0.071–0.133µs),但不能只收hit的中位数费用,再把miss和baseline账单省略。要计算轨迹净收益,必须使用累计成本。
### 具体break-even门槛
固定同轮实测H和全部miss成本后:
```text
G_hit_max = (saved_tail_work − baseline_cost − miss_lookup_cost) / hits
```
五轮上限分别为 **0.109446、−0.560810、−0.193287、0.207143、0.074089µs**。负数表示即使hit完全免费,该轮H+miss也已用完预算;其余轮的实际G_hit同样超过上限。
以较平稳的第0轮举例,命中尾部扣标记均值0.594655µs:
- 若H=1µs/J,hit与miss统一收费G,需要 **G<0.184483µs/每次probe尝试** 才盈利。因此“G≤0.25µs且H≤1µs/J”在37.49%可接受率下也不自动足够。
- 实测H=2.161951µs/J时,所有probe的平均查找预算约 **0.13979µs**;实测约0.15557µs,仍超过。
- 这是具体候选的盈亏边界,不是kernel memo的理论最低实现成本。没有测量未知的新FP状态重放设计,也不把这种未实现方案当作已有收益。
所有净预算是诊断阶段的候选费用估计,**不是实测real-skip加速**。系统负载和计时长尾较大,无法证明纯硬件成本存在绝对负收益下界;但是没有一轮形成完整账单上的正收益,故不满足“诊断显示可盈利才实现”的条件。
## 6. 数值验收与停止决定
8轮(5轮完整诊断、3轮K-only)全部与归档未修改local probe基线一致:states、outputs、events二进制、最终状态、warning和solver counters。diagnostic-0与tail-only-0另分别核对全部896个132×132 Jacobian及对应t/y,逐位一致。
| 计数 | 各轮结果 |
|---|---:|
| accepted / rejected | 10840 / 918 |
| Newton iterations / convergence failures | 19371 / 798 |
| nfev / njev / nlu | 44467 / 896 / 3106 |
| solverStarts / stateTransitions | 4 / 1 |
| 目标原尾部实际执行 | 24,192 / 24,192 |
本轮没有真实skip版本,因此没有声称完成memo版的逐evaluator property/pipe context与memo生命周期双路验收。当前验证证明诊断插桩保持了上述完整轨迹数值;将来若提出另一种盈利方案,仍须重新完成用户要求的全context/FP等真实skip验收,不能借用本轮数值一致性代替。
**停止决定:保留position379原尾部计算;不接入生产、不扩展其他position、不继续调小这套候选以追求过线。**
## 产物与复现
- [诊断构建/执行脚本](diagnose_valve_tail.py)、[只读汇总脚本](analyze_valve_tail.py)。
- [诊断接口](valve_tail_diag.h)、[诊断记录与计时](valve_tail_diag.c)。代码没有真实skip入口。
- [全部轮次统计、预算及FP变化](../../test/position379-tail-20260917/diagnostic-analysis.json)。
- [最终核验记录](../../test/position379-tail-20260917/verification.json):五轮key/output/FP记录投影逐字节相同,源文件/构建哈希及数值检查通过。
- `test/position379-tail-20260917/diagnostic-0..4/`:原始128 B记录、每Jacobian key分组、空时钟标记、逐轮校验结果。
- `tail-only-0..2/`:独立K对照;其后置环境采样不作语义证据。
- `diagnostic-build-v1/`:原五轮使用的构建及哈希;`diag-trace/`为交付诊断构建,关键目标码一致;`tail-only-trace/`为K对照构建。
```powershell
.venv-win/Scripts/python.exe -B tests/manual/diagnose_valve_tail.py prepare
.venv-win/Scripts/python.exe -B tests/manual/diagnose_valve_tail.py run --label diagnostic-new --matrices
.venv-win/Scripts/python.exe -B tests/manual/diagnose_valve_tail.py prepare --tail-only
.venv-win/Scripts/python.exe -B tests/manual/diagnose_valve_tail.py run --worker tail-only-trace --label tail-only-new --matrices
.venv-win/Scripts/python.exe -B tests/manual/analyze_valve_tail.py
```
路径均从脚本所在仓库解析;此实验使用Windows归档工具链和TSC/FP寄存器读数,没有进行Linux性能测试。
@@ -0,0 +1,63 @@
"""Summarize accuracy, contact phases and repeated cost for isolated variants."""
import argparse
import json
from pathlib import Path
import statistics
import numpy as np
def read(path):return json.loads(path.read_bytes())
def main():
parser=argparse.ArgumentParser(description=__doc__)
parser.add_argument('directory',type=Path)
args=parser.parse_args();out=args.directory
runs=read(out/'summary.json');rows=[];branches=[];groups=[]
for profile,variants in runs.items():
baseline=variants['off']
base_force=baseline['groups']['force']['worstAbsolute']['maxAbsoluteError']
base_time=baseline['timing']['medianSeconds']
base_curves=np.load(out/profile/'off/curves.npz')
for mode,r in variants.items():
data=np.load(out/profile/mode/'curves.npz')
contacts=[]
for key in data.files:
if key.startswith('platform|') and key.endswith('.gap'):
expected='amesim|'+key.split('|',1)[1]
mismatch=data['phaseMatched'] & ((data[key]<0)!=(data[expected]<0))
contacts.extend(dict(key=key.split('|',1)[1],time=float(data['time'][i])) for i in np.flatnonzero(mismatch))
equal=all(np.array_equal(data[k],base_curves[k],equal_nan=True)
for k in data.files if k.startswith('platform|'))
worst=r['groups']['force']['worstAbsolute'];timing=r['timing'];native=r['nativeRun']
report=read(out/profile/mode/'comparison.json')
branches.extend(dict(profile=profile,mode=mode,**c) for c in report['curves'] if c['quantity']=='force')
groups.extend(dict(profile=profile,mode=mode,quantity=q,**g) for q,g in r['groups'].items())
counter_keys=('nfev','acceptedSteps','rejectedSteps','solverStarts','stateTransitions','njev','nlu','experimentalEvents')
counters=[{k:t.get(k) for k in counter_keys} for t in timing['runs']]
row=dict(profile=profile,mode=mode,medianSolveSeconds=timing['medianSeconds'],
minimumSolveSeconds=timing['minimumSeconds'],maximumSolveSeconds=timing['maximumSeconds'],
timingRepeats=timing['repeats'],repeatCountersStable=all(c==counters[0] for c in counters),
medianCpuSeconds=statistics.median(t['solveCpuSeconds'] for t in timing['runs']),
solveChangePercent=100*(timing['medianSeconds']/base_time-1),
forceMaxError=worst['maxAbsoluteError'],forceWorstTime=worst['worstTime'],forceWorstKey=worst['key'],
forceErrorChangePercent=100*(worst['maxAbsoluteError']/base_force-1),
forceQuietMax=max(c['quietMaxAbsolute'] for c in report['curves'] if c['quantity']=='force'),
pressureMaxError=r['groups']['pressure']['worstAbsolute']['maxAbsoluteError'],
temperatureMaxError=r['groups']['temperature']['worstAbsolute']['maxAbsoluteError'],
above5PercentCount=r['above5PercentCount'],phaseUnpairedCount=r['phaseUnpairedGridCount'],
contactPhaseMismatches=contacts,gridTrajectoryEqualsOff=equal,
rawPeakContactForce=r['extraEventPointMaxContactForce'],
acceptedSteps=native['acceptedSteps'],rejectedSteps=native['rejectedSteps'],
nfev=native['nfev'],solverStarts=native['solverStarts'],njev=native['njev'],nlu=native['nlu'],
stateTransitions=native['stateTransitions'],
experimentalEvents=native.get('experimentalEvents',{}),jacobianMode=native['jacobianMode'])
rows.append(row)
print(json.dumps(row,ensure_ascii=False))
(out/'effect-summary.json').write_text(json.dumps(rows,ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
(out/'branch-force-summary.json').write_text(json.dumps(branches,ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
(out/'group-summary.json').write_text(json.dumps(groups,ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
if __name__=='__main__':main()
@@ -0,0 +1,71 @@
"""Audit the controlled output-only eight-branch experiment without hiding raw errors."""
import argparse
import json
from pathlib import Path
import numpy as np
def load(path):
return json.loads(path.read_bytes())
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('directory', type=Path)
args = parser.parse_args()
out, result = args.directory, {}
for name in ('full', 'noncyclic'):
before, after = (out / stage / name for stage in ('before', 'after'))
a, b = (load(p/'native/result.json') for p in (before, after))
at, bt = (np.asarray(r['series']['time']) for r in (a, b))
indices = np.searchsorted(bt, at)
assert np.array_equal(bt[indices], at), 'Every old sample must remain at its real timestamp'
differences = []
for key, values in a['series'].items():
old, new = np.asarray(values), np.asarray(b['series'][key])[indices]
if not np.array_equal(old, new):
differences.append(dict(key=key, maxAbsoluteDifference=float(np.max(np.abs(new-old)))))
counters = ('acceptedSteps', 'rejectedSteps', 'nfev', 'njev', 'nlu', 'solverStarts', 'stateTransitions')
counter_pairs = {key: [a[key], b[key]] for key in counters}
paired = np.load(after/'curves.npz')
contact_phase = {}
for key in paired.files:
if key.startswith('platform|') and key.endswith('.gap'):
other = 'amesim|' + key.split('|', 1)[1]
mismatch = paired['phaseMatched'] & ((paired[key]<0) != (paired[other]<0))
contact_phase[key.split('|', 1)[1]] = dict(mismatchCount=int(mismatch.sum()),
times=paired['time'][mismatch].tolist())
old_metrics = load(before/'raw-time-comparison.json')
new_raw = load(after/'raw-time-comparison.json')
aligned = load(after/'comparison.json')
quantities = {}
for quantity in ('signal', 'force', 'pressure', 'temperature', 'mass_flow', 'enthalpy_flow', 'velocity'):
groups = []
for rows in (old_metrics['curves'], new_raw['curves'], aligned['curves']):
rows = [r for r in rows if r['quantity'] == quantity]
worst = max(rows, key=lambda r: r['maxAbsoluteError'])
groups.append(dict(maxAbsoluteError=worst['maxAbsoluteError'], key=worst['key'],
time=worst['worstTime'], above5PercentCount=sum(r['above5PercentCount'] for r in rows)))
quantities[quantity] = dict(beforeRaw=groups[0], afterRaw=groups[1], afterSamePhase=groups[2])
result[name] = dict(finalStateExactlyEqual=a['finalState']==b['finalState'],
solverCountersEqual=all(x==y for x,y in counter_pairs.values()), counters=counter_pairs,
originalSampleCount=len(at), newSampleCount=len(bt), extraSamples=len(bt)-len(at),
allOriginalSamplesPreserved=True, changedOriginalOutputs=differences,
solveSeconds=[a['solveSeconds'], b['solveSeconds']],
processWallSeconds=[load(before/'native-summary.json')['processWallSeconds'],
load(after/'native-summary.json')['processWallSeconds']],
phasePairing=load(after/'phase-pairing.json'), contactPhase=contact_phase,
quantities=quantities,
above5PercentCount=[old_metrics['above5PercentCount'], new_raw['above5PercentCount'], aligned['above5PercentCount']],
above5PercentOutsideEvents=aligned['above5PercentOutsideEvents'],
rawPeakContactForce=[load(before/'comparison.json')['extraEventPointMaxContactForce'], aligned['extraEventPointMaxContactForce']],
sourceVerification={s: load(out/s/'source-verification.json') for s in ('before','after')})
(out/'effect-summary.json').write_text(json.dumps(result, ensure_ascii=False, indent=2, allow_nan=False)+'\n', encoding='utf-8')
for name, row in result.items():
print(name, json.dumps({k:v for k,v in row.items() if k not in ('phasePairing','quantities','sourceVerification')}, ensure_ascii=False))
print(json.dumps(row['quantities'], ensure_ascii=False))
if __name__ == '__main__':
main()
+57
View File
@@ -0,0 +1,57 @@
/* Audit only: every observed primitive access is accounted for. Values read
* from newly created entries are proved against the ordered writes, rather
* than dropping the consumed-field or valid-test conditions in production. */
static void contract_failure(int event_index,const char *name){
fprintf(stderr,"contract Jacobian=%llu event=%d %s\n",jac,event_index,name);fatal("typed effect schema");
}
static void fp_contract_check(void){
if(plan.error || fast.error || plan.path!=fast.path || plan.entry_count!=fast.entry_count || plan.jac!=fast.jacobian || plan.memo_binding!=(uintptr_t)fast.owner || memcmp(plan.inputs,fast.inputs,32) || memcmp(&plan.output,&fast.output,8) || plan.rounding!=fast.rounding || plan.required_flags!=fast.flags || plan.required_sse_flags!=fast.sse_flags || plan.sse_mode!=fast.sse_mode || plan.baseline_errno!=fast.saved_errno){
fprintf(stderr,"generic error=%d fast error=%d\n",plan.error,fast.error);contract_failure(-1,"metadata");
}
const Contract *schema=fast.path?schema1:schema0;
int expected_n=fast.path?132:12;
if(plan.n!=expected_n)contract_failure(-1,"length");
NativePropertyState local[2],zero={0};NativePipeCache pipe={0};memset(local,0,sizeof(local));
Pending pending;
if(fp_prepare(bound,bound_pipes,&pending))contract_failure(-1,"pending construction");
int queries=0,scalars=0,allocations=0;
for(int i=0;i<plan.n;i++){
const Event *e=&plan.events[i];const Contract *c=&schema[i];int slot=e->slot;
if(e->type==READ_STATE || e->type==WRITE_STATE || e->type==WRITE_OR || e->type==VALID || e->type==ALLOCATE)slot-=(int)fast.entry_count;
if(e->type!=c->type || slot!=c->slot || e->offset!=c->offset || e->size!=c->size || e->aux!=c->aux || e->mask!=c->mask)contract_failure(i,"ordered access signature");
if(e->type==QUERY){
const double *v=queries++?fast.down:fast.up;double key[9]={v[0],v[1]};memcpy(key+2,fp_medium_key,56);
if(memcmp(e->data,key,72))contract_failure(i,"query key");
}else if(e->type==ALLOCATE){
if(slot!=allocations++)contract_failure(i,"append order");
}else if(e->type==SCALAR){
int k=scalars++;double key[9],value;
if(k<2){const double *v=k?fast.down:fast.up;memcpy(key,v,16);memcpy(key+2,fp_medium_key,56);value=v[2];}
else {memcpy(key,fast.pipe_key,24);value=fast.pipe_value;}
if(memcmp(e->data,key,e->size*8) || memcmp(&e->result,&value,8) || fp_hash(e->slot,e->offset,key,e->size)!=fast.hashes[k])contract_failure(i,"memo key/value");
}else if(e->type==WRITE_STATE){
unsigned char expected_data[sizeof(NativePropertyState)]={0};
if(e->size==(int)sizeof(NativePropertyState))memcpy(expected_data,&zero,sizeof(zero));
else if(e->offset==(int)offsetof(NativePropertyState,valid)){unsigned valid=NATIVE_PROPERTY_PT;memcpy(expected_data,&valid,sizeof(valid));}
else memcpy(expected_data,(char*)&pending.entries[slot]+e->offset,e->size);
if(memcmp(e->data,expected_data,e->size))contract_failure(i,"property store");
memcpy((char*)&local[slot]+e->offset,expected_data,e->size);
}else if(e->type==WRITE_OR){
local[slot].valid|=e->mask;
if(memcmp(e->data,&local[slot].valid,4))contract_failure(i,"valid update");
}else if(e->type==READ_STATE){
if(memcmp(e->data,(char*)&local[slot]+e->offset,e->size))contract_failure(i,"consumed field");
}else if(e->type==VALID){
unsigned value=local[slot].valid&e->mask;
if(memcmp(e->data,&value,4))contract_failure(i,"valid test");
}else if(e->type==READ_PIPE){
if(memcmp(e->data,(char*)&pipe+e->offset,e->size))contract_failure(i,"pipe read");
}else if(e->type==WRITE_PIPE){
if(e->offset==(int)offsetof(NativePipeCache,valid)){int valid=i==plan.n-1;pipe.valid=valid;}
else memcpy((char*)&pipe+e->offset,(char*)&pending.pipe+e->offset,e->size);
if(memcmp(e->data,(char*)&pipe+e->offset,e->size))contract_failure(i,"pipe store");
}else contract_failure(i,"uncovered effect");
}
if(memcmp(&pipe,&pending.pipe,sizeof(pipe)) || (fast.path && memcmp(local,pending.entries,sizeof(local))))contract_failure(-1,"final effects");
contract_checks++;
}
+62
View File
@@ -0,0 +1,62 @@
/* Audit only. Generic interpretation is an oracle, never a native Reference. */
static SRContext oracle_generic,oracle_typed,negative_context;
static void check_oracle(NativePropertyCache *p,NativePipeCache *pipes,const double *x,int typed_reason,const Pending *patch){
int saved_errno=errno;SREnvironment env;save_environment(&env);
NativePropertyCache *saved_bound=bound;bound=p;probe_entry_count=p->count;
from_live(&oracle_generic,p,pipes);from_live(&oracle_typed,p,pipes);
int mapping[SR_STATES],appends;phase=CANDIDATE;
int generic_reason=replay_overlay(&oracle_generic,x,mapping,&appends);phase=OFF;
if(generic_reason!=typed_reason){fprintf(stderr,"decision Jacobian=%llu generic=%s typed=%s\n",jac,reasons[generic_reason],reasons[typed_reason]);fatal("guard decisions differ");}
if(!generic_reason){
/* replay_overlay already applied the generic effects in its copy. */
Pending adjusted=*patch;
for(int i=0;i<adjusted.append_count;i++)adjusted.entries[i].jacobian=&oracle_typed.memo;
fp_commit(&oracle_typed.context,oracle_typed.pipes,&adjusted);
if(compare_contexts(&oracle_generic,&oracle_typed) || memcmp(&patch->output,&plan.output,8))fatal("generic/typed patch differs");
for(int i=0;i<appends;i++)if(mapping[plan.entry_count+i]!=(int)(p->count+i))fatal("logical relocation differs");
}
bound=saved_bound;restore_environment(&env);errno=saved_errno;
}
static void negative_test(int which,NativePropertyCache *live,NativePipeCache *pipes,const double *inputs){
FastMetadata saved=fast;Plan *saved_plan=malloc(sizeof(plan));if(!saved_plan)fatal("negative metadata allocation");*saved_plan=plan;
int saved_errno=errno;SREnvironment env;save_environment(&env);
from_live(&negative_context,live,pipes);NativePropertyCache *p=&negative_context.context;
fast.owner=p->jacobian;plan.memo_binding=(uintptr_t)p->jacobian;
double x[4];memcpy(x,inputs,sizeof(x));
switch(which){
case 0:p->temperatures=&negative_context.observer;break;
case 1:p->capacity=p->count;break;
case 2:p->capacity=p->count+1;break;
case 3:negative_context.pipes[0].valid=1;break;
case 4:p->jacobian->recording=1;break;
case 5:memset(p->jacobian->entries,0,sizeof(negative_context.entries));break;
case 6:for(size_t i=0;i<p->jacobian->capacity;i++)if(p->jacobian->entries[i].hash)p->jacobian->entries[i].value=nextafter(p->jacobian->entries[i].value,INFINITY);break;
case 7:x[0]=nextafter(x[0],INFINITY);break;
case 8:fast.output=NAN;plan.output=NAN;break;
case 9:fast.jacobian++;plan.jac++;break;
case 10:feclearexcept(FE_ALL_EXCEPT);break;
case 11:fesetround(fast.rounding==FE_DOWNWARD?FE_UPWARD:FE_DOWNWARD);break;
case 12:
#if defined(__SSE__)
_mm_setcsr(env.sse^0x2000u);
#endif
break;
case 13:if(p->count)p->states[0].jacobian=NULL;break;
case 14:if(p->count){p->states[0].medium=fp_medium;p->states[0].p=fast.up[0];p->states[0].T=fast.up[1];p->states[0].valid|=NATIVE_PROPERTY_PT;}break;
case 15:if(p->count){p->states[0].medium=fp_medium;p->states[0].p=fast.down[0];p->states[0].T=fast.down[1];p->states[0].valid|=NATIVE_PROPERTY_PT;}break;
case 16:fast.error=CONSUMED;plan.error=CONSUMED;break;
case 17:fast.error=VALID_BITS;plan.error=VALID_BITS;break;
case 18:fast.error=EXISTING_UPDATE;plan.error=EXISTING_UPDATE;break;
case 19:fast.error=UNKNOWN_EFFECT;plan.error=UNKNOWN_EFFECT;break;
}
static SRContext unchanged;memcpy(&unchanged,&negative_context,sizeof(unchanged));
Pending patch;int reason=fp_validate(p,negative_context.pipes,x);if(!reason)reason=fp_prepare(p,negative_context.pipes,&patch);
if(!reason)fatal("negative test accepted");
check_oracle(p,negative_context.pipes,x,reason,&patch);
if(memcmp(&unchanged,&negative_context,sizeof(unchanged)))fatal("negative validate modified context");
negative_checks++;fast=saved;plan=*saved_plan;free(saved_plan);restore_environment(&env);errno=saved_errno;
}
static void fp_oracle(NativePropertyCache *p,NativePipeCache *pipes,const double *x,int reason,const Pending *pending){
check_oracle(p,pipes,x,reason,pending);oracle_checks++;
if(jac==200 && !force_every)for(int i=0;i<20;i++)negative_test(i,p,pipes,x);
}
+184
View File
@@ -0,0 +1,184 @@
/* Static effect contract: R288, group6, position16 only. No native calls. */
static const NativeMedium fp_medium={1,2077.2643940499802,5193.1609851249505,293.15,0,1.96e-5,293.15,79.4};
static const double fp_medium_key[]={2077.2643940499802,5193.1609851249505,293.15,0,1.96e-5,293.15,79.4};
typedef struct {
unsigned long long jacobian;
NativeJacobianScalars *owner;
size_t entry_count;
double inputs[4],output;
double up[6]; /* p,T,rho,mu,isentropic_factor,isentropic_exponent */
double down[5]; /* p,T,rho,isentropic_factor,isentropic_exponent */
double pipe_key[3],pipe_value;
uint64_t hashes[3];
int flags,rounding,saved_errno,error,ready,path;
unsigned sse_flags,sse_mode;
} FastMetadata;
typedef struct {
double queries[2][2],density_keys[2][2],density_values[2];
int queries_count,allocations,scalars,values;
} Capture;
typedef struct {
NativePropertyState entries[2];
NativePipeCache pipe;
size_t count;
unsigned long reuse[3];
double output;
int append_count;
} Pending;
static FastMetadata fast;
static Capture capture;
static uint64_t fp_hash(int kind,int medium,const double *keys,size_t n){
uint64_t h=UINT64_C(14695981039346656037)^(unsigned)kind;
h=(h^(unsigned)medium)*UINT64_C(1099511628211);
for(size_t i=0;i<n;i++){uint64_t b;memcpy(&b,&keys[i],8);h=(h^b)*UINT64_C(1099511628211);h^=h>>32;}
return h?h:1;
}
static int fp_medium_equal(const NativeMedium *m){
return m->real_helium==fp_medium.real_helium && m->R==fp_medium.R && m->cp==fp_medium.cp &&
m->Tref==fp_medium.Tref && m->slope==fp_medium.slope && m->mu==fp_medium.mu && m->muT==fp_medium.muT && m->S==fp_medium.S;
}
void fp_unknown(void){fast.error=NONFINITE;}
static void fp_begin(NativePropertyCache *p,NativePipeCache *pipes,const double *x){
memset(&fast,0,sizeof(fast));memset(&capture,0,sizeof(capture));
fast.jacobian=jac;fast.owner=p->jacobian;fast.entry_count=p->count;memcpy(fast.inputs,x,32);
fast.rounding=fegetround();fast.sse_mode=sse_control()&~63u;
if(p->temperatures)fast.error=OBSERVER;
if(pipes[0].valid)fast.error=PIPE_BRANCH;
if(!p->jacobian || p->count>p->capacity || p->capacity>SR_STATES)fast.error=CAPACITY;
}
void fp_query(NativePropertyCache *p,const NativeMedium *m,double pressure,double temperature,NativePropertyState *hit){
int i=capture.queries_count++;
if(p!=bound || i>=2 || hit || !fp_medium_equal(m)){fast.error=QUERY_PATH;return;}
if(!isfinite(pressure) || !isfinite(temperature)){fast.error=NONFINITE;return;}
capture.queries[i][0]=pressure;capture.queries[i][1]=temperature;
}
void fp_allocate(NativePropertyCache *p,NativePropertyState *s,int valid){
int i=capture.allocations++;
if(i>=2 || !valid || p!=bound || capture.queries_count!=i+1 || p->count!=fast.entry_count+(size_t)i+1 || s!=p->states+fast.entry_count+i)fast.error=CAPACITY;
}
void fp_scalar_key(int kind,int medium,const double *keys,size_t n){
int i=capture.scalars++;
if(i>=3 || (i<2?(kind!=NATIVE_JACOBIAN_DENSITY || medium!=1 || n!=9):(kind!=NATIVE_JACOBIAN_PIPE || medium!=0 || n!=3))){fast.error=UNKNOWN_EFFECT;return;}
for(size_t k=0;k<n;k++)if(!isfinite(keys[k]))fast.error=NONFINITE;
if(i<2){
if(memcmp(keys+2,fp_medium_key,sizeof(fp_medium_key)))fast.error=UNKNOWN_EFFECT;
memcpy(capture.density_keys[i],keys,16);
}else memcpy(fast.pipe_key,keys,24);
fast.hashes[i]=fp_hash(kind,medium,keys,n);
}
void fp_scalar_value(int kind,int medium,const double *keys,size_t n,double value){
int i=capture.scalars-1;
if(i<0 || i>=3 || capture.values!=i || (i<2?(kind!=NATIVE_JACOBIAN_DENSITY || medium!=1 || n!=9):(kind!=NATIVE_JACOBIAN_PIPE || medium!=0 || n!=3))){fast.error=UNKNOWN_EFFECT;return;}
if((i<2 && (memcmp(keys,capture.density_keys[i],16) || memcmp(keys+2,fp_medium_key,56))) || (i==2 && memcmp(keys,fast.pipe_key,24)))fast.error=UNKNOWN_EFFECT;
if(!isfinite(value))fast.error=NONFINITE;
if(i<2)capture.density_values[i]=value;else fast.pipe_value=value;
capture.values++;
}
static void fp_end(double output){
fast.flags=fetestexcept(FE_ALL_EXCEPT);fast.sse_flags=sse_control()&63u;fast.saved_errno=errno;fast.output=output;
if(!isfinite(output))fast.error=NONFINITE;
if(capture.queries_count==0 && capture.allocations==0 && capture.scalars==0 && capture.values==0 && bound->count==fast.entry_count)fast.path=0;
else if(capture.queries_count==2 && capture.allocations==2 && capture.scalars==3 && capture.values==3 && bound->count==fast.entry_count+2){
fast.path=1;const NativePropertyState *u=&bound->states[fast.entry_count],*d=u+1;
if(u->valid!=29 || d->valid!=21 || u->temperatures || d->temperatures || u->jacobian!=fast.owner || d->jacobian!=fast.owner || !fp_medium_equal(&u->medium) || !fp_medium_equal(&d->medium))fast.error=UNKNOWN_EFFECT;
if(memcmp(&u->p,capture.queries[0],16) || memcmp(&d->p,capture.queries[1],16) || memcmp(&u->p,capture.density_keys[0],16) || memcmp(&d->p,capture.density_keys[1],16) || memcmp(&u->rho,&capture.density_values[0],8) || memcmp(&d->rho,&capture.density_values[1],8))fast.error=CONSUMED;
const double zero=0;
if(memcmp(&u->h,&zero,8) || memcmp(&d->h,&zero,8) || memcmp(&d->mu,&zero,8))fast.error=UNKNOWN_EFFECT;
double up[]={u->p,u->T,u->rho,u->mu,u->isentropic_factor,u->isentropic_exponent};
double down[]={d->p,d->T,d->rho,d->isentropic_factor,d->isentropic_exponent};
memcpy(fast.up,up,sizeof(up));memcpy(fast.down,down,sizeof(down));
for(int i=0;i<6;i++)if(!isfinite(up[i]))fast.error=NONFINITE;
for(int i=0;i<5;i++)if(!isfinite(down[i]))fast.error=NONFINITE;
}else fast.error=NONFINITE;
const NativePipeCache *pipe=&bound_pipes[0];
const double diameter=.014,length=1,roughness=.0032142857142857142;
if(pipe->valid!=1 || pipe->kind!=1 || !fp_medium_equal(&pipe->medium) || memcmp(&pipe->p1,&fast.inputs[3],8) || memcmp(&pipe->p2,&fast.inputs[1],8) || memcmp(&pipe->T,&fast.inputs[0],8) || memcmp(&pipe->diameter,&diameter,8) || memcmp(&pipe->length,&length,8) || memcmp(&pipe->roughness,&roughness,8) || memcmp(&pipe->flow,&output,8))fast.error=UNKNOWN_EFFECT;
fast.ready=1;
}
/* Exact ordered PT matching against the live prefix. The second query also
* sees the first virtual append; it may not silently ignore it. */
static int fp_match(const NativePropertyState *s,double p,double T){
return (s->valid&NATIVE_PROPERTY_PT) && s->p==p && s->T==T && fp_medium_equal(&s->medium);
}
static int fp_query_miss(const NativePropertyCache *p,double pressure,double temperature){
for(size_t i=0;i<p->count;i++)if(fp_match(&p->states[i],pressure,temperature))return 0;
return 1;
}
static int fp_memo(const NativeJacobianScalars *m,int index){
size_t capacity=m->capacity;
if(!capacity || capacity>MODEL_JACOBIAN_SCALAR_COUNT || (capacity&(capacity-1)) || m->recording || !m->entries)return MEMO_BINDING;
int kind=index<2?NATIVE_JACOBIAN_DENSITY:NATIVE_JACOBIAN_PIPE,medium=index<2?1:0;
size_t n=index<2?9:3,limit=capacity<32?capacity:32;
const double *key=index==0?fast.up:index==1?fast.down:fast.pipe_key;
double value=index==0?fast.up[2]:index==1?fast.down[2]:fast.pipe_value;
uint64_t hash=fast.hashes[index];
for(size_t i=0;i<limit;i++){
const NativeJacobianScalarEntry *e=&m->entries[(hash+i)&(capacity-1)];
if(!e->hash)return MEMO_MISS;
if(e->hash==hash && e->kind==kind && e->medium_kind==medium && e->input_count==n &&
(index<2?(!memcmp(e->inputs,key,16) && !memcmp(e->inputs+2,fp_medium_key,56)):!memcmp(e->inputs,key,24))){
if(!isfinite(e->value) || memcmp(&e->value,&value,8))return MEMO_VALUE;
return OK;
}
}
return MEMO_MISS;
}
static int fp_validate(NativePropertyCache *p,NativePipeCache *pipes,const double *inputs){
if(!fast.ready || fast.jacobian!=jac)return NO_RECORD;
if(fast.error)return fast.error;
if(fegetround()!=fast.rounding || (fetestexcept(FE_ALL_EXCEPT)&fast.flags)!=fast.flags ||
(fast.saved_errno && errno!=fast.saved_errno) || (sse_control()&~63u)!=fast.sse_mode ||
(sse_control()&fast.sse_flags)!=fast.sse_flags)return UNKNOWN_EFFECT;
if(memcmp(inputs,fast.inputs,32))return INPUTS;
for(int i=0;i<4;i++)if(!isfinite(inputs[i]))return NONFINITE;
if(p->temperatures)return OBSERVER;
if(p->capacity>SR_STATES || p->count>p->capacity)return CAPACITY;
if(!p->jacobian || p->jacobian!=fast.owner || !p->jacobian->entries || p->jacobian->recording || p->jacobian->capacity>MODEL_JACOBIAN_SCALAR_COUNT)return MEMO_BINDING;
for(size_t i=0;i<p->count;i++)if(p->states[i].temperatures || p->states[i].jacobian!=p->jacobian)return MEMO_BINDING;
if(pipes[0].valid)return PIPE_BRANCH;
if(fast.path==1){
if(!fp_query_miss(p,fast.up[0],fast.up[1]))return QUERY_PATH;
if(p->count>=p->capacity)return CAPACITY;
int reason=fp_memo(p->jacobian,0);if(reason)return reason;
if(!fp_query_miss(p,fast.down[0],fast.down[1]))return QUERY_PATH;
/* The virtual U entry already has PT at the time query D runs. */
if(fast.up[0]==fast.down[0] && fast.up[1]==fast.down[1])return QUERY_PATH;
if(p->count+1>=p->capacity)return CAPACITY;
reason=fp_memo(p->jacobian,1);if(reason)return reason;
reason=fp_memo(p->jacobian,2);if(reason)return reason;
}else if(fast.path!=0)return NONFINITE;
return OK;
}
static int fp_prepare(NativePropertyCache *p,NativePipeCache *pipes,Pending *pending){
pending->append_count=fast.path?2:0;pending->count=p->count+(size_t)pending->append_count;
pending->reuse[0]=0;pending->reuse[1]=fast.path?2:0;pending->reuse[2]=fast.path?1:0;
if(fast.path){
NativePropertyState *u=&pending->entries[0],*d=&pending->entries[1];
memset(pending->entries,0,sizeof(pending->entries));
u->medium=fp_medium;u->p=fast.up[0];u->T=fast.up[1];u->rho=fast.up[2];u->mu=fast.up[3];u->isentropic_factor=fast.up[4];u->isentropic_exponent=fast.up[5];u->valid=29;u->jacobian=p->jacobian;
d->medium=fp_medium;d->p=fast.down[0];d->T=fast.down[1];d->rho=fast.down[2];d->isentropic_factor=fast.down[3];d->isentropic_exponent=fast.down[4];d->valid=21;d->jacobian=p->jacobian;
}
/* Copy ONE pipe so even its unwritten padding is preserved. */
pending->pipe=pipes[0];pending->pipe.medium=fp_medium;
pending->pipe.p1=fast.inputs[3];pending->pipe.p2=fast.inputs[1];pending->pipe.T=fast.inputs[0];
pending->pipe.diameter=.014;pending->pipe.length=1;pending->pipe.roughness=.0032142857142857142;pending->pipe.kind=1;
pending->pipe.flow=fast.output;pending->pipe.valid=1;pending->output=fast.output;
if(!isfinite(fast.output))return NONFINITE;
return OK;
}
static void fp_commit(NativePropertyCache *p,NativePipeCache *pipes,const Pending *pending){
if(pending->append_count){
memcpy(&p->states[p->count],pending->entries,sizeof(pending->entries));p->count=pending->count;
p->jacobian->reuses[1]+=pending->reuse[1];p->jacobian->reuses[2]+=pending->reuse[2];
}
pipes[0].valid=0;pipes[0]=pending->pipe;
}
#if RR_AUDIT
/* Full generic guard and event contracts exist only in correctness builds. */
static unsigned long long contract_checks,oracle_checks,negative_checks;
static void fp_contract_check(void);
static void fp_oracle(NativePropertyCache*,NativePipeCache*,const double*,int,const Pending*);
#endif
+6
View File
@@ -0,0 +1,6 @@
/* Only the separately named baseline recording kernels call these hooks. */
void fp_unknown(void);
void fp_query(NativePropertyCache*,const NativeMedium*,double,double,NativePropertyState*);
void fp_allocate(NativePropertyCache*,NativePropertyState*,int);
void fp_scalar_key(int,int,const double*,size_t);
void fp_scalar_value(int,int,const double*,size_t,double);
+40
View File
@@ -0,0 +1,40 @@
double rr_execute(NativePropertyCache *p,NativePipeCache *pipes,const double *x,SROperation original){
visits++;
#if RR_AUDIT
audit_context(1,0,NULL,NULL,NULL,1,0,p,pipes,NULL);entry_audits++;
from_live(&before_reject,p,pipes);
#endif
uint64_t start=lp_tick(),tick;double output;Pending pending;size_t initial_count=p->count;
attempts++;rr_replaying=1;count_different+=(p->count!=fast.entry_count);
double saved_output=fast.output;
#if RR_AUDIT
double old_plan_output=plan.output;
if(force_every && (attempts-1)%(unsigned)force_every==0){fast.output=NAN;plan.output=NAN;}
#endif
tick=lp_tick();int reason=fp_validate(p,pipes,x);validation_ticks+=lp_tick()-tick;
if(!reason){tick=lp_tick();reason=fp_prepare(p,pipes,&pending);overlay_ticks+=lp_tick()-tick;}
#if RR_AUDIT
fp_oracle(p,pipes,x,reason,&pending);
plan.output=old_plan_output;
#endif
fast.output=saved_output;
if(!reason){
tick=lp_tick();fp_commit(p,pipes,&pending);output=pending.output;commit_ticks+=lp_tick()-tick;
commits++;successes++;appends_total+=pending.append_count;paths[fast.path]++;
relocations+=(fast.path && fast.entry_count!=initial_count);
metadata_min=metadata_max=sizeof(fast);
}else{
rejects++;reject_reason[reason]++;rr_replaying=0;
#if RR_AUDIT
from_live(&after_reject,p,pipes);
if(compare_contexts(&before_reject,&after_reject))fatal("reject altered live context");
rollback_checks++;
#endif
tick=lp_tick();rr_original_scope=1;output=original(p,pipes,x);rr_original_scope=0;fallback_ticks+=lp_tick()-tick;fallbacks++;
}
rr_replaying=0;path_ticks+=lp_tick()-start;
#if RR_AUDIT
audit_context(2,0,NULL,NULL,NULL,1,output,p,pipes,NULL);exit_audits++;
#endif
return output;
}
+63
View File
@@ -0,0 +1,63 @@
/* Diagnostic worker only: original tail executes on every visit. */
#include "valve_tail_diag.h"
#include <windows.h>
#include <cpuid.h>
#include <stdio.h>
#include <stdlib.h>
#include <fenv.h>
#include <math.h>
#include <stddef.h>
int kd_scope;
uint64_t kd_jac;
KdMemo kd_memo;
static KdRecord records[26000];
static size_t count;
static uint64_t entries,hits,executions,lookup_ticks,capture_ticks,reset_ticks,tail_ticks;
static uint64_t base_lookup_ticks,miss_lookup_ticks,hit_lookup_ticks;
static uint64_t qpc_frequency,tsc_start,qpc_start;
static FILE *matrix_file;
static int disabled;
static void fatal(const char *s){fprintf(stderr,"valve tail: %s\n",s);abort();}
void kd_eval_exit(double t,const double*y,const double*dy,const double*w,int r,NativePropertyCache*p,NativePipeCache*c,ModelJacobianWorkspace*m){(void)t;(void)y;(void)dy;(void)w;(void)r;(void)p;(void)c;(void)m;}
void kd_start(void){
unsigned a,b,c,d;if(!__get_cpuid(0x80000007,&a,&b,&c,&d) || !(d&(1u<<8)))fatal("invariant TSC required");
LARGE_INTEGER f;QueryPerformanceFrequency(&f);qpc_frequency=(uint64_t)f.QuadPart;
qpc_start=lp_tick();tsc_start=kd_clock();disabled=getenv("KD_DISABLED")!=NULL;
if(getenv("KD_MATRICES")){matrix_file=fopen("jacobians.bin","wb");if(!matrix_file)fatal("matrix file");setvbuf(matrix_file,NULL,_IOFBF,1024*1024);}
}
void kd_new_jac(void){uint64_t t=kd_clock();kd_memo.ready=0;reset_ticks+=kd_clock()-t;kd_jac++;}
void kd_before(KdRecord *r,double p,double T,double pd,double g,double rho){
double key[5]={p,T,pd,g,rho};memcpy(r->key,key,40);r->before=kd_env();
r->hit=lp_color>=0 && !disabled && kd_lookup(r->key,r->before,&r->cm,&r->vel);
r->jac=kd_jac;r->group=lp_color;
}
void kd_after(KdRecord *r,double cm,double vel,uint64_t elapsed,int executed){
uint64_t capture_start=lp_color<0?kd_clock():0;
r->after=kd_env();r->cm=cm;r->vel=vel;r->tail=elapsed;r->executed=executed;r->capture=0;r->pad=0;
if(lp_color<0){
memcpy(kd_memo.key,r->key,40);kd_memo.cm=cm;kd_memo.vel=vel;kd_memo.env=r->before;
kd_memo.ready=kd_finite(r->key) && isfinite(cm) && isfinite(vel) && kd_env_equal(r->before,r->after) && (r->before.cw&63)==63 && (r->before.mxcsr&0x1f80)==0x1f80;
r->capture=kd_clock()-capture_start;capture_ticks+=r->capture;
}
entries++;lookup_ticks+=r->lookup;if(lp_color<0)base_lookup_ticks+=r->lookup;else if(r->hit)hit_lookup_ticks+=r->lookup;else miss_lookup_ticks+=r->lookup;
hits+=r->hit;executions+=executed;tail_ticks+=elapsed;
#if KD_TRACE
if(count>=sizeof(records)/sizeof(*records)){fatal("record capacity");}
records[count++]=*r;
#endif
}
void kd_matrix(double t,const double*y,const double*m){
if(!matrix_file){return;}
int saved=errno;fenv_t env;fegetenv(&env);unsigned sse=_mm_getcsr();
fwrite(&t,8,1,matrix_file);fwrite(y,8,NSTATES,matrix_file);fwrite(m,8,NSTATES*NSTATES,matrix_file);
fesetenv(&env);_mm_setcsr(sse);errno=saved;
}
void kd_finish(void){
uint64_t t=kd_clock(),q=lp_tick();double frequency=(double)(t-tsc_start)/(q-qpc_start)*qpc_frequency;
uint64_t empty[10001];for(int i=0;i<10001;i++){uint64_t a=kd_clock();empty[i]=kd_clock()-a;}
FILE *f=fopen("tail-records.bin","wb");if(!f)fatal("records");fwrite(records,sizeof(KdRecord),count,f);fclose(f);
f=fopen("empty-clock.bin","wb");fwrite(empty,8,10001,f);fclose(f);
f=fopen("tail.json","wb");if(!f)fatal("summary");
fprintf(f,"{\"frequency\":%.9f,\"recordBytes\":%llu,\"memoBytes\":%llu,\"jacobians\":%llu,\"entries\":%llu,\"hits\":%llu,\"executions\":%llu,\"lookupTicks\":%llu,\"hitLookupTicks\":%llu,\"missLookupTicks\":%llu,\"baseLookupTicks\":%llu,\"captureTicks\":%llu,\"resetTicks\":%llu,\"tailTicks\":%llu,\"skipEnabled\":%d,\"disabled\":%d}\n",frequency,(unsigned long long)sizeof(KdRecord),(unsigned long long)sizeof(KdMemo),(unsigned long long)kd_jac,(unsigned long long)entries,(unsigned long long)hits,(unsigned long long)executions,(unsigned long long)lookup_ticks,(unsigned long long)hit_lookup_ticks,(unsigned long long)miss_lookup_ticks,(unsigned long long)base_lookup_ticks,(unsigned long long)capture_ticks,(unsigned long long)reset_ticks,(unsigned long long)tail_ticks,0,disabled);fclose(f);
if(matrix_file)fclose(matrix_file);
}
+35
View File
@@ -0,0 +1,35 @@
#ifndef VALVE_TAIL_DIAG_H
#define VALVE_TAIL_DIAG_H
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0600
#endif
#include "local_probe.h"
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <xmmintrin.h>
#ifndef KD_TRACE
#define KD_TRACE 1
#endif
typedef struct {unsigned mxcsr;unsigned short cw,sw;int err;} KdEnv;
typedef struct {uint64_t key[5];double cm,vel;KdEnv env;int ready;} KdMemo;
typedef struct {uint64_t jac;int group,hit;uint64_t key[5];double cm,vel;KdEnv before,after;uint64_t tail,lookup,capture;int executed,pad;} KdRecord;
extern int kd_scope;
extern uint64_t kd_jac;
extern KdMemo kd_memo;
static inline uint64_t kd_clock(void){unsigned lo,hi;__asm__ __volatile__("lfence\n\trdtsc\n\tlfence":"=a"(lo),"=d"(hi)::"memory");return ((uint64_t)hi<<32)|lo;}
static inline KdEnv kd_env(void){KdEnv e;__asm__ __volatile__("stmxcsr %0;fnstcw %1;fnstsw %2":"=m"(e.mxcsr),"=m"(e.cw),"=m"(e.sw)::"memory");e.err=errno;return e;}
static inline int kd_env_equal(KdEnv a,KdEnv b){return a.mxcsr==b.mxcsr && a.cw==b.cw && a.sw==b.sw && a.err==b.err;}
static inline int kd_finite(const uint64_t *key){for(int i=0;i<5;i++)if((key[i]&UINT64_C(0x7ff0000000000000))==UINT64_C(0x7ff0000000000000))return 0;return 1;}
/* Diagnostic-only lookup: its answer NEVER bypasses the original tail.
* Only a baseline entry is considered. Probe misses never populate the slot. */
static inline int kd_lookup(const uint64_t *key,KdEnv e,double *cm,double *vel){
if(!kd_memo.ready || !kd_env_equal(e,kd_memo.env) || memcmp(key,kd_memo.key,40))return 0;
*cm=kd_memo.cm;*vel=kd_memo.vel;return 1;
}
void kd_start(void);void kd_finish(void);void kd_new_jac(void);
void kd_before(KdRecord*,double,double,double,double,double);
void kd_after(KdRecord*,double,double,uint64_t,int);
void kd_matrix(double,const double*,const double*);
void kd_eval_exit(double,const double*,const double*,const double*,int,NativePropertyCache*,NativePipeCache*,ModelJacobianWorkspace*);
#endif
@@ -0,0 +1,56 @@
"""Negative checks of the evidence validator (not a production reuse guard)."""
from copy import deepcopy
import json
import analyze_context_access as ax
def rejected(events):
try:
ax.analyze_operation(events)
except AssertionError:
return True
return False
def main():
examples=json.loads((ax.OUT/'jacobian-200.json').read_text(encoding='utf-8'))
original=next(x['events'] for x in examples if x['summary']['group']==6)
assert ax.analyze_operation(original)['replayExact']
results={}
events=deepcopy(original)
index=next(i for i,e in enumerate(events) if e['event']=='write' and e.get('domain')=='pipes' and e.get('field')=='cache->valid')
del events[index]
results['omitted_equal_value_pipe_valid_store']=rejected(events)
events=deepcopy(original)
event=next(e for e in events if e['event']=='read' and e.get('field')=='s->rho')
event['value']='0000000000000000'
results['wrong_consumed_rho']=rejected(events)
events=deepcopy(original)
event=next(e for e in events if e['event']=='allocate')
event['slot']-=1
results['baseline_slot_used_for_shifted_append']=rejected(events)
events=deepcopy(original)
event=next(e for e in events if e['event']=='write' and e.get('field')=='cache->count')
event['value']=(13).to_bytes(8,'little').hex()
results['restored_baseline_count']=rejected(events)
events=deepcopy(original)
event=next(e for e in events if e['event']=='match')
event.update(hit=True,slot=0)
results['wrong_first_match']=rejected(events)
events=deepcopy(original)
event=next(e for e in events if e['event']=='valid_test' and e['mask']==8)
event['value']=8
results['wrong_valid_bit_result']=rejected(events)
assert all(results.values()), results
(ax.OUT/'negative-checks.json').write_text(json.dumps(results,indent=2)+'\n',encoding='utf-8')
print(json.dumps(results,indent=2))
if __name__=='__main__':main()
+45
View File
@@ -0,0 +1,45 @@
"""Conservative-graph contracts of the opt-in manual local-probe experiment."""
import importlib.util
from pathlib import Path
import unittest
from types import SimpleNamespace
from app.simulation.native_codegen.jacobian import StateDependencies
spec=importlib.util.spec_from_file_location('local_probe_experiment',Path(__file__).parent/'manual/local_probe_experiment.py')
experiment=importlib.util.module_from_spec(spec)
spec.loader.exec_module(experiment)
class LocalProbeDependenciesTests(unittest.TestCase):
def test_unknown_leaf_taints_every_state_and_transitive_consumer(self):
d=StateDependencies(3);d.assign('p[0]',{'unregistered_context'});d.assign('q[0]',{'p[0]'})
masks,unknown=experiment.dependency_masks(d)
self.assertEqual(unknown,{'unregistered_context'})
self.assertEqual(masks['q[0]'],0b111)
def test_union_of_branch_writes_is_preserved(self):
d=StateDependencies(3);d.assign('w[0]',{'y[0]'});d.assign('w[0]',{'y[2]'})
d.assign('q[0]',{'w[0]'})
masks,_=experiment.dependency_masks(d)
self.assertEqual(masks['q[0]'],0b101)
def test_projected_state_coupling_reaches_downstream_equations(self):
d=StateDependencies(3);d.project_states([0,1]);d.assign('p[0]',{'y[0]'})
d.assign('dy[2]',{'p[0]'})
masks,_=experiment.dependency_masks(d)
self.assertEqual(masks['dy[2]'],0b011)
def test_dependency_cycle_conservatively_reaches_fixed_point(self):
d=StateDependencies(3);d.assign('p[0]',{'q[0]','y[0]'})
d.assign('q[0]',{'p[0]','y[2]'})
masks,_=experiment.dependency_masks(d)
self.assertEqual(masks['p[0]'],0b101)
self.assertEqual(masks['q[0]'],0b101)
def test_experiment_refuses_to_slice_cyclic_schedule(self):
d=StateDependencies(2);d.expression('dy[0]','y[0]');d.expression('dy[1]','y[1]')
program=SimpleNamespace(manifest=lambda:{'jacobianStructure':{'reuse':{'enabled':True}}})
saved={'dependencies':d,'schedule':SimpleNamespace(blocks=[SimpleNamespace(cyclic=True)])}
with self.assertRaisesRegex(ValueError,'rejects SCCs'):
experiment.generate(program,saved)
if __name__=='__main__':unittest.main()
+246
View File
@@ -0,0 +1,246 @@
"""Independent analytic and Amesim-reference checks for opt-in event variants."""
import json
import math
import time
import subprocess
import sys
from pathlib import Path
import unittest
from unittest.mock import patch
import numpy as np
from app.simulation.config import SolveIVPConfig
from app.simulation.native_codegen import build as builder, result_storage
from app.simulation.native_codegen.compiler import compile_native_program
from app.simulation.native_codegen.extended import compile_extended_program
from app.simulation.native_codegen.runner import execute_native
from tests.manual.mechanical_event_variant import event_program, prepare_runtime
from tests.test_native_catalog import Circuit
ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT/'test/mechanical-events-20260917/isolated'
CONTROL = ROOT/'test/mechanical-events-20260917/source-control/native'
if not CONTROL.exists():
CONTROL = ROOT/'native'
def free_ports(b, mass, connected=()):
for port in ('port_1','port_2'):
if port not in connected:
free=b.add('amesim_f000',mass.name+'_'+port+'_free')
b.connect(mass,port,free,'port_1')
def elastic_mass(damping=1, signed=1, pdis=0):
b=Circuit()
mass=b.add('amesim_mecmas21','mass',mass=1,useFriction=1,stoptype=2,x0=.5,v0=1,
xmin=0,xmax=1,Kbmin=100,Kbmax=100,Dbmin=damping,Dbmax=damping,
Pdmin=pdis,Pdmax=pdis,discContactOption=signed)
free_ports(b,mass)
return b
def contact_pair(damping=0,signed=1,pdis=0,x2=.2,v1=1,v2=0):
b=Circuit()
a=b.add('amesim_mecmas21','left',mass=1,useFriction=1,stoptype=4,x0=0,v0=v1,
fcoul=0,fstick=0,rvisc=0,wind=0,theta=0)
z=b.add('amesim_mecmas21','right',mass=1,useFriction=1,stoptype=4,x0=x2,v0=v2,
fcoul=0,fstick=0,rvisc=0,wind=0,theta=0)
c=b.add('amesim_lstp00a','contact',gap0=0,stiffmode=1,kcont=100,rcont=damping,
Pdis=pdis,discContactOption=signed)
b.connect(a,'port_2',c,'port_1');b.connect(z,'port_1',c,'port_2')
free_ports(b,a,('port_2',));free_ports(b,z,('port_1',))
return b
class MechanicalEventExperimentTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
OUT.mkdir(parents=True,exist_ok=True)
cls.runtime=OUT/'experimental-native'
prepare_runtime(CONTROL,cls.runtime)
cls.run_tag=str(time.time_ns())
def run_case(self,b,mode,method,*,stop=1,max_step=.05,label='',compiler=compile_native_program,sample_step=.002):
original=compiler(b.net)
program,metadata=event_program(original,b.net,mode)
self.assertEqual(program.state_keys,original.state_keys)
self.assertEqual(program.jacobian_structure,original.jacobian_structure)
self.assertEqual(program.evaluation_schedule,original.evaluation_schedule)
self.assertTrue(program.source.startswith(original.source))
directory=OUT/self._testMethodName/(mode+'-'+method+label)/self.run_tag
directory.mkdir(parents=True,exist_ok=True)
(directory/'event-descriptors.json').write_text(json.dumps(metadata,indent=2)+'\n',encoding='utf-8')
with patch.object(builder,'NATIVE',CONTROL if mode=='off' else self.runtime), \
patch.object(builder,'CACHE',OUT/'build-cache'),patch.object(result_storage,'RESULT_ROOT',OUT/'results'):
build=builder.build_native(program)
try:
result=execute_native(build,SolveIVPConfig(t_stop=stop,method=method,max_step=max_step,rtol=1e-9),
sample_step,run_dir=directory,timeout=60)
finally:
build.close()
self.assertTrue(result['success'],result['message'])
self.assertEqual(result['simulatedUntil'],stop)
times=result['series']['time']
self.assertTrue(all(a<c for a,c in zip(times,times[1:])))
events=[]
for line in (directory/'worker.log').read_text(encoding='utf-8').splitlines():
try: e=json.loads(line)
except ValueError: continue
if e.get('phase')=='mechanical-event':events.append(e)
return result,events
def test_mass_elastic_upper_and_lower_contacts_analytic(self):
duration=math.pi/math.sqrt(100-.25)
restitution=math.exp(-.5*duration)
expected=[.5,.5+duration,.5+duration+1/restitution,.5+2*duration+1/restitution]
for method in ('BDF','RK45'):
for h in (.08,.02):
r,events=self.run_case(elastic_mass(),'mass',method,stop=2.5,max_step=h,label=str(h))
hits=[e for e in events if e['kind']=='mass-contact']
self.assertEqual([e['direction'] for e in hits],[1,-1,1,-1])
np.testing.assert_allclose([e['time'] for e in hits],expected,rtol=0,atol=2e-6)
# Elastic contact must not apply a hard-stop velocity reset.
np.testing.assert_allclose([abs(e['relativeVelocity']) for e in hits],
[1,restitution,restitution,restitution**2],rtol=0,atol=2e-6)
def test_mass_ideal_release_both_sides_analytic(self):
for upper in (False,True):
b=Circuit();sign=-1 if upper else 1
drive=b.add('amesim_ud00','drive',nstages=1,iscyclic=0,start1=-sign,end1=sign,t1=2)
force=b.add('amesim_forc','force',direction=1)
mass=b.add('amesim_mecmas21','mass',mass=1,useFriction=1,stoptype=1,
xmin=0,xmax=1,x0=float(upper),v0=0)
b.connect(drive,'out',force,'res');b.connect(force,'port_2',mass,'port_1');free_ports(b,mass,('port_1',))
for method in ('BDF','RK45'):
r,events=self.run_case(b,'mass',method,stop=1.5,max_step=.4,label=str(upper))
releases=[e for e in events if e['kind']=='mass-release']
self.assertEqual(len(releases),1)
self.assertAlmostEqual(releases[0]['time'],1,delta=1e-8)
self.assertAlmostEqual(r['final']['mass.v'],sign*.125,delta=2e-6)
self.assertAlmostEqual(r['final']['mass.x'],float(upper)+sign/48,delta=2e-6)
def test_mass_existing_restitution_impact_is_preserved(self):
b=Circuit()
mass=b.add('amesim_mecmas21','mass',mass=1,useFriction=1,stoptype=3,
xmin=0,xmax=1,x0=.9,v0=1,restcoeff=.5,restdvel=.001)
free_ports(b,mass)
for method in ('BDF','RK45'):
baseline,_=self.run_case(b,'off',method,stop=.4,max_step=.02)
r,events=self.run_case(b,'mass',method,stop=.4,max_step=.02)
self.assertEqual(r['series'],baseline['series'])
self.assertEqual(r['finalState'],baseline['finalState'])
self.assertEqual(r['solverStarts'],baseline['solverStarts'])
self.assertEqual(events,[])
np.testing.assert_allclose(r['finalState'],[-.5,.85],rtol=0,atol=2e-6)
def test_lstp_elastic_collision_conserves_momentum_and_energy(self):
expected=[.2,.2+math.pi/math.sqrt(200)]
for method in ('BDF','RK45'):
for h in (.06,.015):
r,events=self.run_case(contact_pair(),'lstp',method,stop=.7,max_step=h,label=str(h))
hits=[e for e in events if e['kind']=='lstp-contact']
self.assertEqual([e['direction'] for e in hits],[1,-1])
np.testing.assert_allclose([e['time'] for e in hits],expected,rtol=0,atol=2e-6)
v1=np.array(r['series']['left.v']);v2=np.array(r['series']['right.v'])
penetration=np.maximum(-np.array(r['series']['contact.gap']),0)
np.testing.assert_allclose(v1+v2,1,rtol=0,atol=1e-9)
np.testing.assert_allclose(.5*(v1*v1+v2*v2)+50*penetration**2,.5,rtol=0,atol=2e-6)
self.assertAlmostEqual(r['final']['left.v'],0,delta=2e-6)
self.assertAlmostEqual(r['final']['right.v'],1,delta=2e-6)
def test_nonnegative_force_clip_is_local_event_mass_and_lstp(self):
for label,b,mode in (('mass',elastic_mass(10,2),'mass'),('lstp',contact_pair(10,2),'lstp')):
for method in ('BDF','RK45'):
r,events=self.run_case(b,mode,method,stop=1.05,label=label)
clips=[e for e in events if e['kind']=='force-clip']
self.assertTrue(clips)
self.assertTrue(all(e['penetration']>0 for e in clips))
key='mass.Fmax' if label=='mass' else 'contact.force'
self.assertGreaterEqual(min(r['series'][key]),0)
if label=='mass':
expected=.5+(math.pi-math.atan(math.sqrt(3)))/math.sqrt(75)
self.assertAlmostEqual(clips[0]['time'],expected,delta=3e-6)
def test_no_contact_initial_touch_and_continuous_contact_do_not_restart(self):
cases=[('apart',contact_pair(v1=.1,v2=.1),.3),('touch',contact_pair(x2=0,v1=0,v2=0),.3),
('inside',contact_pair(x2=-.1,v1=0,v2=0),.01)]
for label,b,stop in cases:
a,_=self.run_case(b,'off','BDF',stop=stop,label=label)
z,events=self.run_case(b,'lstp','BDF',stop=stop,label=label)
self.assertEqual(events,[])
self.assertEqual(a['finalState'],z['finalState'])
self.assertEqual(a['series'],z['series'])
self.assertEqual(a['solverStarts'],z['solverStarts'])
def test_exponential_damping_modes_and_extended_lowering(self):
for signed in (1,2):
for mode,b in (('mass',elastic_mass(2,signed,.01)),('lstp',contact_pair(2,signed,.01))):
for method in ('BDF','RK45'):
r,events=self.run_case(b,mode,method,stop=1.1,label=str(signed),compiler=compile_extended_program)
self.assertGreaterEqual(len(events),2)
self.assertLess(len(events),15)
self.assertEqual(r['experimentalEvents']['releaseRhsCalls'],0)
def test_existing_amesim_mechanical_reference_circuits(self):
cases=json.loads((ROOT/'tests/fixtures/amesim-component-semantics-2404.json').read_text(encoding='utf-8'))['cases']
for case in cases:
if case['name'] not in ('friction_ideal','friction_elastic','joint_mechanical'):continue
b=Circuit()
for c in case['components']:b.add(c['type'],c['name'],**c['parameters'])
for left,right in case['connections']:b.net.connect(*left,*right)
modes=('mass',) if case['name']!='joint_mechanical' else ('lstp','all')
for mode in modes:
r,_=self.run_case(b,mode,'BDF',stop=3,max_step=.002,label=case['name'],sample_step=.001)
for key,expected in case['series'].items():
actual=np.interp(case['time'],r['series']['time'],r['series'][key])
field=key.split('.')[-1]
atol=.002 if field in ('a','Ffric','Fmin','Fmax','force') else 3e-6
np.testing.assert_allclose(actual,expected,rtol=0,atol=atol,err_msg=case['name']+'/'+mode+'/'+key)
def test_same_sign_gap_excursion_and_tangent_with_analytic_dense_output(self):
directory=OUT/self._testMethodName/self.run_tag;directory.mkdir(parents=True)
header='''#include "kernels.h"
#define NSTATES 2
#define NOUTPUTS 1
#define NSTOPS 0
#define NEXPERIMENT_CONTACTS 1
#define NEXPERIMENT_RELEASES 0
typedef struct { int kind,v1,v2;double boundary,stiffness,damping,pdis;int signed_force; } ExperimentContact;
extern const ExperimentContact experiment_contacts[1];
'''
(directory/'model.h').write_text(header,encoding='ascii')
harness=r'''
#include "runtime.h"
#include <math.h>
#include <string.h>
#include "experimental_events.c"
const ExperimentContact experiment_contacts[1]={{2,0,-1,0,100,1,0,1}};
static int polynomial(void *context,double t,double *y) {
y[0]=1-2*t;y[1]=(*(int*)context?0:.01)-(t-.5)*(t-.5);return 1;
}
int main(void) {
for(int tangent=0;tangent<2;tangent++) {
NativeRun r={0};double a[2],b[2],when[8],t=0;int indices[8],kinds[8],count=0;
polynomial(&tangent,t,a);polynomial(&tangent,1,b);
if(!experiment_candidates(&r,t,1,a,b,polynomial,&tangent,when,indices,kinds,&count))return 1;
if(tangent) {if(count)return 2;continue;}
if(count!=1 || fabs(when[0]-.4)>1e-12)return 3;
t=when[0];polynomial(&tangent,t,a);experiment_commit(&r,t,a,when,indices,kinds,count);
count=0;
if(!experiment_candidates(&r,t,1,a,b,polynomial,&tangent,when,indices,kinds,&count))return 4;
if(count!=1 || fabs(when[0]-.6)>1e-12)return 5;
}
return 0;
}
'''
(directory/'check.c').write_text(harness,encoding='ascii')
cc,_,_=builder.toolchain();exe=directory/('check.exe' if sys.platform=='win32' else 'check')
builder._command([cc,*builder.COMPILER_FLAGS,'-I',str(directory),'-I',str(self.runtime/'include'),
'-I',str(self.runtime/'runtime'),str(directory/'check.c'),'-lm','-o',str(exe)],log=[])
r=subprocess.run([str(exe)],capture_output=True,text=True,timeout=15)
self.assertEqual(r.returncode,0,r.stderr)
if __name__=='__main__':unittest.main()
+176
View File
@@ -0,0 +1,176 @@
"""Production LSTP event regression against analytic dynamics and Amesim."""
from concurrent.futures import ThreadPoolExecutor
from dataclasses import replace
import json
import math
from pathlib import Path
import re
import subprocess
import sys
import time
import unittest
from unittest.mock import patch
import numpy as np
from app.simulation.config import SolveIVPConfig
from app.simulation.native_codegen import build as builder, result_storage
from app.simulation.native_codegen.compiler import compile_native_program
from app.simulation.native_codegen.contacts import contact_table
from app.simulation.native_codegen.extended import compile_extended_program
from app.simulation.native_codegen.runner import execute_native
from tests.test_mechanical_event_experiment import contact_pair
from tests.test_native_catalog import Circuit
ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT/'test/lstp-mainline-20260917/unit'
class NativeContactEventsTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
OUT.mkdir(parents=True, exist_ok=True)
cls.tag = str(time.time_ns())
def run_case(self, b, method='BDF', stop=.7, h=.02, compiler=compile_native_program,
disabled=False, label='', record=True, step=.002):
program = compiler(b.net)
if disabled:
program = replace(program, header=re.sub(r'#define NCONTACTS \d+', '#define NCONTACTS 0', program.header))
path = OUT/self._testMethodName/self.tag/(method+compiler.__name__+str(disabled)+label)
with patch.object(builder, 'CACHE', OUT/'cache'), \
patch.object(result_storage, 'RESULT_ROOT', OUT/'results'), \
patch.object(builder, 'ThreadPoolExecutor', lambda **kw: ThreadPoolExecutor(max_workers=1)):
build = builder.build_native(program)
try:
r = execute_native(build, SolveIVPConfig(t_stop=stop, method=method, max_step=h, rtol=1e-9),
step, run_dir=path, record_samples=record, timeout=60)
finally:
build.close()
self.assertTrue(r['success'], r['message'])
events = []
for line in (path/'worker.log').read_text(encoding='utf-8').splitlines():
try: e = json.loads(line)
except ValueError: continue
if e.get('phase') == 'mechanical-event': events.append(e)
return r, events
def test_elastic_collision_analytic_and_conservation(self):
for compiler in (compile_native_program, compile_extended_program):
for method in ('BDF', 'RK45'):
for h in (.06, .015):
r, events = self.run_case(contact_pair(), method, h=h, compiler=compiler, label=str(h))
self.assertEqual([e['direction'] for e in events], [1, -1])
np.testing.assert_allclose([e['time'] for e in events], [.2, .2+math.pi/math.sqrt(200)], atol=2e-6, rtol=0)
v1=np.array(r['series']['left.v']);v2=np.array(r['series']['right.v'])
p=np.maximum(-np.array(r['series']['contact.gap']),0)
np.testing.assert_allclose(v1+v2,1,atol=1e-9,rtol=0)
np.testing.assert_allclose(.5*(v1*v1+v2*v2)+50*p*p,.5,atol=2e-6,rtol=0)
self.assertAlmostEqual(r['final']['left.v'],0,delta=2e-6)
self.assertAlmostEqual(r['final']['right.v'],1,delta=2e-6)
def test_nonnegative_force_clips_before_detachment(self):
for method in ('BDF','RK45'):
r,events=self.run_case(contact_pair(10,2),method)
clips=[e for e in events if e['kind']=='force-clip']
self.assertEqual(len(clips),1)
self.assertAlmostEqual(clips[0]['time'],.2+math.pi/20,delta=3e-6)
self.assertGreater(clips[0]['penetration'],0)
self.assertGreaterEqual(min(r['series']['contact.force']),0)
def test_geometry_stiffness_uses_same_event_parameters(self):
b=contact_pair();contact=b.net.components['contact']
# Independent geometry: G*d^4/(8*D^3*n) = 100 N/m exactly.
for name,value in dict(stiffmode=2,G=51200,sdiam=.5,wdiam=.25,na=2,kcont=1e9).items():
setattr(contact,name,value)
contact.set_parameter_values({p.name:getattr(contact,p.name) for p in contact.PARAMETERS})
for method in ('BDF','RK45'):
r,events=self.run_case(b,method)
np.testing.assert_allclose([e['time'] for e in events],[.2,.2+math.pi/math.sqrt(200)],atol=2e-6,rtol=0)
self.assertAlmostEqual(r['final']['left.v'],0,delta=2e-6)
self.assertAlmostEqual(r['final']['right.v'],1,delta=2e-6)
def test_exponential_damping_modes(self):
for option in (1,2):
for method in ('BDF','RK45'):
r,events=self.run_case(contact_pair(2,option,.01),method,stop=1.1,label=str(option))
self.assertGreaterEqual(len(events),2)
self.assertLess(len(events),15)
self.assertNotIn('experimentalEvents',r)
def test_no_events_preserve_trajectory(self):
for label,b,stop in [('apart',contact_pair(v1=.1,v2=.1),.3),
('touch',contact_pair(x2=0,v1=0,v2=0),.3),
('inside',contact_pair(x2=-.1,v1=0,v2=0),.01)]:
r,events=self.run_case(b,stop=stop,label=label)
base,_=self.run_case(b,stop=stop,disabled=True,label=label)
self.assertEqual(events,[])
for key in ('series','finalState','solverStarts','nfev'):
self.assertEqual(r[key],base[key])
def test_output_replay_does_not_change_events_or_state(self):
for method in ('BDF','RK45'):
r,events=self.run_case(contact_pair(),method)
bare,bare_events=self.run_case(contact_pair(),method,label='bare',record=False)
for key in ('finalState','contactEvents','nfev','acceptedSteps','solverStarts'):
self.assertEqual(r[key],bare[key])
self.assertEqual(events,bare_events)
times=r['series']['time']
self.assertTrue(all(a<b for a,b in zip(times,times[1:])))
for event in events:
self.assertIn(event['time'],times)
self.assertIn(math.nextafter(event['time'],-math.inf),times)
def test_existing_amesim_joint_mechanical(self):
cases=json.loads((ROOT/'tests/fixtures/amesim-component-semantics-2404.json').read_bytes())['cases']
case=next(c for c in cases if c['name']=='joint_mechanical')
b=Circuit()
for c in case['components']:b.add(c['type'],c['name'],**c['parameters'])
for left,right in case['connections']:b.net.connect(*left,*right)
r,_=self.run_case(b,stop=3,h=.002,step=.001)
for key,expected in case['series'].items():
actual=np.interp(case['time'],r['series']['time'],r['series'][key])
atol=.002 if key.split('.')[-1] in ('a','Ffric','Fmin','Fmax','force') else 3e-6
np.testing.assert_allclose(actual,expected,atol=atol,rtol=0,err_msg=key)
def test_dense_excursion_tangent_and_expression_association(self):
path=OUT/self._testMethodName/self.tag;path.mkdir(parents=True)
_,header=contact_table([(0,2,0,100,1,0,1,0)])
(path/'model.h').write_text('#define NSTATES 4\n#define NOUTPUTS 1\n#define NSTOPS 0\n'+header,encoding='ascii')
code=r'''
#include "runtime.h"
#include <math.h>
#include "contact_events.h"
const NativeContact model_contacts[1]={{0,2,0,100,1,0,1,0}};
static int polynomial(void *context,double t,double *y) {
y[0]=1-2*t;y[1]=(*(int*)context?0:.01)-(t-.5)*(t-.5);y[2]=y[3]=0;return 1;
}
int main(void) {
for(int tangent=0;tangent<2;tangent++) {
NativeRun r={0};double a[4],b[4],when[8],t=0;int indices[8],kinds[8],count=0;
polynomial(&tangent,t,a);polynomial(&tangent,1,b);
if(!contact_candidates(&r,t,1,a,b,polynomial,&tangent,when,indices,kinds,&count))return 1;
if(tangent) {if(count)return 2;continue;}
if(count!=1 || fabs(when[0]-.4)>1e-12)return 3;
t=when[0];polynomial(&tangent,t,a);contact_commit(&r,t,a,when,indices,kinds,count);
count=0;
if(!contact_candidates(&r,t,1,a,b,polynomial,&tangent,when,indices,kinds,&count))return 4;
if(count!=1 || fabs(when[0]-.6)>1e-12)return 5;
}
return 0;
}
'''
(path/'check.c').write_text(code,encoding='ascii')
cc,_,_=builder.toolchain();exe=path/('check.exe' if sys.platform=='win32' else 'check')
builder._command([cc,*builder.COMPILER_FLAGS,'-I',str(path),'-I',str(ROOT/'native/include'),
'-I',str(ROOT/'native/runtime'),str(path/'check.c'),'-lm','-o',str(exe)],log=[])
r=subprocess.run([str(exe)],capture_output=True,text=True,timeout=15)
self.assertEqual(r.returncode,0,r.stderr)
# Both schedules must register their own arithmetic association.
compact=compile_native_program(contact_pair().net)
extended=compile_extended_program(contact_pair().net)
self.assertIn('0.0,100.0,0.0,0.0,1,1}',compact.source)
self.assertIn('0.0,100.0,0.0,0.0,1,0}',extended.source)
if __name__=='__main__':unittest.main()
+99
View File
@@ -0,0 +1,99 @@
"""Event output sides, replay, and phase pairing independent of output errors."""
import math
from pathlib import Path
import tempfile
import unittest
from unittest.mock import patch
import numpy as np
from app.simulation.config import SolveIVPConfig
from app.simulation.native_codegen import build as builder, result_storage
from app.simulation.native_codegen.compiler import compile_native_program
from app.simulation.native_codegen.runner import execute_native
from tests.manual.event_phase_comparison import pair_saved_phases
from tests.test_native_catalog import Circuit
class PhasePairingTests(unittest.TestCase):
def test_roundoff_different_sides_match_reference_phase_using_one_row(self):
boundary = 32.400000000000006
nt = [32.4, boundary, 32.41]
rt = [32.40000000000221, 32.41]
ni, ai, records = pair_saved_phases(nt, rt,
[[49000., 2.], [1e17, 2.], [1e17, 2.]], [[1e17, 2.], [1e17, 2.]], [32.4, 32.41], .01)
self.assertEqual(ni.tolist(), [1, 2])
self.assertEqual(ai.tolist(), [0, 1])
self.assertEqual(records[0]['status'], 'matched-other-event-side')
def test_reference_before_event_uses_saved_left_side(self):
left = math.nextafter(.8, -math.inf)
ni, _, _ = pair_saved_phases([left, .8], [.8], [[1e17], [49000.]], [[1e17]], [.8], .01)
self.assertEqual(ni.tolist(), [0])
def test_missing_side_or_one_different_signal_is_not_interpolated_or_hidden(self):
ni, _, records = pair_saved_phases([.79, .8, .81], [.8],
[[1., 1.], [2., 1.], [2., 2.]], [[2., 2.]], [.8], .01)
self.assertEqual(ni.tolist(), [-1])
self.assertEqual(records[0]['status'], 'unmatched-forcing-phase')
ni, _, records = pair_saved_phases([.79, .81], [.8], [[1.], [1.]], [[1.]], [.8], .01)
self.assertEqual(ni.tolist(), [-1])
self.assertEqual(records[0]['status'], 'missing-saved-sample')
class NativeEventOutputTests(unittest.TestCase):
def test_off_grid_step_keeps_regular_samples_and_both_sides(self):
b = Circuit()
b.add('amesim_step0', 'step', time=.055, initial=2., final=4.)
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
with patch.object(builder, 'CACHE', root/'build'), patch.object(result_storage, 'RESULT_ROOT', root/'results'):
build = builder.build_native(compile_native_program(b.net))
try:
for method in ('BDF', 'RK45'):
result = execute_native(build, SolveIVPConfig(t_stop=.2, method=method, max_step=1.),
.01, run_dir=root/method)
self.assertTrue(result['success'], result['message'])
times = result['series']['time']
expected = sorted({i*.01 for i in range(21)} | {.055, math.nextafter(.055, -math.inf)})
self.assertEqual(times, expected)
self.assertEqual(result['series']['step.y'], [2. if t<.055 else 4. for t in times])
self.assertEqual(result['solverStarts'], 2)
finally:
build.close()
def test_collision_sides_and_recording_do_not_change_integration(self):
b = Circuit()
mass = b.add('amesim_mecmas21', 'mass', mass=1, stoptype=3,
xmin=0, xmax=1, x0=.9, v0=1, restcoeff=.5, restdvel=.001, useFriction=1)
for i in (1, 2):
free = b.add('amesim_f000', 'free'+str(i))
b.connect(mass, 'port_'+str(i), free, 'port_1')
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
with patch.object(builder, 'CACHE', root/'build'), patch.object(result_storage, 'RESULT_ROOT', root/'results'):
build = builder.build_native(compile_native_program(b.net))
try:
for method in ('BDF', 'RK45'):
config = SolveIVPConfig(t_stop=.25, method=method, max_step=.03, rtol=1e-8)
result = execute_native(build, config, .007, run_dir=root/method)
control = execute_native(build, config, .007, run_dir=root/(method+'-solve-only'), record_samples=False)
self.assertTrue(result['success'], result['message'])
self.assertEqual(result['finalState'], control['finalState'])
for key in ('acceptedSteps', 'rejectedSteps', 'solverStarts', 'stateTransitions', 'nfev'):
self.assertEqual(result[key], control[key], key)
series = result['series']
times, speeds = series['time'], series['mass.v']
self.assertTrue(all(a < z for a, z in zip(times, times[1:])))
idx = next(i for i in range(1, len(times)) if speeds[i] < 0 <= speeds[i-1])
self.assertEqual(times[idx-1], math.nextafter(times[idx], -math.inf))
self.assertAlmostEqual(times[idx], .1, delta=1e-6)
self.assertAlmostEqual(speeds[idx-1], 1., places=10)
self.assertAlmostEqual(speeds[idx], -.5, places=10)
self.assertAlmostEqual(series['mass.x'][idx], 1., places=12)
finally:
build.close()
if __name__ == '__main__':
unittest.main()
+188
View File
@@ -0,0 +1,188 @@
"""Independent stage/one-ULP checks and isolated cyclic UD00 execution."""
from __future__ import annotations
from bisect import bisect_right
import math
from pathlib import Path
import subprocess
import sys
import tempfile
import unittest
from unittest.mock import patch
from app.simulation.config import SolveIVPConfig
from app.simulation.native_codegen import build as builder, result_storage
from app.simulation.native_codegen.compiler import compile_native_program
from app.simulation.native_codegen.runner import execute_native
from tests.test_native_catalog import Circuit
ROOT = Path(__file__).resolve().parents[1]
HARNESS = r'''
#include "kernels.h"
#include <math.h>
#include <stdio.h>
int main(void) {
char mode; double t,start,end,data[24]; int stages,cyclic,count;
while(scanf(" %c %lf %lf %lf %d %d %d",&mode,&t,&start,&end,&stages,&cyclic,&count)==7) {
for(int i=0;i<24;i++) if(scanf("%lf",&data[i])!=1) return 2;
if(mode=='V') {
printf("%.17g %.17g\n",native_signal(t,start,stages,cyclic,data),
native_signal_break(t,end,start,stages,cyclic,data));
} else {
for(int i=0;i<count;i++) {
double b=native_signal_break(t,end,start,stages,cyclic,data);
printf("%.17g %.17g %.17g %.17g\n",b,
native_signal(nextafter(b,-INFINITY),start,stages,cyclic,data),
native_signal(b,start,stages,cyclic,data),
native_signal(nextafter(b,INFINITY),start,stages,cyclic,data));
if(!(b>t)) return 3;
t=b;
}
}
}
return 0;
}
'''
def stage_data(starts, ends, durations):
return [*starts, *([0.] * (8-len(starts))), *ends, *([0.] * (8-len(ends))),
*durations, *([0.] * (8-len(durations)))]
class NativeSignalBoundaryTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.tmp = tempfile.TemporaryDirectory(prefix='native-signal-boundaries-')
cls.addClassCleanup(cls.tmp.cleanup)
cls.root = Path(cls.tmp.name)
compiler, _, _ = builder.toolchain()
source = cls.root / 'probe.c'
source.write_text(HARNESS, encoding='ascii')
cls.exe = cls.root / ('probe.exe' if sys.platform == 'win32' else 'probe')
builder._command([compiler, *builder.COMPILER_FLAGS, '-I', str(ROOT / 'native/include'),
str(source), str(ROOT / 'native/components/modules/signal.c'), '-lm', '-o', str(cls.exe)], log=[])
def probe(self, data, stages, queries, *, start=0., cyclic=True, end=1e12, events=0):
lines = [' '.join(map(str, ['E' if events else 'V', t, start, end, stages,
int(cyclic), events or 1, *data])) for t in queries]
run = subprocess.run([str(self.exe)], input='\n'.join(lines)+'\n',
capture_output=True, text=True, timeout=15, check=True)
rows = [list(map(float, line.split())) for line in run.stdout.splitlines()]
self.assertEqual(len(rows), len(queries) * (events or 1))
return rows
def test_mql_cyclic_boundaries_and_adjacent_floats(self):
# Freeze the known event times independently of signal evaluation.
boundaries = [.8, 10.8, 11.600000000000001, 21.6, 22.400000000000002,
32.400000000000006, 33.2, 43.2, 44., 54.]
data = stage_data([1e17, 49000.], [1e17, 49000.], [.8, 10.])
queries = [t for boundary in boundaries for t in
(math.nextafter(boundary, -math.inf), boundary, math.nextafter(boundary, math.inf))]
rows = self.probe(data, 2, [*queries, 32.4])
for i, boundary in enumerate(boundaries):
before, after = (1e17, 49000.) if i % 2 == 0 else (49000., 1e17)
left, at, right = rows[3*i:3*i+3]
with self.subTest(boundary=boundary):
self.assertEqual([left[0], at[0], right[0]], [before, after, after])
self.assertEqual(left[1], boundary)
self.assertGreater(at[1], boundary)
if i+1 < len(boundaries):
self.assertEqual(at[1], boundaries[i+1])
# A genuinely earlier sample must not be pulled onto the event.
self.assertEqual(rows[-1][0], 49000.)
def test_multistage_long_cycle_sequence_has_no_skipped_or_duplicate_events(self):
data = stage_data(list(range(1, 9)), list(range(1, 9)), [.1, .2, .3, .4, .5, .6, .7, .8])
rows = self.probe(data, 8, [0.], events=8000)
last = 0.
for i, (boundary, left, at, right) in enumerate(rows):
old, new = i % 8 + 1, (i+1) % 8 + 1
self.assertGreater(boundary, last, (i, boundary))
self.assertEqual((left, at, right), (old, new, new), (i, boundary))
last = boundary
self.assertAlmostEqual(last, 3600., places=9)
def test_offset_start_and_small_period_boundaries(self):
for start, scale in ((.37, 1.), (-1.25, 1.), (1e6, 1e-5)):
data = stage_data([1., 2., 3.], [1., 2., 3.], [.1*scale, .2*scale, .3*scale])
rows = self.probe(data, 3, [start], start=start, events=900)
for i, (boundary, left, at, right) in enumerate(rows):
with self.subTest(start=start, scale=scale, event=i):
self.assertEqual((left, at, right), (i % 3 + 1, (i+1) % 3 + 1, (i+1) % 3 + 1), boundary)
def test_ramps_delayed_start_and_noncyclic_final_value(self):
data = stage_data([0., 10., -10.], [2., 12., -7.], [.25, .5, .75])
times = [0., 1.25, 1.375, 1.5, 1.75, 2., 2.375, 2.75, 2.875, 3.]
expected = [0., 0., 1., 10., 11., -10., -8.5, 0., 1., 10.]
actual = self.probe(data, 3, times, start=1.25)
self.assertEqual([row[0] for row in actual], expected)
once = self.probe(data, 3, [2.75, 5.], start=1.25, cyclic=False, end=10.)
self.assertEqual(once, [[-7., 10.], [-7., 10.]])
# A simulation beginning after the signal's start can enter any cycle.
shifted = self.probe(data, 3, [-2., -1.875, -1.75, -.5], start=-2.)
self.assertEqual([row[0] for row in shifted], [0., 1., 10., 0.])
def test_zero_duration_stages_and_zero_total_duration_keep_existing_semantics(self):
data = stage_data([-100., 10., -300., 20., -500.], [-100., 10., -300., 20., -500.],
[0., .5, 0., .5, 0.])
rows = self.probe(data, 5, [-1., 0., .5, 1., 1.5], end=10.)
self.assertEqual(rows, [[-100., 0.], [10., .5], [20., 1.], [10., 1.5], [20., 2.]])
self.assertEqual(self.probe(data, 5, [1.], cyclic=False, end=10.), [[-500., 10.]])
zero = stage_data([2., 9.], [4., 11.], [0., 0.])
self.assertEqual(self.probe(zero, 2, [-1., 0., 50.], end=100.),
[[2., 0.], [11., 100.], [11., 100.]])
# A trailing zero stage must not create a second, differently rounded
# representation of the cycle endpoint or leak its instantaneous value.
fractional = stage_data([-100., 10., -300., 20., -500.], [-100., 10., -300., 20., -500.],
[0., .8, 0., 10., 0.])
for i, (boundary, left, at, right) in enumerate(self.probe(fractional, 5, [0.], events=2000)):
old, new = (10., 20.) if i % 2 == 0 else (20., 10.)
self.assertEqual((left, at, right), (old, new, new), (i, boundary))
def test_single_stage_sawtooth_and_queries_out_of_order(self):
data = stage_data([2.], [6.], [2.])
times = [50., 2., -1., 1., 3., 4., 1.]
expected = [2., 2., 2., 4., 4., 2., 4.]
self.assertEqual([row[0] for row in self.probe(data, 1, times)], expected)
def test_isolated_cyclic_component_runs_both_solvers_and_replays_boundaries(self):
b = Circuit()
b.add('amesim_ud00', 'signal', start1=1e17, end1=1e17, t1=.8,
start2=49000., end2=49000., t2=10., nstages=2, iscyclic=1)
with patch.object(builder, 'CACHE', self.root / 'build-cache'), \
patch.object(result_storage, 'RESULT_ROOT', self.root / 'results'):
build = builder.build_native(compile_native_program(b.net))
self.addCleanup(build.close)
outputs = []
for method in ('BDF', 'RK45'):
result = execute_native(build, SolveIVPConfig(t_stop=60., method=method, max_step=1e30, rtol=1e-8),
.01, run_dir=self.root / method)
self.assertTrue(result['success'], result['message'])
self.assertEqual(result['simulatedUntil'], 60.)
series = result['series']
self.assertGreater(len(series['time']), 6001)
self.assertTrue(set(i*.01 for i in range(6001)).issubset(series['time']))
self.assertTrue(all(a < b for a, b in zip(series['time'], series['time'][1:])))
values = series['signal.out.signal']
self.assertEqual(set(values), {1e17, 49000.})
self.assertEqual(values[series['time'].index(44.)], 49000.)
self.assertEqual(values[series['time'].index(54.)], 1e17)
known_boundaries = [.8, 10.8, 11.600000000000001, 21.6, 22.400000000000002,
32.400000000000006, 33.2, 43.2, 44., 54., 54.8]
# A correct replay alone would not prove that integration
# stopped and restarted at each of the eleven time events.
self.assertEqual(result['solverStarts'], len(known_boundaries) + 1)
expected = [49000. if bisect_right(known_boundaries, t) % 2 else 1e17
for t in series['time']]
self.assertEqual(values, expected)
for boundary in known_boundaries:
self.assertIn(boundary, series['time'])
self.assertIn(math.nextafter(boundary, -math.inf), series['time'])
self.assertEqual(result['outputSemantics']['version'], 2)
outputs.append(values)
self.assertEqual(*outputs)
if __name__ == '__main__':
unittest.main()