优化 Jacobian 确定性复用并补充性能剖析与平台依赖文档
在单次 Jacobian 构建内按完整输入精确复用储气物性、PH 反算、密度和管路求根结果,保持原有求值副作用、差分政策与失败回退。八路模型求解 CPU 中位数减少 19.27%,循环和不循环的完整原始采样均与恢复基线一致。 增加独立的跨平台时间剖析工具,记录互斥阶段耗时、Newton/LU 统计、矩阵复用与内核复用,保存 UD00 两种工况的调查报告和机器可读汇总。 补充 Windows/Linux 运行、测试、原生编译和剖析所需依赖文档及索引,不修改依赖清单、版本锁或安装环境。 验证:8 项新增专项回归通过;2270 次完整 Jacobian 核对零差异;16 次剖析配对及预热运行保持完整数值一致。既有固定样本哈希失败和 Linux 实机验收限制见报告。
This commit is contained in:
1 parent
2b07d996cf
commit
1aac220084
26 files changed
+2293
-13
No files matched your search
@@ -0,0 +1,132 @@
|
||||
"""Freeze native workers and compare exact outputs before/after Jacobian reuse.
|
||||
|
||||
Use prepare --name before before editing production sources. Then prepare after.
|
||||
Run measured workers serially, outside compilation or verification activity.
|
||||
"""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import platform
|
||||
import shutil
|
||||
import statistics
|
||||
import subprocess
|
||||
import sys
|
||||
import 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.build import build_native
|
||||
from app.simulation.native_codegen.compiler import compile_native_program
|
||||
from app.simulation.native_codegen.input import load_input
|
||||
|
||||
OUT=ROOT/'test/jacobian-reuse-20260915'
|
||||
MODEL=ROOT/'tests/data/test-mql-8-corrected.json'
|
||||
EXACT=('statesSha256','outputsSha256','finalState','final','propertyWarnings',
|
||||
'nfev','acceptedSteps','rejectedSteps','njev','nlu','stateTransitions','solverStarts')
|
||||
|
||||
|
||||
def digest(path):
|
||||
with path.open('rb') as stream:
|
||||
return hashlib.file_digest(stream,'sha256').hexdigest()
|
||||
|
||||
|
||||
def write(path,data):
|
||||
path.write_text(json.dumps(data,ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
|
||||
|
||||
|
||||
def prepare(name,cyclic):
|
||||
variant=MODEL
|
||||
if cyclic:
|
||||
project=json.loads(MODEL.read_text(encoding='utf-8'))
|
||||
for node in project['nodes']:
|
||||
if node['data'].get('modelType')=='amesim_ud00':
|
||||
node['data']['parameters']['iscyclic']=1
|
||||
project['simulation']['t_stop']=21.7
|
||||
variant=OUT/'cyclic-project.json'
|
||||
write(variant,project)
|
||||
_,document=load_input(variant)
|
||||
program=compile_native_program(compile_system_xml_network(document))
|
||||
build=build_native(program)
|
||||
try:
|
||||
target=OUT/(name+('-cyclic' if cyclic else ''))
|
||||
shutil.copytree(build.executable.parent,target)
|
||||
sources=list((ROOT/'app/simulation/native_codegen').glob('*.py'))+list((ROOT/'native').rglob('*.c'))+list((ROOT/'native/include').glob('*.h'))
|
||||
write(target/'comparison-metadata.json',dict(buildKey=build.manifest['buildKey'],
|
||||
inputSha256=digest(variant),sourceHashes={p.relative_to(ROOT).as_posix():digest(p) for p in sources},
|
||||
platform=platform.platform(),buildSeconds=build.seconds,details=build.details,
|
||||
sourceRevision=subprocess.check_output(['git','rev-parse','HEAD'],cwd=ROOT,text=True).strip()))
|
||||
print(json.dumps(dict(build=target.name,key=build.manifest['buildKey'],seconds=build.seconds)),flush=True)
|
||||
finally:
|
||||
build.close()
|
||||
|
||||
|
||||
def run(name,label,cyclic,stop,verify=False):
|
||||
build=OUT/(name+('-cyclic' if cyclic else ''))
|
||||
target=OUT/(name+('-cyclic-' if cyclic else '-')+label)
|
||||
target.mkdir()
|
||||
command=[str(build/('model.exe' if sys.platform=='win32' else 'model')),
|
||||
'--method','BDF','--start','0','--stop',str(stop),'--sample-step','.01',
|
||||
'--rtol','1e-8','--max-step','1e30','--timeout','300',
|
||||
'--sample-file',str(target/'states.bin'),'--output-block-file',str(target/'outputs.bin'),
|
||||
'--output',str(target/'result.json')]
|
||||
if verify: command.append('--verify-jacobian')
|
||||
start=time.perf_counter()
|
||||
with (target/'stderr.log').open('w',encoding='utf-8') as log:
|
||||
process=subprocess.run(command,cwd=build,stdout=subprocess.DEVNULL,stderr=log,timeout=330)
|
||||
elapsed=time.perf_counter()-start
|
||||
result=json.loads((target/'result.json').read_text(encoding='utf-8'))
|
||||
assert process.returncode==0 and result['success'],result.get('message')
|
||||
record={k:result[k] for k in EXACT if k not in ('statesSha256','outputsSha256')}
|
||||
record.update(name=name,label=label,cyclic=cyclic,stop=stop,verify=verify,
|
||||
processWallSeconds=elapsed,solveSeconds=result['solveSeconds'],solveCpuSeconds=result['solveCpuSeconds'],
|
||||
statesSha256=digest(target/'states.bin'),outputsSha256=digest(target/'outputs.bin'),
|
||||
jacobianChecks=result['jacobianChecks'],jacobianMismatches=result['jacobianMismatches'],
|
||||
jacobianReuse=result.get('jacobianReuse'),sampleCount=len(result['series']['time']))
|
||||
write(target/'measurement.json',record)
|
||||
print(json.dumps({k:v for k,v in record.items() if k not in ('finalState','final','propertyWarnings')}),flush=True)
|
||||
|
||||
|
||||
def report():
|
||||
records=[json.loads(p.read_text(encoding='utf-8')) for p in sorted(OUT.glob('*-run-*/measurement.json'))]
|
||||
assert len(records)>=6
|
||||
checks=[dict(run=r['name']+'-'+r['label'],differences=[k for k in EXACT if r[k]!=records[0][k]]) for r in records]
|
||||
medians={name:{key:statistics.median(r[key] for r in records if r['name']==name)
|
||||
for key in ('solveCpuSeconds','solveSeconds','processWallSeconds')} for name in ('before','after')}
|
||||
cases=[]
|
||||
for cyclic,suffix in ((False,'noncyclic-check'),(True,'cyclic-check')):
|
||||
pair=[json.loads((OUT/f'{name}-{suffix}'/'measurement.json').read_text(encoding='utf-8')) for name in ('before','after')]
|
||||
case=dict(cyclic=cyclic,stop=21.7,sampleCount=pair[0]['sampleCount'],
|
||||
differences=[k for k in EXACT if pair[0][k]!=pair[1][k]],
|
||||
measurements=[{k:v for k,v in r.items() if k not in ('finalState','final','propertyWarnings')} for r in pair])
|
||||
cases.append(case)
|
||||
verification=[]
|
||||
for suffix,reference_suffix in (('verify-check','run-0'),('cyclic-verify-check','cyclic-check')):
|
||||
checked=json.loads((OUT/f'after-{suffix}'/'measurement.json').read_text(encoding='utf-8'))
|
||||
reference=json.loads((OUT/f'after-{reference_suffix}'/'measurement.json').read_text(encoding='utf-8'))
|
||||
differences=[k for k in EXACT if k!='nfev' and checked[k]!=reference[k]]
|
||||
assert checked['jacobianMismatches']==0 and checked['jacobianChecks']==reference['njev']
|
||||
assert checked['nfev']==reference['nfev']+checked['jacobianChecks']*len(reference['finalState'])
|
||||
assert checked['jacobianReuse']==reference['jacobianReuse']
|
||||
verification.append(dict(run=suffix,checks=checked['jacobianChecks'],mismatches=checked['jacobianMismatches'],differences=differences))
|
||||
summary=dict(checks=checks,medians=medians,ud00=cases,verification=verification,measurements=records,
|
||||
scope='One warmup per build excluded. Three alternating serial measurements at 0-10s; UD00 modes separately compared at 0-21.7s. Full native worker with raw samples; excludes build/API/browser.')
|
||||
write(OUT/'comparison.json',summary)
|
||||
print(json.dumps(dict(checks=checks,medians=medians,ud00=cases,verification=verification),indent=2))
|
||||
assert not any(c['differences'] for c in checks+cases+verification),'Numerical comparison failed'
|
||||
|
||||
|
||||
if __name__=='__main__':
|
||||
parser=argparse.ArgumentParser()
|
||||
parser.add_argument('action',choices=('prepare','run','report'))
|
||||
parser.add_argument('--name',choices=('before','after'),default='before')
|
||||
parser.add_argument('--label',default='warmup')
|
||||
parser.add_argument('--cyclic',action='store_true')
|
||||
parser.add_argument('--verify',action='store_true')
|
||||
parser.add_argument('--stop',type=float,default=10)
|
||||
args=parser.parse_args()
|
||||
OUT.mkdir(parents=True,exist_ok=True)
|
||||
if args.action=='prepare': prepare(args.name,args.cyclic)
|
||||
elif args.action=='run': run(args.name,args.label,args.cyclic,args.stop,args.verify)
|
||||
else: report()
|
||||
Reference in new issue
Block a user