Files
Robot_Arm/tests/policy_bench.cjs
T
Hark 5591adb39a ml: 第二轮审查 B-1~B-8 处置 + 一键训练控制台
- B-1 运行时投影护栏恢复 5 mm;老师轨迹按认证最小间隙 < 3 mm 剔除(stats/build_dataset,gapDrop)
- B-2 APG 端点+中点 hinge 取均值;B-3 check_r2 空匹配保护、5000×50000、分块 256,pass=false 时 prep 停下
- B-4 R7 按全部 B/C 题统计;B-5 r8 起点贴柱;B-7 pipeline r4(export_onnx --random,训练前测延迟)
- B-8 起步失败 ≤0.2%、column_gap 分块、pipefail、门禁结果先写 .tmp
- 训练控制台:ml/console.bat|sh → ml/monitor.py(开始/停止/进度/日志接口)+ monitor.html;
  pipeline.sh all 支持 FROM/TO,阶段状态写 ml/runs/pipeline.json(ml/pstate.py);训练/APG 日志加 total、t
- 文档:README 一键运行与训练控制台;审查文档追加第三轮处置;方案余量决策与 R4 时点
2026-10-18 06:40:24 +08:00

121 lines
8.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 部署形态评测(方案 §9):策略 + 安全层(webapp/js/ik-policy.js 的 Runner,与浏览器同一份代码)
* node tests/policy_bench.cjs [model=webapp/models/traj_policy.onnx] [sets=nc,route,r8] [--nc1000] [--nc-n 30] [--route-n 40] [--r8-n 50] [--out file.json]
* 集合:nc = near_column_bench 同款 30 个贴立柱任务(pos 与 pose 各跑一遍)
* route = route_bench 同款 40 个直线被挡任务(pos)
* nc1000= 一次性扩展贴立柱集 1000 例(新种子,验收用;加 --nc1000 开启)
* r8 = 方案 R8 对抗集 50 例:J1 起终差 ≥ 140°、直线被挡、终点贴立柱(两侧都要绕远);
* 恢复 = 首次换向锁后 3 s 内到达(无锁则按到达计);活锁 = 超时未到达且锁 ≥ 2 次
* 2026-10-18:--n 拆成 --nc-n / --route-n / --r8-n;输出 brakeRate(刹停次数 / 推理次数,验收 < 2%)。
* 判据:到达(TCP < 1 mm,pose 另需姿态 < 0.5°)且执行轨迹无干涉帧(每步端点+中点 collideFree,外加距离认证复核);
* 超时 10 s。另报告推理 / 安全层耗时 P50/P99(Node CPU,仅作参考;浏览器延迟用查看器 ?policy=1 实测)。
* 依赖:cd ml && npm install(onnxruntime-node) */
'use strict';
const fs = require('fs'), path = require('path');
const argv = process.argv.slice(2);
const flag = k => argv.includes(k);
const opt = k => { const i = argv.indexOf(k); return i >= 0 ? argv[i + 1] : null; };
const VALOPT = new Set(['--out', '--nc-n', '--route-n', '--r8-n']);
const pos = argv.filter((a, i) => !a.startsWith('--') && !(i > 0 && VALOPT.has(argv[i - 1])));
const MODEL = pos[0] || path.join(__dirname, '../webapp/models/traj_policy.onnx');
const SETS = (pos[1] || 'nc,route').split(',').concat(flag('--nc1000') ? ['nc1000'] : []);
let ort; try { ort = require(path.join(__dirname, '../ml/node_modules/onnxruntime-node')); } catch (e) { ort = require('onnxruntime-node'); }
const { S, ArmIK } = require('./route_bench_setup.cjs')();
const POL = require('../webapp/js/ik-policy.js');
const L = S.LIM, O = S.ORDER, w = S.wrap180;
const meta = JSON.parse(fs.readFileSync(MODEL.replace('.onnx', '.meta.json'), 'utf8'));
if (meta.fp !== S.modelFingerprint()) { console.error('模型碰撞指纹', meta.fp, '≠ 当前', S.modelFingerprint(), ':须重训'); process.exit(2); }
if (meta.feat_ver !== POL.FEAT_VER) { console.error('特征版本不符'); process.exit(2); }
function lcg(seed) { let s = seed >>> 0; return () => { s = (s * 1664525 + 1013904223) >>> 0; return s / 4294967296; }; }
function ncCases(N, seed) {
const rnd = lcg(seed);
const rq = () => O.map(id => { const t = L[id]; return t.lo + rnd() * (t.hi - t.lo); });
const d = (a, b) => a.map((v, i) => L[O[i]].cont ? w(b[i] - v) : b[i] - v);
const segOK = (a, b) => { const dd = d(a, b); const m = Math.max(...dd.map(Math.abs)); const n = Math.max(1, Math.ceil(m)); for (let i = 1; i <= n; i++) if (!S.collideFree(a.map((v, k) => v + dd[k] * i / n))) return false; return true; };
const hitsColumn = q => (S.collideDetail(q) || []).some(p => /立柱/.test(p.cnA + p.cnB));
const nearColumn = q => { for (const k of [0, 1, 2]) for (const sg of [-4, 4]) { const p = q.slice(); p[k] += sg; if (hitsColumn(p)) return true; } return false; };
const cases = []; let tries = 0;
while (cases.length < N && tries < 4000000) {
tries++;
const b = rq(); if (!S.collideFree(b) || !nearColumn(b)) continue;
let a = null;
for (let t = 0; t < 200; t++) { const c = rq(); if (S.collideFree(c) && !segOK(c, b)) { a = c; break; } }
if (a) cases.push({ q0: a, T: S.fk(b, 0), R: S.orient(b) });
}
return cases;
}
function r8Cases(N, seed) {
const rnd = lcg(seed);
const rq = () => O.map(id => { const t = L[id]; return t.lo + rnd() * (t.hi - t.lo); });
const d = (a, b) => a.map((v, i) => L[O[i]].cont ? w(b[i] - v) : b[i] - v);
const segOK = (a, b) => { const dd = d(a, b); const m = Math.max(...dd.map(Math.abs)); const n = Math.max(1, Math.ceil(m)); for (let i = 1; i <= n; i++) if (!S.collideFree(a.map((v, k) => v + dd[k] * i / n))) return false; return true; };
const hitsColumn = q => (S.collideDetail(q) || []).some(p => /立柱/.test(p.cnA + p.cnB));
const nearColumn = q => { for (const k of [0, 1, 2]) for (const sg of [-4, 4]) { const p = q.slice(); p[k] += sg; if (hitsColumn(p)) return true; } return false; };
const cases = []; let tries = 0;
while (cases.length < N && tries < 4000000) {
tries++;
const b = rq(); if (!S.collideFree(b) || !nearColumn(b)) continue;
for (let t = 0; t < 200; t++) {
const a = rq(); if (!S.collideFree(a) || !nearColumn(a)) continue; // 2026-10-18:起点也贴柱 → 两侧都要绕远(方案 R8 口径)
if (Math.abs(d(a, b)[0]) < 140 || segOK(a, b)) continue;
cases.push({ q0: a, T: S.fk(b, 0), R: null }); break;
}
}
return cases;
}
function routeCases(N) {
const rnd = lcg(12345);
const rq = () => O.map(id => { const t = L[id]; return t.lo + rnd() * (t.hi - t.lo); });
const d = (a, b) => a.map((v, i) => L[O[i]].cont ? w(b[i] - v) : b[i] - v);
const segOK = (a, b) => { const dd = d(a, b); const m = Math.max(...dd.map(Math.abs)); const n = Math.max(1, Math.ceil(m / 1)); for (let i = 1; i <= n; i++) if (!S.collideFree(a.map((v, k) => v + dd[k] * i / n))) return false; return true; };
const cases = []; let tries = 0;
while (cases.length < N && tries < 20000) { tries++; const a = rq(), b = rq(); if (!S.collideFree(a) || !S.collideFree(b)) continue; if (segOK(a, b)) continue; cases.push({ q0: a, T: S.fk(b, 0), R: null }); }
return cases;
}
const pct = (a, p) => { if (!a.length) return null; const s = a.slice().sort((x, y) => x - y); return +s[Math.min(s.length - 1, Math.floor(s.length * p))].toFixed(2); };
(async () => {
const sess = await ort.InferenceSession.create(MODEL, { executionProviders: ['cpu'], intraOpNumThreads: 1 });
const infer = POL.ortInfer(ort, sess);
const cert = POL.makeCert(S, {});
const out = { model: MODEL, meta: { fp: meta.fp, eval: meta.eval } };
const runSet = async (name, cases, mode) => {
let ok = 0, reached = 0, colTasks = 0, colFrames = 0, uncert = 0, brakes = 0, locks = 0, plans = 0, recovered = 0, lockTasks = 0, livelock = 0;
const inf = [], saf = [], times = [], fails = [];
for (let ci = 0; ci < cases.length; ci++) {
const c = cases[ci];
const R = mode === 'pose' ? c.R : null;
const r = new POL.Runner(S, infer, { seed: 1000 + ci });
r.reset(c.q0); r.setGoal(c.T, R);
let prev = c.q0.slice(), cf = 0, tLock = null;
for (let k = 0; k < 500 && !r.done; k++) {
if (r.needPlan()) await r.plan();
if (tLock == null && r.stats.locks > 0) tLock = r.t;
const q = r.tick();
const mid = prev.map((v, j) => 0.5 * (v + q[j]));
if (!S.collideFree(q) || !S.collideFree(mid)) cf++;
else if (!cert.seg(prev, q)) uncert++;
prev = q.slice();
}
const { e, oe } = r.err(r.q);
const rc = r.done && e < 1 && oe < 0.5;
if (rc) reached++; if (cf) { colTasks++; colFrames += cf; } if (rc && !cf) { ok++; times.push(r.t); } else fails.push({ ci, err: +e.toFixed(1), oerr: +oe.toFixed(1), colFrames: cf, brakes: r.stats.brakes });
inf.push(...r.stats.inferMs); saf.push(...r.stats.safetyMs); brakes += r.stats.brakes; locks += r.stats.locks; plans += r.stats.plans;
if (tLock != null) { lockTasks++; if (rc && !cf && r.t - tLock <= 3.0) recovered++; } else if (rc && !cf) recovered++;
if (!rc && r.stats.locks >= 2) livelock++;
if ((ci + 1) % 50 === 0) process.stderr.write(`${name} ${ci + 1}/${cases.length}\n`);
}
out[name] = { n: cases.length, ok, reached, collisionTasks: colTasks, collisionFrames: colFrames, uncertifiedSegs: uncert, brakes, locks,
plans, brakeRate: plans ? +(brakes / plans).toFixed(4) : null, lockTasks, recovered3s: recovered, recoveryRate: cases.length ? +(recovered / cases.length).toFixed(4) : null, livelock,
timeP50: pct(times, 0.5), inferMsP50: pct(inf, 0.5), inferMsP99: pct(inf, 0.99), safetyMsP50: pct(saf, 0.5), safetyMsP99: pct(saf, 0.99), fails: fails.slice(0, 15) };
console.error(name, JSON.stringify({ ok, n: cases.length, collisionFrames: colFrames }));
};
if (SETS.includes('nc')) { const c = ncCases(+opt('--nc-n') || 30, 20261017); await runSet('near_column_pos', c, 'pos'); await runSet('near_column_pose', c, 'pose'); }
if (SETS.includes('route')) await runSet('route40', routeCases(+opt('--route-n') || 40), 'pos');
if (SETS.includes('r8')) await runSet('r8_adversarial', r8Cases(+opt('--r8-n') || 50, 8888), 'pos');
if (SETS.includes('nc1000')) await runSet('near_column_1000', ncCases(1000, 1), 'pos');
const s = JSON.stringify(out, null, 1);
console.log(s);
if (opt('--out')) fs.writeFileSync(opt('--out'), s);
})();