"""Compare real native/browser results to the frozen 72-curve Amesim reference. Run with Python + NumPy. --plot additionally requires Matplotlib, available in this workstation's system Python. Outputs stay in the requested artifact folder. """ from __future__ import annotations import argparse import csv import hashlib import json from pathlib import Path import numpy as np ROOT = Path(__file__).resolve().parents[2] LIMITS = {'pressure': 250., 'temperature': .015, 'displacement': 2e-6, 'velocity': 1e-5, 'mass_flow': 3e-5} UNITS = {'pressure': 'Pa', 'temperature': 'K', 'displacement': 'm', 'velocity': 'm/s', 'mass_flow': 'kg/s'} def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('result', type=Path) parser.add_argument('--reference', type=Path, default=ROOT/'tests/baselines/simulation/test_mql_4/test-mql-4-amesim-reference.json') parser.add_argument('--output-dir', type=Path, required=True) parser.add_argument('--plot', action='store_true') args = parser.parse_args() ref = json.loads(args.reference.read_text()) data = json.loads(args.result.read_text()) if 'snapshot' in data: data = data['snapshot']['result'] series = data['series']; times = np.array(series['time']); ref_times = np.array(ref['times']) if not data['success'] or times[-1] < ref_times[-1] or times[0] > ref_times[0]: raise ValueError('A completed result covering all reference times is required.') if np.any(np.diff(times) < 0): raise ValueError('Native sample times are not ordered.') finite = all(np.all(np.isfinite(values)) for values in series.values()) rows = []; grouped = {} for key, curve in ref['series'].items(): actual = np.interp(ref_times, times, series[key]); error = actual - curve['values'] worst = int(np.argmax(abs(error))); quantity = curve['quantity'] row = {'key': key, 'quantity': quantity, 'unit': UNITS[quantity], 'maxAbsoluteError': float(abs(error[worst])), 'rmsError': float(np.sqrt(np.mean(error**2))), 'worstTime': float(ref_times[worst]), 'limit': LIMITS[quantity], 'passed': bool(np.max(abs(error)) <= LIMITS[quantity])} rows.append(row) if quantity not in grouped or row['maxAbsoluteError'] > grouped[quantity]['maxAbsoluteError']: grouped[quantity] = row masses = np.array([v for k, v in series.items() if k.rsplit('.', 1)[-1] in ('m', 'm1', 'm2')]) total = np.sum(masses, axis=0) mass_drift = float(np.max(abs(total-total[0]))) force_peaks = sorted(({'key': k, 'peakAbsolute': max(map(abs, v))} for k, v in series.items() if k.startswith('amesim_lstp00a_') and k.endswith('.f')), key=lambda row: row['peakAbsolute'], reverse=True) summary = {'resultPath': str(args.result.resolve()), 'resultSha256': hashlib.sha256(args.result.read_bytes()).hexdigest(), 'referencePath': str(args.reference.resolve()), 'referenceSha256': hashlib.sha256(args.reference.read_bytes()).hexdigest(), 'referenceSource': ref['source'], 'freshAmesimRun': False, 'referenceCurveCount': len(rows), 'referenceTimeCount': len(ref_times), 'nativeSampleCount': len(times), 'allSamplesFinite': bool(finite), 'massStateCount': len(masses), 'maxTotalMassDriftKg': mass_drift, 'groupedWorstErrors': grouped, 'curves': rows, 'contactForcePeaksOutsideReferenceScope': force_peaks, 'passed': bool(finite and mass_drift <= 1e-10 and all(row['passed'] for row in rows))} args.output_dir.mkdir(parents=True, exist_ok=True) (args.output_dir/'comparison.json').write_text(json.dumps(summary, ensure_ascii=False, indent=2)+'\n') with (args.output_dir/'curve-errors.csv').open('w', newline='') as output: writer = csv.DictWriter(output, fieldnames=list(rows[0])); writer.writeheader(); writer.writerows(rows) if args.plot: import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt fig, axes = plt.subplots(5, 2, figsize=(13, 15), constrained_layout=True) for (quantity, row), (left, right) in zip(grouped.items(), axes): key = row['key']; values = ref['series'][key]['values'] actual = np.interp(ref_times, times, series[key]) left.plot(times, series[key], linewidth=1, label='Native, rtol=1e-8') left.plot(ref_times, values, '.', markersize=3, label='Saved Amesim reference') left.set_title(key, fontsize=9); left.set_ylabel(UNITS[quantity]); left.legend(fontsize=7) right.plot(ref_times, actual-values, '.-', linewidth=.8, markersize=3) right.axhline(row['limit'], color='gray', linestyle='--', linewidth=.6) right.axhline(-row['limit'], color='gray', linestyle='--', linewidth=.6) right.set_title(f'{quantity}: sampled difference and acceptance limits', fontsize=9) right.set_ylabel(UNITS[quantity]) for ax in (left, right): ax.grid(alpha=.2); ax.set_xlabel('Time [s]') fig.suptitle('Four-branch model: 72 curves checked at 57 stored Amesim times\nWorst curve per quantity; contact force is outside this reference', fontsize=12) fig.savefig(args.output_dir/'comparison.svg'); fig.savefig(args.output_dir/'comparison.png', dpi=140) print(json.dumps({k: summary[k] for k in ('passed', 'nativeSampleCount', 'maxTotalMassDriftKg', 'groupedWorstErrors')}, indent=2)) if not summary['passed']: raise SystemExit(1) if __name__ == '__main__': main()