diff --git a/ml/eval/diag_collision.py b/ml/eval/diag_collision.py new file mode 100644 index 0000000..7fde784 --- /dev/null +++ b/ml/eval/diag_collision.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python +"""闭环碰撞定位:第一次碰撞发生在第几秒、碰撞时最近的是哪两条连杆(J几-J几)。 + +用法(仓库根目录): + PYTHONUTF8=1 python -m ml.eval.diag_collision --ckpt ml/runs/base/last.pt --n 512 --out ml/runs/diag_collision.json + +口径与 closed_loop.rollout 一致:每步同时检查当前状态 q 与相邻状态中点 mid 的间隙(cap=0.5 mm); +间隙组对索引 = gA*7+gB(gA 5 s'] += 1 + break + rows.append({ + 'tid': t.get('tid'), 'cat': t.get('cat'), 'mode': t.get('mode'), + 'collided': bool(per['col'][b] > 0), + 'first_collision_s': None if math.isnan(ft) else round(ft, 2), + 'first_pair': None if name == '—' else name, 'first_pair_idx': None if fp < 0 else fp, + 'first_gap_mm': None if math.isnan(float(first_gap[b])) else round(float(first_gap[b]), 3), + 'min_gap_mm': round(float(min_gap[b]), 3), + 'collision_steps': int(per['col'][b]), + }) + print(f'[diag] {min(i + a.bs, len(tasks))}/{len(tasks)} 已回放', flush=True) + + collided = [r for r in rows if r['collided']] + def pct(v, p): + v = sorted(v); return v[min(len(v) - 1, int(len(v) * p))] if v else None + times = [r['first_collision_s'] for r in collided] + out = { + 'ckpt': a.ckpt, 'step': ck.get('step'), 'n': len(rows), 'n_collided': len(collided), + 'collision_rate': len(collided) / max(1, len(rows)), + 'first_time_s': {'p10': pct(times, .1), 'p50': pct(times, .5), 'p90': pct(times, .9)}, + 'first_time_hist': dict(hist), + 'first_pair_top': pair_first.most_common(), + 'min_gap_mm_p10/p50': [pct([r['min_gap_mm'] for r in rows], .1), pct([r['min_gap_mm'] for r in rows], .5)], + 'by_cat': {}, + 'tasks': rows, + } + for k in sorted({f"{r['cat']}/{r['mode']}" for r in rows}): + g = [r for r in rows if f"{r['cat']}/{r['mode']}" == k] + out['by_cat'][k] = {'n': len(g), 'first_t_p50': pct([r['first_collision_s'] for r in g if r['collided']], .5), + 'first_pairs': Counter([r['first_pair'] for r in g if r['first_pair']]).most_common(3)} + s = json.dumps(out, indent=1, ensure_ascii=False) + if a.out: + open(a.out + '.tmp', 'w', encoding='utf-8').write(s); os.replace(a.out + '.tmp', a.out) + print('== 汇总 ==') + print('题数 %d,撞过 %d(%.1f%%)' % (len(rows), len(collided), 100 * out['collision_rate'])) + print('首次碰撞时间 p10/p50/p90 = %s / %s / %s s' % ( + out['first_time_s']['p10'], out['first_time_s']['p50'], out['first_time_s']['p90'])) + print('首次碰撞时间分布:', out['first_time_hist']) + print('首次碰撞的连杆对 Top:', pair_first.most_common(8)) + print('全程最小间隙 p10/p50 = %s / %s mm' % tuple(out['min_gap_mm_p10/p50'])) + if a.out: print('已写', a.out) + + +if __name__ == '__main__': + main()