C内核流量计算方法优化,前端文件名称读取优化
This commit is contained in:
1 parent
5d5a2e1843
commit
808c484f5b
94 files changed
+20163
-4418
No files matched your search
@@ -0,0 +1,91 @@
|
||||
"""Compare identical native models with the old fixed-point, previous and current pipe solvers.
|
||||
|
||||
Run with Python 3.12 from the repository; generated programs/results stay in --output-dir.
|
||||
The baseline ref is an explicit Git revision, never a frozen numerical result substituted for execution.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import argparse
|
||||
from hashlib import sha256
|
||||
import json
|
||||
from pathlib import Path
|
||||
import statistics
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
OLD_ITERATION = '''double rough_limit=pipe_rough_limit(rr);
|
||||
double base=area*p*cm/sqrt(T),q=sqrt(d/(length*.02))*base;
|
||||
for(int i=0;i<(kind==0?64:16);i++) {
|
||||
double next=sqrt(d/(length*pipe_friction_prepared(4*fabs(q)/den,rr,rough_limit)))*base;
|
||||
if(fabs(next-q)<=fmax(1e-12,fabs(q)*1e-9)) return sign*next;
|
||||
q=.5*(q+next);
|
||||
}
|
||||
return sign*q;'''
|
||||
NEW_CALL = '''double base=area*p*cm/sqrt(T),K=pow(4*base/den,2)*d/length;
|
||||
return sign*native_pipe_resistance(K,rr,den/4,NULL)*den/4;'''
|
||||
|
||||
|
||||
def main():
|
||||
parser=argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('input',type=Path)
|
||||
parser.add_argument('--output-dir',type=Path,required=True)
|
||||
parser.add_argument('--baseline-ref',default='5d5a2e1')
|
||||
parser.add_argument('--runs',type=int,default=3)
|
||||
args=parser.parse_args()
|
||||
if args.runs<1:parser.error('--runs must be positive')
|
||||
out=args.output_dir.resolve();out.mkdir(parents=True,exist_ok=True)
|
||||
if (out/'summary.json').exists():parser.error('Choose a fresh output directory')
|
||||
revision=subprocess.check_output(['git','rev-parse',args.baseline_ref],cwd=ROOT,text=True).strip()
|
||||
xml,doc=load_input(args.input)
|
||||
program=compile_native_program(compile_system_xml_network(doc))
|
||||
config=simulation_config(doc.simulation)
|
||||
(out/'input.xml').write_bytes(xml)
|
||||
(out/('input'+args.input.suffix)).write_bytes(args.input.read_bytes())
|
||||
paths=subprocess.check_output(['git','ls-tree','-r','--name-only',revision,'native'],cwd=ROOT,text=True).splitlines()
|
||||
variants={}
|
||||
original_native=builder.NATIVE
|
||||
try:
|
||||
for variant in ('fixed-point','previous-newton','guarded-newton'):
|
||||
directory=out/variant
|
||||
for name in paths:
|
||||
if variant=='guarded-newton':data=(ROOT/name).read_bytes()
|
||||
else:data=subprocess.check_output(['git','show',f'{revision}:{name}'],cwd=ROOT)
|
||||
target=directory/name;target.parent.mkdir(parents=True,exist_ok=True);target.write_bytes(data)
|
||||
if variant=='fixed-point':
|
||||
target=directory/'native/components/kernels.c';source=target.read_text()
|
||||
if source.count(NEW_CALL)!=1:raise ValueError('Baseline pipe flow layout does not match the audited fixed-point substitution')
|
||||
target.write_text(source.replace(NEW_CALL,OLD_ITERATION))
|
||||
builder.NATIVE=directory/'native'
|
||||
variants[variant]=builder.build_native(program,cache_dir=out/'cache')
|
||||
finally:builder.NATIVE=original_native
|
||||
rows=[]
|
||||
for index in range(args.runs+1):
|
||||
order=list(variants)
|
||||
if index%2:order.reverse()
|
||||
for variant in order:
|
||||
data=execute_native(variants[variant],config,doc.simulation.sample_step,
|
||||
run_dir=out/variant/('warmup' if index==0 else f'run-{index}'),timeout=120)
|
||||
row={key:value for key,value in data.items() if key not in ('series','final','finalState')}
|
||||
row.update(variant=variant,run=index)
|
||||
rows.append(row);print(json.dumps(row,ensure_ascii=False),flush=True)
|
||||
if not data['success']:raise RuntimeError(f'{variant} did not complete: {data["message"]}')
|
||||
summary={'baselineRef':revision,'input':str(args.input.resolve()),
|
||||
'inputSha256':sha256(args.input.read_bytes()).hexdigest(),'xmlSha256':sha256(xml).hexdigest(),
|
||||
'settings':vars(config),'sampleStep':doc.simulation.sample_step,'rows':rows,
|
||||
'variants':{name:{'sourceSha256':sha256((out/name/'native/components/kernels.c').read_bytes()).hexdigest(),
|
||||
'buildKey':build.manifest['buildKey'],
|
||||
'medianSolveSeconds':statistics.median(row['solveSeconds'] for row in rows if row['variant']==name and row['run']>0),
|
||||
'medianProcessSeconds':statistics.median(row['processWallSeconds'] for row in rows if row['variant']==name and row['run']>0)}
|
||||
for name,build in variants.items()}}
|
||||
(out/'summary.json').write_text(json.dumps(summary,ensure_ascii=False,indent=2)+'\n')
|
||||
print(json.dumps(summary['variants'],ensure_ascii=False,indent=2))
|
||||
|
||||
if __name__=='__main__':main()
|
||||
@@ -0,0 +1,89 @@
|
||||
// Real browser/HTTP/native execution. No API routes or result data are mocked.
|
||||
import { chromium } from '../../frontend/node_modules/playwright/index.mjs';
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { performance } from 'node:perf_hooks';
|
||||
import assert from 'node:assert/strict';
|
||||
import { createHash } from 'node:crypto';
|
||||
const [input, output, baseURL = 'http://127.0.0.1:8011', runCount = '3'] = process.argv.slice(2);
|
||||
if (!input || !output) throw new Error('Usage: node browser_native_simulation.mjs MODEL.json OUTPUT_DIR [URL] [RUNS]');
|
||||
await fs.mkdir(output, { recursive: true });
|
||||
const inputText = await fs.readFile(input, 'utf8');
|
||||
const project = JSON.parse(inputText);
|
||||
const curveNodeId = project.nodes.find(node => node.id === 'amesim_pnl0002_10')?.id
|
||||
?? project.nodes.find(node => node.data.modelType === 'amesim_pnl0002')?.id;
|
||||
if (!curveNodeId) throw new Error('Curve verification requires a PNL0002 component.');
|
||||
await fs.writeFile(path.join(output, 'input.json'), inputText);
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
const context = await browser.newContext({ viewport: { width: 1600, height: 1000 }, acceptDownloads: true });
|
||||
const page = await context.newPage();
|
||||
page.setDefaultTimeout(30000);
|
||||
const errors = [];
|
||||
page.on('pageerror', error => errors.push(String(error)));
|
||||
const rows = [];
|
||||
try {
|
||||
await page.goto(baseURL);
|
||||
await page.locator('input[type="file"][accept*=".json"]').setInputFiles(path.resolve(input));
|
||||
await page.getByRole('textbox', { name: '工程', exact: true }).waitFor();
|
||||
await page.waitForFunction(name => document.querySelector('input[aria-label="工程"]')?.value === name || [...document.querySelectorAll('input')].some(input => input.value === name), path.basename(input, path.extname(input)));
|
||||
await page.screenshot({ path: path.join(output, 'model.png'), fullPage: true });
|
||||
for (let i = 0; i <= Number(runCount); i++) {
|
||||
const oldMarker = await page.evaluate(() => sessionStorage.getItem('system-simulation-flow:latest-result'));
|
||||
const responsePromise = page.waitForResponse(response => response.url().endsWith('/api/system-xml/simulate-stream'), { timeout: 180000 });
|
||||
const started = performance.now();
|
||||
await page.getByRole('button', { name: '运行仿真', exact: true }).click();
|
||||
const response = await responsePromise;
|
||||
await response.finished();
|
||||
if (!response.ok()) throw new Error(`Simulation HTTP ${response.status()}`);
|
||||
const receivedMs = performance.now() - started;
|
||||
await page.waitForFunction(old => {
|
||||
const marker = sessionStorage.getItem('system-simulation-flow:latest-result');
|
||||
return marker && marker !== old && JSON.parse(marker).storage === 'indexeddb';
|
||||
}, oldMarker, { timeout: 180000 });
|
||||
await page.getByRole('button', { name: '运行仿真', exact: true }).waitFor({ state: 'visible' });
|
||||
await page.waitForFunction(() => !document.querySelector('button[aria-label="运行仿真"]')?.disabled);
|
||||
await page.getByRole('tab', { name: /^结果/ }).click();
|
||||
await page.getByRole('button', { name: '下载结果文件', exact: true }).waitFor();
|
||||
const readyMs = performance.now() - started;
|
||||
await page.waitForFunction(old => {
|
||||
const marker = sessionStorage.getItem('system-simulation-flow:latest-result');
|
||||
return marker && marker !== old && JSON.parse(marker).storage === 'indexeddb';
|
||||
}, oldMarker);
|
||||
const downloadPromise = page.waitForEvent('download');
|
||||
await page.getByRole('button', { name: '下载结果文件', exact: true }).click();
|
||||
await (await downloadPromise).saveAs(path.join(output, `run-${i}.simresult`));
|
||||
const exported = JSON.parse(await fs.readFile(path.join(output, `run-${i}.simresult`), 'utf8'));
|
||||
const result = exported.snapshot.result;
|
||||
if (!result.success) throw new Error(result.message);
|
||||
assert.equal(result.diagnostics.integration.rtol, 1e-8);
|
||||
assert.equal(result.simulatedUntil, project.simulation.t_stop);
|
||||
const row = { run: i, receivedMs, readyMs, sampleCount: result.series.time.length,
|
||||
...result.diagnostics.native };
|
||||
rows.push(row);
|
||||
console.log(JSON.stringify(row));
|
||||
if (i < Number(runCount)) await page.getByRole('tab', { name: '建模', exact: true }).click();
|
||||
}
|
||||
await page.getByRole('button', { name: '适应系统图窗口', exact: true }).click();
|
||||
await page.locator(`.results-system-panel .react-flow__node[data-id=${JSON.stringify(curveNodeId)}]`).click();
|
||||
await page.locator('.results-variable-list button').filter({ has: page.locator('small', { hasText: /^K$/ }) }).first().click();
|
||||
await page.locator('.results-chart-panel .result-chart-window svg').first().waitFor();
|
||||
const csvDownload = page.waitForEvent('download');
|
||||
await page.getByRole('button', { name: '下载结果 CSV', exact: true }).click();
|
||||
await (await csvDownload).saveAs(path.join(output, 'result.csv'));
|
||||
await page.screenshot({ path: path.join(output, 'result.png'), fullPage: true });
|
||||
await page.reload();
|
||||
await page.getByRole('tab', { name: /^结果/ }).click();
|
||||
await page.getByRole('button', { name: '下载结果文件', exact: true }).waitFor();
|
||||
const restored = page.waitForEvent('download');
|
||||
await page.getByRole('button', { name: '下载结果文件', exact: true }).click();
|
||||
await (await restored).saveAs(path.join(output, 'restored.simresult'));
|
||||
const restoredData = JSON.parse(await fs.readFile(path.join(output, 'restored.simresult'), 'utf8'));
|
||||
const lastData = JSON.parse(await fs.readFile(path.join(output, `run-${runCount}.simresult`), 'utf8'));
|
||||
assert.deepEqual(restoredData.snapshot.result, lastData.snapshot.result);
|
||||
await fs.writeFile(path.join(output, 'summary.json'), JSON.stringify({ input: path.resolve(input), inputSha256: createHash('sha256').update(inputText).digest('hex'), baseURL, browser: browser.version(), rows, errors, restored: true, restoredResultIdentical: true, csvExported: true }, null, 2));
|
||||
if (errors.length) throw new Error(`Browser errors: ${errors.join('\n')}`);
|
||||
} catch (error) {
|
||||
await page.screenshot({ path: path.join(output, 'failure.png'), fullPage: true });
|
||||
await fs.writeFile(path.join(output, 'failure.txt'), `${error.stack}\nPage errors: ${JSON.stringify(errors)}\n${await page.locator('body').innerText()}`);
|
||||
throw error;
|
||||
} finally { await context.close(); await browser.close(); }
|
||||
@@ -0,0 +1,92 @@
|
||||
"""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()
|
||||
Reference in new issue
Block a user