修复循环信号与事件采样并接入 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,412 @@
|
||||
"""Re-run audited eight-branch inputs against the AME archive's executable.
|
||||
|
||||
Use project Python 3.12; --output must be a new directory. Only copied time
|
||||
settings change unless --align-cyclic-from-ame is explicitly supplied, which
|
||||
permits only the audited UD00 cyclic flags to change in a new JSON copy.
|
||||
Numeric differences are evidence, not a claim of physical validation.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from contextlib import redirect_stdout
|
||||
from dataclasses import replace
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tarfile
|
||||
import time
|
||||
from unittest.mock import patch
|
||||
|
||||
import numpy as np
|
||||
|
||||
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 mql8_comparison as curves
|
||||
from tests.manual.event_phase_comparison import pair_saved_phases
|
||||
from tools import audit_test_mql8_model as auditor
|
||||
|
||||
# Report boundaries, not accepted engineering tolerances. Preserve absolute
|
||||
# errors when the reference is small, and report the sensitivity to this choice.
|
||||
EPS = dict(pressure=1., temperature=1e-6, enthalpy_flow=1., mass_flow=1e-6,
|
||||
displacement=1e-9, velocity=1e-6, gap=1e-9, mass=1e-12,
|
||||
volume=1e-12, piston_volume=1e-12, volume_rate=1e-12,
|
||||
chamber_volume_rate=1e-12, volume_work=1., force=1e-6, signal=1e-12)
|
||||
PROFILES = {'default': (10., .01, 1e-8), 'full': (50., .01, 1e-8),
|
||||
'startup': (.15, .0001, 1e-8), 'startup-refined': (.15, .0001, 1e-10),
|
||||
'volume-startup': (2e-6, 2e-9, 1e-10), 'noncyclic': (50., .01, 1e-8)}
|
||||
|
||||
|
||||
def digest(path):
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def save(path, data):
|
||||
path.write_text(json.dumps(data, ensure_ascii=False, indent=2, allow_nan=False) + '\n', encoding='utf-8')
|
||||
|
||||
|
||||
def audit_input(ame, project, out, allow_cyclic_mismatch=False):
|
||||
ame, project = ame.resolve(), project.resolve()
|
||||
out.mkdir(parents=True)
|
||||
with patch.object(auditor, 'HERE', out), patch.object(auditor, 'AME', ame), \
|
||||
patch.object(auditor, 'INPUT', project), patch.object(sys, 'argv', ['audit', '--check']), \
|
||||
(out / 'audit.log').open('w', encoding='utf-8') as log, redirect_stdout(log):
|
||||
try:
|
||||
auditor.main()
|
||||
except AssertionError:
|
||||
if not allow_cyclic_mismatch or not (out / 'audit.json').exists():
|
||||
raise
|
||||
evidence = json.loads((out / 'audit.json').read_bytes())
|
||||
changes = evidence['parameterChanges']
|
||||
if evidence['connectionChanges'] or not changes or not all(
|
||||
row['parameter'] == 'iscyclic' and row['component'].startswith('amesim_ud00_')
|
||||
for row in changes):
|
||||
raise
|
||||
return json.loads((out / 'audit.json').read_bytes())
|
||||
|
||||
|
||||
def prepare_ame(archive_path, target, stop, step, rtol):
|
||||
target.mkdir()
|
||||
hashes = {}
|
||||
with tarfile.open(archive_path) as archive:
|
||||
for member in archive:
|
||||
if not member.isfile() or member.name.endswith(('.results', '.ameperf')):
|
||||
continue
|
||||
path = (target / member.name).resolve()
|
||||
if not path.is_relative_to(target.resolve()):
|
||||
raise ValueError('Archive path outside target: ' + member.name)
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
data = archive.extractfile(member).read()
|
||||
path.write_bytes(data)
|
||||
hashes[member.name] = hashlib.sha256(data).hexdigest()
|
||||
sim = target / 'test_mql_.sim'
|
||||
lines = sim.read_text(encoding='ascii').splitlines()
|
||||
original = lines[0].split()
|
||||
fields = original[:]
|
||||
fields[:5] = ['0', str(stop), str(step), '1e30', str(rtol)]
|
||||
lines[0] = ' '.join(fields)
|
||||
sim.write_text('\n'.join(lines) + '\n', encoding='ascii')
|
||||
assert all(digest(target / name) == value for name, value in hashes.items() if name != sim.name)
|
||||
save(target / 'source-verification.json', dict(archiveSha256=digest(archive_path),
|
||||
originalSim=original, actualSim=fields, unchangedArchiveFiles=hashes))
|
||||
|
||||
|
||||
def run_ame(target, ame_home):
|
||||
env = dict(os.environ, AME=str(ame_home))
|
||||
env['PATH'] = str(ame_home / 'win64') + os.pathsep + str(ame_home) + os.pathsep + env['PATH']
|
||||
start = time.perf_counter()
|
||||
with (target / 'run.log').open('wb') as log:
|
||||
proc = subprocess.run([str(target / 'test_mql_.exe')], cwd=target, env=env,
|
||||
stdout=log, stderr=subprocess.STDOUT, timeout=240,
|
||||
creationflags=subprocess.CREATE_NO_WINDOW if os.name == 'nt' else 0)
|
||||
elapsed = time.perf_counter() - start
|
||||
log = (target / 'run.log').read_text(encoding='utf-8', errors='replace')
|
||||
cpu = re.search(r'Total CPU time:\s*([\d.eE+-]+)', log)
|
||||
result = dict(returncode=proc.returncode, processWallSeconds=elapsed,
|
||||
cpuSeconds=float(cpu.group(1)) if cpu else None,
|
||||
normalTermination='terminated normally' in log)
|
||||
save(target / 'run-summary.json', result)
|
||||
if proc.returncode or not result['normalTermination']:
|
||||
raise RuntimeError(log[-3000:])
|
||||
result['resultsSha256'] = digest(target / 'test_mql_.results')
|
||||
return result
|
||||
|
||||
|
||||
def event_times(project, stop):
|
||||
result = set()
|
||||
for node in project['nodes']:
|
||||
kind, p = node['data']['modelType'], node['data']['parameters']
|
||||
if kind == 'amesim_step0':
|
||||
result.add(float(p['time']))
|
||||
elif kind == 'amesim_ud00':
|
||||
durations = [float(p['t' + str(i)]) for i in range(1, int(p['nstages']) + 1)]
|
||||
period = sum(durations)
|
||||
cycles = range(int(stop / period) + 1) if int(p['iscyclic']) else range(1)
|
||||
for cycle in cycles:
|
||||
at = float(p['tstart']) + cycle * period
|
||||
result.add(at)
|
||||
for duration in durations:
|
||||
at += duration
|
||||
result.add(at)
|
||||
return sorted(t for t in result if 0 < t <= stop)
|
||||
|
||||
|
||||
def metric(actual, expected, grid, quantity, exact_events, quiet):
|
||||
error = actual - expected
|
||||
absolute = np.abs(error)
|
||||
nonzero = np.abs(expected) > EPS[quantity]
|
||||
relative = np.zeros_like(error)
|
||||
relative[nonzero] = 100 * error[nonzero] / expected[nonzero]
|
||||
bad = nonzero & (np.abs(relative) > 5)
|
||||
near = ~nonzero
|
||||
index = int(np.argmax(absolute))
|
||||
def maximum(values, mask):
|
||||
return float(np.max(values[mask])) if np.any(mask) else None
|
||||
examples = []
|
||||
for j in np.flatnonzero(bad)[np.argsort(absolute[bad])[-5:][::-1]]:
|
||||
examples.append(dict(time=float(grid[j]), platform=float(actual[j]), amesim=float(expected[j]),
|
||||
absoluteError=float(absolute[j]), relativePercent=float(relative[j]),
|
||||
atSignalEvent=bool(exact_events[j])))
|
||||
return dict(maxAbsoluteError=float(absolute[index]), worstTime=float(grid[index]),
|
||||
platformAtWorst=float(actual[index]), amesimAtWorst=float(expected[index]),
|
||||
rmse=float(np.sqrt(np.mean(error**2))), finalError=float(error[-1]),
|
||||
referencePeak=float(np.max(np.abs(expected))),
|
||||
maxRelativePercent=maximum(np.abs(relative), nonzero),
|
||||
maxRelativeOutsideEvents=maximum(np.abs(relative), nonzero & ~exact_events),
|
||||
maxAbsoluteOutsideEvents=maximum(absolute, ~exact_events),
|
||||
quietMaxAbsolute=maximum(absolute, quiet),
|
||||
nonzeroCount=int(nonzero.sum()), above5PercentCount=int(bad.sum()),
|
||||
above5PercentOutsideEvents=int((bad & ~exact_events).sum()),
|
||||
nearZeroCount=int(near.sum()), nearZeroMaxAbsolute=maximum(absolute, near),
|
||||
nearZeroBeyondEpsilon=int((near & (absolute > EPS[quantity])).sum()),
|
||||
epsilon=EPS[quantity], examples=examples,
|
||||
relativeScreenSensitivity={str(factor): int(((np.abs(expected) > EPS[quantity] * factor)
|
||||
& (absolute > .05 * np.abs(expected))).sum()) for factor in (.1, 1., 10.)})
|
||||
|
||||
|
||||
def sample_grid(times, values, grid, step):
|
||||
"""Use the actual saved value at the same nominal output-grid position.
|
||||
|
||||
Repeated floating-point additions move an Amesim output timestamp slightly
|
||||
off its nominal grid. Interpolating 1e17 -> 49000 immediately before an
|
||||
almost-equal endpoint invents a plateau error through cancellation. Match
|
||||
only within 1e-7 of one output interval; retain that record's original side
|
||||
of an event. Do not move event times, average duplicates, or smooth spikes.
|
||||
"""
|
||||
times = np.asarray(times)
|
||||
right = np.clip(np.searchsorted(times, grid), 0, len(times) - 1)
|
||||
left = np.maximum(right - 1, 0)
|
||||
closest = np.where(np.abs(times[left] - grid) < np.abs(times[right] - grid), left, right)
|
||||
same_output = np.abs(times[closest] - grid) <= step * 1e-7
|
||||
result = np.interp(grid, times, values)
|
||||
result[same_output] = np.asarray(values)[closest[same_output]]
|
||||
return result
|
||||
|
||||
|
||||
def compare(directory, project, network, audit, settings):
|
||||
stop, step, rtol = settings
|
||||
for node in project['nodes']:
|
||||
if node['data']['modelType'] == 'amesim_ud00':
|
||||
p = node['data']['parameters']
|
||||
if any(float(p['start'+str(i)]) != float(p['end'+str(i)]) for i in range(1, int(p['nstages'])+1)):
|
||||
raise ValueError('This baseline phase matcher requires piecewise-constant UD00 stages; ramps need explicit stage metadata.')
|
||||
curves.configure(audit, network)
|
||||
mapping = curves.curve_mapping()
|
||||
save(directory / 'curve-mapping.json', mapping)
|
||||
raw = json.loads((directory / 'native/result.json').read_bytes())
|
||||
native = curves.native_curves(raw['series'])
|
||||
ame = curves.read_ame(directory / 'amesim')
|
||||
nt, at = native['time'], np.array(ame.times)
|
||||
assert raw['success'] and raw['simulatedUntil'] == stop
|
||||
assert nt[0] == at[0] == 0 and abs(at[-1] - stop) < 1e-9 and nt[-1] == stop
|
||||
assert np.all(np.diff(nt) > 0) and np.all(np.diff(at) >= 0)
|
||||
assert all(np.isfinite(v).all() for v in native.values())
|
||||
assert all(np.isfinite(v).all() for v in ame.series_by_data_path.values())
|
||||
grid = np.arange(round(stop / step) + 1) * step
|
||||
events = event_times(project, stop)
|
||||
exact_events = np.zeros(grid.shape, dtype=bool)
|
||||
quiet = grid >= .1
|
||||
for event in events:
|
||||
exact_events |= np.abs(grid - event) <= 1e-9
|
||||
quiet &= np.abs(grid - event) > .0200001
|
||||
signal_mapping = [m for m in mapping if m['quantity'] == 'signal']
|
||||
ni, ai, pairing = pair_saved_phases(nt, at,
|
||||
np.column_stack([native[m['key']] for m in signal_mapping]),
|
||||
np.column_stack([curves.ame_curve(ame, m) for m in signal_mapping]), grid, step)
|
||||
valid = (ni >= 0) & (ai >= 0)
|
||||
save(directory / 'phase-pairing.json', dict(
|
||||
policy='Reference saved forcing phase; one shared row pair for all curves; no cross-event interpolation',
|
||||
scope='Observed STEP/UD00 forcing phases; unregistered contact mode is not certified',
|
||||
signalKeys=[m['key'] for m in signal_mapping], toleranceSeconds=step*1e-7,
|
||||
gridCount=len(grid), pairedCount=int(valid.sum()), unpairedCount=int((~valid).sum()),
|
||||
adjustedCount=sum(p['status'] == 'matched-other-event-side' for p in pairing), records=pairing))
|
||||
if not np.any(valid):
|
||||
raise ValueError('No matching saved physical phases; see phase-pairing.json')
|
||||
rows, raw_rows, arrays = [], [], {'time': grid, 'phaseMatched': valid}
|
||||
for m in mapping:
|
||||
raw_actual = sample_grid(nt, native[m['key']], grid, step)
|
||||
reference = curves.ame_curve(ame, m)
|
||||
raw_expected = sample_grid(at, reference, grid, step)
|
||||
raw_rows.append(m | metric(raw_actual, raw_expected, grid, m['quantity'], exact_events, quiet))
|
||||
actual, expected = np.full(len(grid), np.nan), np.full(len(grid), np.nan)
|
||||
actual[valid], expected[valid] = native[m['key']][ni[valid]], reference[ai[valid]]
|
||||
rows.append(m | metric(actual[valid], expected[valid], grid[valid], m['quantity'], exact_events[valid], quiet[valid]))
|
||||
arrays['platform|' + m['key']] = actual
|
||||
arrays['amesim|' + m['key']] = expected
|
||||
np.savez_compressed(directory / 'curves.npz', **arrays)
|
||||
save(directory / 'raw-time-comparison.json', dict(curves=raw_rows,
|
||||
above5PercentCount=sum(r['above5PercentCount'] for r in raw_rows)))
|
||||
groups = {}
|
||||
for quantity in sorted({r['quantity'] for r in rows}):
|
||||
selected = [r for r in rows if r['quantity'] == quantity]
|
||||
groups[quantity] = dict(curveCount=len(selected),
|
||||
worstAbsolute=max(selected, key=lambda r: r['maxAbsoluteError']),
|
||||
worstRelative=max(selected, key=lambda r: r['maxRelativePercent'] or 0),
|
||||
nonzeroCount=sum(r['nonzeroCount'] for r in selected),
|
||||
above5PercentCount=sum(r['above5PercentCount'] for r in selected),
|
||||
above5PercentOutsideEvents=sum(r['above5PercentOutsideEvents'] for r in selected),
|
||||
nearZeroBeyondEpsilon=sum(r['nearZeroBeyondEpsilon'] for r in selected))
|
||||
mass_keys = [k for k in raw['series'] if k.rsplit('.', 1)[-1] in ('m', 'm1', 'm2')]
|
||||
mass = sum(native[k] for k in mass_keys)
|
||||
pressures = [v for k, v in native.items() if k.rsplit('.', 1)[-1] in ('p', 'p1', 'p2')]
|
||||
temperatures = [v for k, v in native.items() if k.rsplit('.', 1)[-1] in ('T', 'T1', 'T2')]
|
||||
physical = dict(massKeys=mass_keys, initialMassKg=float(mass[0]),
|
||||
maxTotalMassDriftKg=float(np.max(np.abs(mass - mass[0]))),
|
||||
relativeMassDrift=float(np.max(np.abs(mass - mass[0])) / mass[0]),
|
||||
minimumGasMassKg=float(min(np.min(native[k]) for k in mass_keys)),
|
||||
minimumAbsolutePressurePa=float(min(np.min(v) for v in pressures)),
|
||||
minimumTemperatureK=float(min(np.min(v) for v in temperatures)),
|
||||
maximumTemperatureK=float(max(np.max(v) for v in temperatures)))
|
||||
event_samples = []
|
||||
for event in events:
|
||||
for m in mapping:
|
||||
if m['quantity'] not in ('signal', 'force'):
|
||||
continue
|
||||
entry = dict(eventTime=event, key=m['key'])
|
||||
for label, times, values in [('platform', nt, native[m['key']]),
|
||||
('amesim', at, curves.ame_curve(ame, m))]:
|
||||
idx = int(np.searchsorted(times, event))
|
||||
entry[label] = [dict(time=float(times[i]), timeHex=float(times[i]).hex(), value=float(values[i]))
|
||||
for i in range(max(0, idx-2), min(len(times), idx+3))]
|
||||
event_samples.append(entry)
|
||||
save(directory / 'event-samples.json', event_samples)
|
||||
summary = dict(curveCount=len(rows), gridCount=len(grid), nativeSampleCount=len(nt),
|
||||
phaseMatchedGridCount=int(valid.sum()), phaseUnpairedGridCount=int((~valid).sum()),
|
||||
phaseAdjustedGridCount=sum(p['status'] == 'matched-other-event-side' for p in pairing),
|
||||
rawTimeAbove5PercentCount=sum(r['above5PercentCount'] for r in raw_rows),
|
||||
amesimSampleCount=len(at), platformOutputCount=len(raw['series']) - 1, allFinite=True,
|
||||
signalEvents=events, physical=physical, groups=groups, curves=rows,
|
||||
extraEventPointMaxContactForce=max(float(np.max(np.abs(native[m['key']])))
|
||||
for m in mapping if m['quantity'] == 'force'),
|
||||
above5PercentCurveCount=sum(r['above5PercentCount'] > 0 for r in rows),
|
||||
above5PercentCount=sum(r['above5PercentCount'] for r in rows),
|
||||
above5PercentOutsideEvents=sum(r['above5PercentOutsideEvents'] for r in rows))
|
||||
save(directory / 'comparison.json', summary)
|
||||
return {k: v for k, v in summary.items() if k != 'curves'}
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--output', type=Path, required=True)
|
||||
parser.add_argument('--ame', type=Path, default=ROOT / 'tests/data/test_mql.ame')
|
||||
parser.add_argument('--project', type=Path, default=ROOT / 'tests/data/test-mql-8-corrected.json')
|
||||
parser.add_argument('--ame-home', type=Path, required=True)
|
||||
parser.add_argument('--noncyclic-ame', type=Path)
|
||||
parser.add_argument('--noncyclic-project', type=Path)
|
||||
parser.add_argument('--align-cyclic-from-ame', action='store_true')
|
||||
parser.add_argument('--analyze-only', action='store_true', help='Reanalyze existing raw results without solving.')
|
||||
parser.add_argument('--profiles', nargs='+', choices=tuple(PROFILES), default=list(PROFILES))
|
||||
parser.add_argument('--native-source', type=Path, help='Frozen native source tree for a controlled before/after run.')
|
||||
args = parser.parse_args()
|
||||
if args.native_source:
|
||||
builder.NATIVE = args.native_source.resolve()
|
||||
out = args.output.resolve()
|
||||
if args.analyze_only:
|
||||
summaries = json.loads((out / 'summary.json').read_bytes())
|
||||
for name, old in list(summaries.items()):
|
||||
directory = out / name
|
||||
project = json.loads((directory / 'platform.json').read_bytes())
|
||||
_, document = load_input(directory / 'platform.json')
|
||||
audit_name = 'audit-noncyclic' if name == 'noncyclic' else 'audit-aligned'
|
||||
audit = json.loads((out / audit_name / 'audit.json').read_bytes())
|
||||
summary = compare(directory, project, compile_system_xml_network(document), audit, PROFILES[name])
|
||||
summary.update({key: old[key] for key in ('settings', 'amesimRun', 'nativeRun')})
|
||||
summaries[name] = summary
|
||||
print(name, 'reanalyzed', summary['above5PercentCount'], flush=True)
|
||||
save(out / 'summary.json', summaries)
|
||||
manifest = json.loads((out / 'manifest.json').read_bytes())
|
||||
manifest['interpolation'] = 'Same saved forcing phase within 1e-7 output intervals. Unpaired points explicitly reported; raw time-only metrics retained.'
|
||||
save(out / 'manifest.json', manifest)
|
||||
return
|
||||
out.mkdir(parents=True, exist_ok=False)
|
||||
if bool(args.noncyclic_ame) != bool(args.noncyclic_project):
|
||||
parser.error('Both noncyclic input paths are required together.')
|
||||
inputs = [args.ame, args.project] + ([args.noncyclic_ame, args.noncyclic_project] if args.noncyclic_ame else [])
|
||||
tracked = subprocess.check_output(['git', 'ls-files', 'app', 'native', 'frontend/src'], cwd=ROOT, text=True).splitlines()
|
||||
sources = {str(p.resolve()): digest(p) for p in inputs}
|
||||
code = {p: digest(ROOT / p) for p in tracked if (ROOT / p).is_file()}
|
||||
manifest = dict(gitCommit=subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=ROOT, text=True).strip(),
|
||||
python=sys.version, sources=sources, productionSources=code, epsilons=EPS,
|
||||
criterion='5% is a diagnostic screen, not an approved engineering tolerance',
|
||||
freshAmesimExecution=True, amesimExecutable='Extracted from source AME; not recompiled',
|
||||
nativeSource=str(builder.NATIVE),
|
||||
interpolation='Same saved forcing phase within 1e-7 output intervals. Unpaired points explicitly reported; raw time-only metrics retained.')
|
||||
save(out / 'manifest.json', manifest)
|
||||
original_audit = audit_input(args.ame, args.project, out / 'audit-original', args.align_cyclic_from_ame)
|
||||
if original_audit['parameterChanges']:
|
||||
aligned = json.loads(args.project.read_bytes())
|
||||
nodes = {node['id']: node for node in aligned['nodes']}
|
||||
for row in original_audit['parameterChanges']:
|
||||
nodes[row['component']]['data']['parameters'][row['parameter']] = row['expected']
|
||||
aligned_path = out / 'aligned-source.json'
|
||||
save(aligned_path, aligned)
|
||||
save(out / 'alignment-changes.json', original_audit['parameterChanges'])
|
||||
args.project = aligned_path
|
||||
audits = {'standard': audit_input(args.ame, args.project, out / 'audit-aligned')}
|
||||
if args.noncyclic_ame:
|
||||
audits['noncyclic'] = audit_input(args.noncyclic_ame, args.noncyclic_project, out / 'audit-noncyclic')
|
||||
summaries = {}
|
||||
for name, settings in PROFILES.items():
|
||||
if name not in args.profiles:
|
||||
continue
|
||||
if name == 'noncyclic' and not args.noncyclic_ame:
|
||||
continue
|
||||
stop, step, rtol = settings
|
||||
ame_path = args.noncyclic_ame if name == 'noncyclic' else args.ame
|
||||
project_path = args.noncyclic_project if name == 'noncyclic' else args.project
|
||||
audit = audits['noncyclic' if name == 'noncyclic' else 'standard']
|
||||
directory = out / name
|
||||
directory.mkdir()
|
||||
project = json.loads(project_path.read_bytes())
|
||||
project['simulation'].update(t_start=0., t_stop=stop, step=step, max_step=1e30, method='BDF')
|
||||
save(directory / 'platform.json', project)
|
||||
prepare_ame(ame_path, directory / 'amesim', stop, step, rtol)
|
||||
print(name, 'Amesim running', flush=True)
|
||||
ame_summary = run_ame(directory / 'amesim', args.ame_home)
|
||||
xml, document = load_input(directory / 'platform.json')
|
||||
(directory / 'platform.xml').write_bytes(xml)
|
||||
network = compile_system_xml_network(document)
|
||||
program = compile_native_program(network)
|
||||
print(name, 'current platform running', flush=True)
|
||||
started = time.perf_counter()
|
||||
# Isolate cache and result quotas from the user's live application.
|
||||
with patch.object(builder, 'CACHE', out / 'build-cache'), \
|
||||
patch.object(result_storage, 'RESULT_ROOT', out / 'result-storage'):
|
||||
build = builder.build_native(program)
|
||||
try:
|
||||
result = execute_native(build, replace(simulation_config(document.simulation), rtol=rtol),
|
||||
step, run_dir=directory / 'native', timeout=240)
|
||||
native_summary = {k: v for k, v in result.items() if k not in ('series', 'final', 'finalState')}
|
||||
native_summary.update(buildSeconds=build.seconds, buildCacheHit=build.cache_hit,
|
||||
buildKey=build.manifest['buildKey'], pipelineWallSeconds=time.perf_counter()-started,
|
||||
stateKeys=program.state_keys)
|
||||
save(directory / 'native-summary.json', native_summary)
|
||||
finally:
|
||||
build.close()
|
||||
summary = compare(directory, project, network, audit, settings)
|
||||
summary.update(settings=dict(stop=stop, sampleStep=step, rtol=rtol),
|
||||
amesimRun=ame_summary, nativeRun=native_summary)
|
||||
summaries[name] = summary
|
||||
save(out / 'summary.json', summaries)
|
||||
print(name, 'completed', 'pressure=', summary['groups']['pressure']['worstAbsolute']['maxAbsoluteError'],
|
||||
'temperature=', summary['groups']['temperature']['worstAbsolute']['maxAbsoluteError'],
|
||||
'above5%=', summary['above5PercentCount'], flush=True)
|
||||
unchanged = all(digest(Path(p)) == h for p, h in sources.items())
|
||||
unchanged_code = all(digest(ROOT / p) == h for p, h in code.items())
|
||||
save(out / 'source-verification.json', dict(inputsUnchanged=unchanged, productionUnchanged=unchanged_code,
|
||||
checkedProductionFiles=len(code)))
|
||||
assert unchanged and unchanged_code
|
||||
print('All profiles completed; original inputs and production sources unchanged.', flush=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in new issue
Block a user