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