相较上一版 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 通过。
103 lines
7.1 KiB
Python
103 lines
7.1 KiB
Python
"""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()
|