Files

72 lines
5.2 KiB
JavaScript
Raw Permalink 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.
/* M1 数据生成器(2026-10-17,方案 §4)
* node ml/gen/gen_dataset.cjs --n 500 --seed 1 --out ml/data/shard_0001.jsonl [--consensus 4]
* --consensus k:邻域共识过滤。起点不变、目标位置各扰动 5 mm 造 k 个邻居跑老师,原任务路径若与多数邻居
* 不在同一绕行类(pathdev.sameClass),整条样本 keep=false(why=consensus)。成本约 (1+k) 倍。
* 每行一条任务:采样 → 老师 → 距离下界认证过滤 → 局部时间律(50 Hz)→ 写出。
* 认证不过的样本也写出(keep=false + 原因),供 stats 统计丢弃率;训练只用 keep=true。
* 2026-10-18 断点续写:第 i 条任务的随机数只由 (seed, i) 决定(sampler.reseed);输出文件已存在时保留其中完整的行、
* 截掉写了一半的最后一行,从下一条接着写 —— 生成几小时被中断,重跑只补剩下的,不重来、不重复。 */
'use strict';
const fs = require('fs'), path = require('path');
const args = {}; for (let i = 2; i < process.argv.length; i += 2) args[process.argv[i].replace(/^--/, '')] = process.argv[i + 1];
const N = +args.n || 100, SEED = +args.seed || 1, OUT = args.out || 'ml/data/shard_' + String(SEED).padStart(4, '0') + '.jsonl';
const { S, ArmIK } = require('../../tests/route_bench_setup.cjs')();
const sampler = require('./sampler.cjs')(S, SEED * 7919 + 13);
const teach = require('./teacher.cjs')(S, ArmIK);
const { sameClass } = require('./pathdev.cjs');
const K_CONS = +args.consensus || 0;
/* --tasks file.jsonl:按给定任务(q0/T/R/mode/cat)请老师,不采样(挖错循环 E2 补路径用) */
const GIVEN = args.tasks ? fs.readFileSync(args.tasks, 'utf8').split('\n').filter(Boolean).map(l => JSON.parse(l)) : null;
const NT = GIVEN ? GIVEN.length : N;
const cert = require('./cert.cjs')(S, {});
const { timeParam } = require('./timelaw.cjs');
const fp = S.modelFingerprint ? S.modelFingerprint() : null;
fs.mkdirSync(path.dirname(OUT), { recursive: true });
let done = 0;
if (fs.existsSync(OUT)) {
const keep = [];
for (const l of fs.readFileSync(OUT, 'utf8').split('\n')) { if (!l) continue; try { JSON.parse(l); keep.push(l); } catch (e) { break; } }
done = Math.min(keep.length, NT);
fs.writeFileSync(OUT, keep.slice(0, done).map(l => l + '\n').join(''));
if (done) process.stderr.write(`[seed ${SEED}] 续写:已有 ${done}/${NT} 条,从第 ${done + 1} 条继续\n`);
}
const fd = fs.openSync(OUT, 'a');
const taskSeed = i => (Math.imul(SEED, 2654435761) ^ Math.imul(i + 1, 40503) ^ 0x9e3779b9) >>> 0;
const r3 = v => Math.round(v * 1000) / 1000;
const t00 = performance.now();
for (let i = done; i < NT; i++) {
sampler.reseed(taskSeed(i));
const task = GIVEN ? Object.assign({ cat: 'E2' }, GIVEN[i], { mode: GIVEN[i].R ? 'pose' : 'pos' }) : sampler.sample();
const t = teach(task);
const rec = { id: 's' + String(SEED).padStart(4, '0') + '_' + String(i).padStart(6, '0'), cat: task.cat, mode: task.mode,
q0: task.q0.map(r3), T: task.T.map(r3), R: task.R, fp };
if (!t.ok) { rec.keep = false; rec.why = t.why; fs.writeSync(fd, JSON.stringify(rec) + '\n'); continue; }
const c0 = performance.now();
let c = null;
if (t.ptsJ6Frozen) { const cf = cert.path(t.ptsJ6Frozen); if (cf.ok) { t.pts = t.ptsJ6Frozen; t.travel = t.travelOf(t.pts); c = cf; rec.j6Frozen = true; } }
if (!c) c = cert.path(t.pts);
if (task.cat === 'D' && t.reached) rec.cat = 'Dr'; // "不可达"采样其实可达:单独记类,不当停靠样本
Object.assign(rec, { path: t.pts.map(q => q.map(r3)), reached: t.reached, err: r3(t.err), oerr: r3(t.oerr), travel_deg: r3(t.travel),
plan_ms: Math.round(t.ms), blocked: t.blocked, poseRelaxed: t.poseRelaxed, detour: t.detour,
cert: { ok: c.ok, queries: c.queries, minGap: r3(c.minGap), ms: Math.round(performance.now() - c0), why: c.why || null, failAt: c.failAt == null ? null : c.failAt } });
/* 训练可用:认证通过;D 类允许未到达(停靠),其余类必须到达;poseRelaxed 的 pose 样本降为 pos */
rec.keep = c.ok && (t.reached || task.cat === 'D');
if (rec.keep && K_CONS > 0 && t.reached) {
let agree = 0, valid = 0;
for (let k = 0; k < K_CONS; k++) {
const u = [sampler.rnd() - 0.5, sampler.rnd() - 0.5, sampler.rnd() - 0.5], n = Math.hypot(u[0], u[1], u[2]);
const nb = teach(Object.assign({}, task, { T: task.T.map((v, j) => v + 5 * u[j] / n) }));
if (!nb.ok || !nb.reached) continue;
valid++; if (sameClass(t.pts, nb.ptsJ6Frozen && rec.j6Frozen ? nb.ptsJ6Frozen : nb.pts, task.mode)) agree++;
}
rec.consensus = { agree, valid };
if (valid >= 2 && agree * 2 < valid) { rec.keep = false; rec.why = 'consensus'; }
}
if (t.poseRelaxed && task.mode === 'pose') { rec.mode = 'pos'; rec.R = null; rec.demoted = true; rec.reached = t.err < 1; }
if (rec.keep) { const tp = timeParam(t.pts); rec.traj = tp.traj.map(q => q.map(r3)); rec.exec_s = r3(tp.steps * 0.02); }
fs.writeSync(fd, JSON.stringify(rec) + '\n');
if ((i + 1) % 25 === 0) process.stderr.write(`[seed ${SEED}] ${i + 1}/${NT} ${((performance.now() - t00) / 1000 / (i + 1 - done)).toFixed(2)} s/条\n`);
}
fs.closeSync(fd);
process.stderr.write(`[seed ${SEED}] done ${NT}(本次 ${NT - done}) in ${((performance.now() - t00) / 1000).toFixed(0)} s\n`);