Files
SystemSimulationApp/tests/manual/mql8_comparison.py
T
ljz 7611f13208 修复循环信号与事件采样并接入 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 通过。
2026-09-17 23:50:13 +08:00

119 lines
6.1 KiB
Python

"""Eight-branch SI mapping, carried forward from the audited 2026-09-14 comparison.
The caller supplies a fresh topology audit and network; no historical curves are loaded.
"""
import numpy as np
from app.simulation.reporting.amesim_results import _parse_variable_line, parse_amesim_results_bytes
def configure(current_audit, current_network):
global audit, network, aliases, nodes, adj
audit, network = current_audit, current_network
aliases = {row['component']: row['ameAlias'] for row in audit['mapping']}
nodes = {c.name: c for c in network.components.values()
if c.model_type in ('amesim_pn3node2', 'amesim_p4node2')}
adj = {}
for edge in network.connections:
if edge.kind == 'physical':
a, b = (e.key for e in edge.endpoints)
adj[a], adj[b] = b, a
def native_curves(series):
values = {k: np.asarray(v) for k, v in series.items()}
def energy(name):
key = name + '.reference_enthalpy_flow'
if key not in values:
result = np.zeros_like(values['time'])
for port in nodes[name].active_port_definitions:
if port.name == 'port_2':
continue
other = adj[name, port.name]
if other[0] in nodes and other[1] == 'port_2':
result += energy(other[0])
else:
q = values[name + '.' + port.name + '.m_flow']
result += q * np.where(q > 0, values['.'.join(other) + '.h_outflow'],
values[name + '.' + port.name + '.h_outflow'])
values[key] = result
return values[key]
for name in nodes:
energy(name)
values[name + '.reference_mass_flow'] = -values[name + '.port_2.m_flow']
for component in network.components.values():
if component.model_type == 'amesim_pnch012':
name = component.name
values[name + '.volume_work'] = -values[name + '.p'] * values[name + '.dvol']
return values
def read_ame(directory):
variables = tuple(_parse_variable_line(i, line) for i,line in enumerate(
(directory/'test_mql_.var').read_text(encoding='latin1').splitlines()))
return parse_amesim_results_bytes((directory/'test_mql_.results').read_bytes(), variables)
def curve_mapping():
result = []
for row in audit['mapping']:
kind, name, alias = row['type'],row['component'],row['ameAlias']
fields = []
if kind == 'amesim_mecmas21':
fields = [('x','x1','displacement','m',1,0),('v','v1','velocity','m/s',1,0)]
elif kind == 'amesim_lstp00a':
fields = [('force','f1','force','N',1,0),('gap','gap','gap','m',.001,0)]
elif kind in ('amesim_pnch023','amesim_pnch012'):
fields = [('p','press','pressure','Pa',1,101300),('T','temp','temperature','K',1,0)]
if kind == 'amesim_pnch012':
fields += [('vol','vol','volume','m3',1e-6,0),('m','mgas1','mass','kg',1e-3,0)]
elif kind in ('amesim_pnl0001','amesim_pnl0002'):
s = '2' if kind.endswith('1') else 'ctr'
fields = [('p','p'+s,'pressure','Pa',1,101300),('T','t'+s,'temperature','K',1,0)]
elif kind == 'amesim_pnl0003':
fields = [(k+str(i), k.lower()+str(i), 'pressure' if k=='p' else 'temperature',
'Pa' if k=='p' else 'K',1,101300 if k=='p' else 0) for k in ('p','T') for i in (1,2)]
elif kind in ('amesim_ud00','amesim_step0'):
fields = [('out.signal','output' if kind=='amesim_ud00' else 'out','signal','1',1,0)]
elif kind in ('amesim_pn3node2','amesim_p4node2'):
fields = [('reference_enthalpy_flow','dh2','enthalpy_flow','W',1,0),
('reference_mass_flow','dm2','mass_flow','kg/s',1e-3,0)]
elif kind == 'amesim_pnrp17':
fields = [('volume','vol1','piston_volume','m3',1e-6,0),
('volume_flow','vvol1','volume_rate','m3/s',1e-3/60,0)]
for prop, field, quantity, unit, scale, offset in fields:
result.append(dict(key=name+'.'+prop, amePath=field+'@'+alias,
quantity=quantity, unit=unit, scale=scale, offset=offset))
if kind == 'amesim_pnch012':
sources = []
volumes = []
for p in network.components[name].active_port_definitions:
other = adj[name,p.name]
component = network.components[other[0]]
if component.model_type == 'amesim_pnrp17':
sources.append('vvol1@'+aliases[other[0]])
volumes.append('vol1@'+aliases[other[0]])
elif other[0] in nodes and other[1] == 'port_2':
sources.append('dvol2@'+aliases[other[0]])
volumes.append('vol2@'+aliases[other[0]])
chamber = network.components[name]
effective_volume = dict(volumePaths=volumes, deadVolume=chamber.cvol0,
prescribedVolume=sum(chamber.external_volumes.values()))
result.append(dict(key=name+'.dvol',amePaths=sources, quantity='chamber_volume_rate',unit='m3/s', **effective_volume))
result.append(dict(key=name+'.volume_work',amePaths=sources,pressurePath='press@'+alias,
quantity='volume_work',unit='W', **effective_volume))
return result
def ame_curve(ame, m):
if 'amePath' in m:
return np.array(ame.series(m['amePath']))*m['scale']+m['offset']
rate = sum((np.array(ame.series(p)) for p in m['amePaths']), np.zeros(len(ame.times)))*(1e-3/60)
volume = sum((np.array(ame.series(p)) for p in m['volumePaths']), np.zeros(len(ame.times)))*1e-6
volume += m['deadVolume']+m['prescribedVolume']
# PNCH012.c sets its internal dvol to zero while the volume is limited.
# Summing raw piston rates without this condition invents enormous work
# that the original Amesim chamber does not actually apply.
limit = m['deadVolume']/100
rate = np.where((volume<limit) | ((volume<=limit) & (rate<0)), 0, rate)
if m['quantity'] == 'volume_work':
return -(np.array(ame.series(m['pressurePath']))+101300)*rate
return rate