修复循环信号与事件采样并接入 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,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()
|
||||
Reference in new issue
Block a user