修复循环信号与事件采样并接入 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:
1 parent
1aac220084
commit
7611f13208
85 files changed
+11358
-33
No files matched your search
@@ -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()
|
||||
Reference in new issue
Block a user