ml: 全流程代码与运行手册(数据打包/可微运动学与对拍/MeanFlow+WTA 模型/训练/闭环仿真/APG/挖错循环/ONNX 导出);webapp 策略运行时 ik-policy.js + ?policy=1 接入;tests/policy_bench.cjs;ik-solver 增 pairList/clusterExport/criticalPairs/pairGap/leverTable
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
"""对拍:python -m ml.eval.check_parity [--kin ml/assets/kin.json] [--data ml/data/parity.json]
|
||||
检查 PyTorch 版 fk / orient / 组对间隙 / 83 维特征 与 JS 一致,以及间隙梯度与有限差分一致。"""
|
||||
import argparse, json
|
||||
import torch
|
||||
from ml.common.kin_torch import Kin
|
||||
from ml.common.features import features, GAP_CAP
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument('--kin', default=None); ap.add_argument('--data', default='ml/data/parity.json')
|
||||
a = ap.parse_args()
|
||||
kin = Kin(a.kin) if a.kin else Kin(dtype=torch.float64)
|
||||
D = json.load(open(a.data, encoding='utf-8'))
|
||||
assert D['fp'] == kin.fp, f"指纹不符:parity {D['fp']} vs kin {kin.fp}(重跑 export_kin.cjs)"
|
||||
R = D['recs']; f64 = torch.float64
|
||||
q = torch.tensor([r['q'] for r in R], dtype=f64)
|
||||
qp = torch.tensor([r['qPrev'] for r in R], dtype=f64)
|
||||
T = torch.tensor([r['T'] for r in R], dtype=f64)
|
||||
Rt = torch.tensor([r['R'] if r['R'] else [0.] * 9 for r in R], dtype=f64)
|
||||
pre = torch.zeros(len(R), 3, 6, dtype=f64); pl = torch.zeros(len(R), dtype=torch.long)
|
||||
for n, r in enumerate(R):
|
||||
for k, d in enumerate(r['prefix']): pre[n, k] = torch.tensor(d, dtype=f64)
|
||||
pl[n] = len(r['prefix'])
|
||||
sub = torch.tensor([r['sub'] if r['sub'] else [0.] * 6 for r in R], dtype=f64)
|
||||
sf = torch.tensor([1. if r['sub'] else 0. for r in R], dtype=f64)
|
||||
kin = Kin(a.kin, dtype=f64) if a.kin else kin
|
||||
xf = kin.cum_xf(q)
|
||||
e_fk = (kin.fk(q, xf) - torch.tensor([r['fk'] for r in R], dtype=f64)).abs().max().item()
|
||||
e_or = (kin.orient(q, xf).reshape(-1, 9) - torch.tensor([r['orient'] for r in R], dtype=f64)).abs().max().item()
|
||||
G = kin.gaps(q, GAP_CAP, xf)
|
||||
gm_js = torch.tensor([r['gm'] for r in R], dtype=f64)
|
||||
e_gm = (G['gm'] - gm_js.clamp(max=GAP_CAP)).abs().max().item()
|
||||
col_js = gm_js.min(-1).values <= 0; col_py = G['gm'].min(-1).values <= 0
|
||||
f = features(kin, q, qp, T, Rt, pre, pl, sub, sf, gm=G['gm'], xf=xf)
|
||||
e_f = (f - torch.tensor([r['feat'] for r in R], dtype=f64)).abs().max().item()
|
||||
# 梯度:最小间隙对 q 的梯度 vs 有限差分(取 50 个无干涉且间隙 < 20 的样本)
|
||||
sel = ((G['gm'].min(-1).values > 0.5) & (G['gm'].min(-1).values < 20)).nonzero().flatten()[:50]
|
||||
rel = []
|
||||
if len(sel):
|
||||
qs = q[sel].clone().requires_grad_(True)
|
||||
g = kin.gaps(qs, GAP_CAP)['gm'].min(-1).values
|
||||
gr, = torch.autograd.grad(g.sum(), qs)
|
||||
h = 1e-4
|
||||
for j in range(6):
|
||||
dq = torch.zeros_like(qs); dq[:, j] = h
|
||||
fd = (kin.gaps(qs.detach() + dq, GAP_CAP)['gm'].min(-1).values - kin.gaps(qs.detach() - dq, GAP_CAP)['gm'].min(-1).values) / (2 * h)
|
||||
rel.append(((gr[:, j] - fd).abs() / (fd.abs() + 1e-3)).max().item())
|
||||
res = {'n': len(R), 'fk_max_abs_mm': e_fk, 'orient_max_abs': e_or, 'gap_max_abs_mm': e_gm,
|
||||
'collide_bool_mismatch': int((col_js != col_py).sum()), 'feat_max_abs': e_f,
|
||||
'grad_rel_err_max': max(rel) if rel else None}
|
||||
ok = e_fk < 1e-6 and e_or < 1e-9 and e_gm < 1e-4 and res['collide_bool_mismatch'] == 0 and e_f < 1e-4 and (not rel or max(rel) < 1e-3)
|
||||
res['PASS'] = bool(ok)
|
||||
print(json.dumps(res, indent=1))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,25 @@
|
||||
/* 对拍数据:JS 侧 fk / orient / gapMatrix / 特征(ik-policy.js)→ ml/data/parity.json
|
||||
* node ml/eval/dump_parity.cjs [N=2000]
|
||||
* 然后 python -m ml.eval.check_parity 比对 PyTorch 版。 */
|
||||
'use strict';
|
||||
const fs = require('fs'), path = require('path');
|
||||
const N = +process.argv[2] || 2000;
|
||||
const { S } = require('../../tests/route_bench_setup.cjs')();
|
||||
const POL = require('../../webapp/js/ik-policy.js');
|
||||
const L = S.LIM, O = S.ORDER;
|
||||
let st = 99; const rnd = () => { st = (st * 1664525 + 1013904223) >>> 0; return st / 4294967296; };
|
||||
const rq = () => O.map(id => L[id].lo + rnd() * (L[id].hi - L[id].lo));
|
||||
const recs = [];
|
||||
for (let n = 0; n < N; n++) {
|
||||
const q = rq(), qp = q.map((v, i) => v - (rnd() - 0.5) * 2);
|
||||
const b = rq(); const T = S.fk(b, 0); const pose = rnd() < 0.5; const R = pose ? S.orient(b) : null;
|
||||
const r = Math.floor(rnd() * 4); const prefix = []; for (let k = 0; k < r; k++) prefix.push(q.map(() => (rnd() - 0.5) * 2));
|
||||
const sub = rnd() < 0.5 ? rq() : null;
|
||||
const st0 = { q, qPrev: qp, T, R, prefix, sub };
|
||||
const f = POL.features(S, st0);
|
||||
recs.push({ q, qPrev: qp, T, R, prefix, sub, fk: S.fk(q, 0), orient: S.orient(q), gm: Array.from(S.gapMatrix(q, 40)), feat: Array.from(f) });
|
||||
}
|
||||
const out = path.join(__dirname, '../data/parity.json');
|
||||
fs.mkdirSync(path.dirname(out), { recursive: true });
|
||||
fs.writeFileSync(out, JSON.stringify({ fp: S.modelFingerprint(), recs }));
|
||||
console.log('wrote', out, recs.length);
|
||||
Reference in New Issue
Block a user