#!/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()