exp+fix: 轮次31——确定界连续认证抓到段内碰撞,但单段P50 22.9ms进不了20ms;自适应cap收益经交错复测撤回
- Kin.cert_lever 用三角不等式给全姿态位移界(替代 JS 随机采样×1.05),界量级 5.93–48.86 mm/整步 - Kin.certify_segments 批量前沿二分+每根查询预算:48 固定段中预算32判安全37/碰撞1/未知拒10, 第180题1.08–1.10s漏检段以5次查询判碰撞;未知只拒不放 - 修 seg_seg_dist 第二条退化成点不投影(模型内退化胶囊686受影响);但7586个加密点的组最小间隙 逐位不变、2000样本JS对拍仍 PASS → 不解释轮次30漏检、不改既有成绩 - 负结论:B=1 单段认证 P50 22.93/P90 58.43/P99 101.57 ms,432次中225次>20ms; 自适应宽相位 cap 交错复测耗时无收益,先前1.6×为GPU顺序漂移,已在日志撤回 - 成本模型 L=ceil(log2(bound/(ga+gb)))、查询=2^L+1 与实测对齐(安全段2–17次,未知段33–65次) - 新增8项段认证测试+1项回放测试(14→23项通过);变异实测证明张量 max_depth 回绕会误放行
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,658 @@
|
||||
{
|
||||
"segments": 48,
|
||||
"kin_fp": "2ebcbf17",
|
||||
"root_gap_cap_mm": 20.0,
|
||||
"formula": "L = ceil(log2(bound_pair/(ga+gb)))\uff0c\u67e5\u8be2\u6570 = 2^L + 1\uff1bbound_pair \u53d6\u6700\u7d27\u7ec4\u5bf9\uff0c\u4e0d\u7528\u5168\u7ec4\u5bf9\u6700\u5927\u503c",
|
||||
"levels_needed_by_status": {
|
||||
"safe": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
3,
|
||||
3,
|
||||
3,
|
||||
3,
|
||||
3,
|
||||
4,
|
||||
4,
|
||||
4,
|
||||
4,
|
||||
4
|
||||
],
|
||||
"unresolved": [
|
||||
5,
|
||||
5,
|
||||
5,
|
||||
5,
|
||||
5,
|
||||
6,
|
||||
6,
|
||||
6,
|
||||
6,
|
||||
6
|
||||
],
|
||||
"collision": [
|
||||
4
|
||||
]
|
||||
},
|
||||
"queries_needed_safe": [
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
3,
|
||||
3,
|
||||
3,
|
||||
5,
|
||||
5,
|
||||
5,
|
||||
5,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
17,
|
||||
17,
|
||||
17,
|
||||
17,
|
||||
17
|
||||
],
|
||||
"model_vs_measured_max_excess": 20,
|
||||
"unresolved_needed_queries": [
|
||||
33,
|
||||
33,
|
||||
33,
|
||||
33,
|
||||
33,
|
||||
65,
|
||||
65,
|
||||
65,
|
||||
65,
|
||||
65
|
||||
],
|
||||
"collision_dense_check": [
|
||||
{
|
||||
"i": 180,
|
||||
"start_it": 54,
|
||||
"status": "collision",
|
||||
"tight_group_pair": 5,
|
||||
"bound_pair_mm": 29.09,
|
||||
"endpoint_gap_sum_mm": 1.98854,
|
||||
"levels_needed": 4,
|
||||
"queries_needed": 17,
|
||||
"queries_used_batch32": 5
|
||||
}
|
||||
],
|
||||
"note": "\u6a21\u578b\u662f\u201c\u754c\u53ea\u968f\u4e8c\u5206\u51cf\u534a\u201d\u4e0b\u7684\u4e0b\u754c\uff1b\u5b9e\u6d4b\u67e5\u8be2\u53ef\u80fd\u66f4\u5c0f\uff08\u591a\u5c42\u91cc\u6709\u7684\u7ec4\u5bf9\u5148\u6ee1\u8db3\uff09\u6216\u66f4\u5927\uff08\u78b0\u649e\u6bb5\u63d0\u524d\u7ec8\u6b62\uff09\u3002",
|
||||
"rows": [
|
||||
{
|
||||
"i": 0,
|
||||
"start_it": 0,
|
||||
"status": "safe",
|
||||
"tight_group_pair": 18,
|
||||
"bound_pair_mm": 2.382,
|
||||
"endpoint_gap_sum_mm": 17.77168,
|
||||
"levels_needed": 0,
|
||||
"queries_needed": 2,
|
||||
"queries_used_batch32": 2
|
||||
},
|
||||
{
|
||||
"i": 0,
|
||||
"start_it": 8,
|
||||
"status": "unresolved",
|
||||
"tight_group_pair": 13,
|
||||
"bound_pair_mm": 8.2833,
|
||||
"endpoint_gap_sum_mm": 0.25216,
|
||||
"levels_needed": 6,
|
||||
"queries_needed": 65,
|
||||
"queries_used_batch32": 17
|
||||
},
|
||||
{
|
||||
"i": 0,
|
||||
"start_it": 91,
|
||||
"status": "safe",
|
||||
"tight_group_pair": 18,
|
||||
"bound_pair_mm": 2.459,
|
||||
"endpoint_gap_sum_mm": 14.45459,
|
||||
"levels_needed": 0,
|
||||
"queries_needed": 2,
|
||||
"queries_used_batch32": 2
|
||||
},
|
||||
{
|
||||
"i": 0,
|
||||
"start_it": 173,
|
||||
"status": "safe",
|
||||
"tight_group_pair": 18,
|
||||
"bound_pair_mm": 6.8776,
|
||||
"endpoint_gap_sum_mm": 15.48695,
|
||||
"levels_needed": 0,
|
||||
"queries_needed": 2,
|
||||
"queries_used_batch32": 2
|
||||
},
|
||||
{
|
||||
"i": 21,
|
||||
"start_it": 0,
|
||||
"status": "safe",
|
||||
"tight_group_pair": 18,
|
||||
"bound_pair_mm": 4.7095,
|
||||
"endpoint_gap_sum_mm": 12.81844,
|
||||
"levels_needed": 0,
|
||||
"queries_needed": 2,
|
||||
"queries_used_batch32": 2
|
||||
},
|
||||
{
|
||||
"i": 21,
|
||||
"start_it": 100,
|
||||
"status": "unresolved",
|
||||
"tight_group_pair": 18,
|
||||
"bound_pair_mm": 1.3371,
|
||||
"endpoint_gap_sum_mm": 0.03365,
|
||||
"levels_needed": 6,
|
||||
"queries_needed": 65,
|
||||
"queries_used_batch32": 17
|
||||
},
|
||||
{
|
||||
"i": 21,
|
||||
"start_it": 127,
|
||||
"status": "safe",
|
||||
"tight_group_pair": 18,
|
||||
"bound_pair_mm": 1.5104,
|
||||
"endpoint_gap_sum_mm": 0.68046,
|
||||
"levels_needed": 2,
|
||||
"queries_needed": 5,
|
||||
"queries_used_batch32": 4
|
||||
},
|
||||
{
|
||||
"i": 21,
|
||||
"start_it": 282,
|
||||
"status": "safe",
|
||||
"tight_group_pair": 5,
|
||||
"bound_pair_mm": 3.5692,
|
||||
"endpoint_gap_sum_mm": 1.72327,
|
||||
"levels_needed": 2,
|
||||
"queries_needed": 5,
|
||||
"queries_used_batch32": 4
|
||||
},
|
||||
{
|
||||
"i": 84,
|
||||
"start_it": 0,
|
||||
"status": "safe",
|
||||
"tight_group_pair": 18,
|
||||
"bound_pair_mm": 11.7042,
|
||||
"endpoint_gap_sum_mm": 13.79742,
|
||||
"levels_needed": 0,
|
||||
"queries_needed": 2,
|
||||
"queries_used_batch32": 2
|
||||
},
|
||||
{
|
||||
"i": 84,
|
||||
"start_it": 170,
|
||||
"status": "safe",
|
||||
"tight_group_pair": 18,
|
||||
"bound_pair_mm": 4.9891,
|
||||
"endpoint_gap_sum_mm": 0.3999,
|
||||
"levels_needed": 4,
|
||||
"queries_needed": 17,
|
||||
"queries_used_batch32": 18
|
||||
},
|
||||
{
|
||||
"i": 84,
|
||||
"start_it": 234,
|
||||
"status": "unresolved",
|
||||
"tight_group_pair": 18,
|
||||
"bound_pair_mm": 7.5939,
|
||||
"endpoint_gap_sum_mm": 0.28698,
|
||||
"levels_needed": 5,
|
||||
"queries_needed": 33,
|
||||
"queries_used_batch32": 31
|
||||
},
|
||||
{
|
||||
"i": 84,
|
||||
"start_it": 325,
|
||||
"status": "safe",
|
||||
"tight_group_pair": 18,
|
||||
"bound_pair_mm": 3.5738,
|
||||
"endpoint_gap_sum_mm": 10.95173,
|
||||
"levels_needed": 0,
|
||||
"queries_needed": 2,
|
||||
"queries_used_batch32": 2
|
||||
},
|
||||
{
|
||||
"i": 128,
|
||||
"start_it": 0,
|
||||
"status": "safe",
|
||||
"tight_group_pair": 18,
|
||||
"bound_pair_mm": 4.4648,
|
||||
"endpoint_gap_sum_mm": 16.52712,
|
||||
"levels_needed": 0,
|
||||
"queries_needed": 2,
|
||||
"queries_used_batch32": 2
|
||||
},
|
||||
{
|
||||
"i": 128,
|
||||
"start_it": 31,
|
||||
"status": "safe",
|
||||
"tight_group_pair": 18,
|
||||
"bound_pair_mm": 2.1155,
|
||||
"endpoint_gap_sum_mm": 14.24909,
|
||||
"levels_needed": 0,
|
||||
"queries_needed": 2,
|
||||
"queries_used_batch32": 2
|
||||
},
|
||||
{
|
||||
"i": 128,
|
||||
"start_it": 62,
|
||||
"status": "safe",
|
||||
"tight_group_pair": 18,
|
||||
"bound_pair_mm": 0.3619,
|
||||
"endpoint_gap_sum_mm": 12.62201,
|
||||
"levels_needed": 0,
|
||||
"queries_needed": 2,
|
||||
"queries_used_batch32": 2
|
||||
},
|
||||
{
|
||||
"i": 145,
|
||||
"start_it": 0,
|
||||
"status": "safe",
|
||||
"tight_group_pair": 18,
|
||||
"bound_pair_mm": 1.5691,
|
||||
"endpoint_gap_sum_mm": 11.60111,
|
||||
"levels_needed": 0,
|
||||
"queries_needed": 2,
|
||||
"queries_used_batch32": 2
|
||||
},
|
||||
{
|
||||
"i": 145,
|
||||
"start_it": 32,
|
||||
"status": "safe",
|
||||
"tight_group_pair": 4,
|
||||
"bound_pair_mm": 4.6053,
|
||||
"endpoint_gap_sum_mm": 0.67129,
|
||||
"levels_needed": 3,
|
||||
"queries_needed": 9,
|
||||
"queries_used_batch32": 28
|
||||
},
|
||||
{
|
||||
"i": 145,
|
||||
"start_it": 138,
|
||||
"status": "safe",
|
||||
"tight_group_pair": 4,
|
||||
"bound_pair_mm": 9.6062,
|
||||
"endpoint_gap_sum_mm": 1.11765,
|
||||
"levels_needed": 4,
|
||||
"queries_needed": 17,
|
||||
"queries_used_batch32": 15
|
||||
},
|
||||
{
|
||||
"i": 145,
|
||||
"start_it": 294,
|
||||
"status": "safe",
|
||||
"tight_group_pair": 5,
|
||||
"bound_pair_mm": 8.2956,
|
||||
"endpoint_gap_sum_mm": 0.61574,
|
||||
"levels_needed": 4,
|
||||
"queries_needed": 17,
|
||||
"queries_used_batch32": 17
|
||||
},
|
||||
{
|
||||
"i": 164,
|
||||
"start_it": 0,
|
||||
"status": "safe",
|
||||
"tight_group_pair": 18,
|
||||
"bound_pair_mm": 12.0298,
|
||||
"endpoint_gap_sum_mm": 11.50625,
|
||||
"levels_needed": 1,
|
||||
"queries_needed": 3,
|
||||
"queries_used_batch32": 3
|
||||
},
|
||||
{
|
||||
"i": 164,
|
||||
"start_it": 84,
|
||||
"status": "safe",
|
||||
"tight_group_pair": 5,
|
||||
"bound_pair_mm": 18.0143,
|
||||
"endpoint_gap_sum_mm": 1.7857,
|
||||
"levels_needed": 4,
|
||||
"queries_needed": 17,
|
||||
"queries_used_batch32": 18
|
||||
},
|
||||
{
|
||||
"i": 164,
|
||||
"start_it": 182,
|
||||
"status": "unresolved",
|
||||
"tight_group_pair": 5,
|
||||
"bound_pair_mm": 14.1522,
|
||||
"endpoint_gap_sum_mm": 0.38976,
|
||||
"levels_needed": 6,
|
||||
"queries_needed": 65,
|
||||
"queries_used_batch32": 17
|
||||
},
|
||||
{
|
||||
"i": 164,
|
||||
"start_it": 227,
|
||||
"status": "safe",
|
||||
"tight_group_pair": 18,
|
||||
"bound_pair_mm": 5.6457,
|
||||
"endpoint_gap_sum_mm": 1.13586,
|
||||
"levels_needed": 3,
|
||||
"queries_needed": 9,
|
||||
"queries_used_batch32": 10
|
||||
},
|
||||
{
|
||||
"i": 175,
|
||||
"start_it": 0,
|
||||
"status": "safe",
|
||||
"tight_group_pair": 18,
|
||||
"bound_pair_mm": 6.5456,
|
||||
"endpoint_gap_sum_mm": 15.18067,
|
||||
"levels_needed": 0,
|
||||
"queries_needed": 2,
|
||||
"queries_used_batch32": 2
|
||||
},
|
||||
{
|
||||
"i": 175,
|
||||
"start_it": 46,
|
||||
"status": "unresolved",
|
||||
"tight_group_pair": 6,
|
||||
"bound_pair_mm": 30.8668,
|
||||
"endpoint_gap_sum_mm": 1.26529,
|
||||
"levels_needed": 5,
|
||||
"queries_needed": 33,
|
||||
"queries_used_batch32": 32
|
||||
},
|
||||
{
|
||||
"i": 175,
|
||||
"start_it": 75,
|
||||
"status": "safe",
|
||||
"tight_group_pair": 18,
|
||||
"bound_pair_mm": 17.8885,
|
||||
"endpoint_gap_sum_mm": 10.74989,
|
||||
"levels_needed": 1,
|
||||
"queries_needed": 3,
|
||||
"queries_used_batch32": 3
|
||||
},
|
||||
{
|
||||
"i": 175,
|
||||
"start_it": 142,
|
||||
"status": "safe",
|
||||
"tight_group_pair": 18,
|
||||
"bound_pair_mm": 0.8185,
|
||||
"endpoint_gap_sum_mm": 10.77208,
|
||||
"levels_needed": 0,
|
||||
"queries_needed": 2,
|
||||
"queries_used_batch32": 2
|
||||
},
|
||||
{
|
||||
"i": 180,
|
||||
"start_it": 0,
|
||||
"status": "safe",
|
||||
"tight_group_pair": 18,
|
||||
"bound_pair_mm": 3.4654,
|
||||
"endpoint_gap_sum_mm": 14.04707,
|
||||
"levels_needed": 0,
|
||||
"queries_needed": 2,
|
||||
"queries_used_batch32": 2
|
||||
},
|
||||
{
|
||||
"i": 180,
|
||||
"start_it": 14,
|
||||
"status": "unresolved",
|
||||
"tight_group_pair": 4,
|
||||
"bound_pair_mm": 17.6609,
|
||||
"endpoint_gap_sum_mm": 0.46562,
|
||||
"levels_needed": 6,
|
||||
"queries_needed": 65,
|
||||
"queries_used_batch32": 17
|
||||
},
|
||||
{
|
||||
"i": 180,
|
||||
"start_it": 52,
|
||||
"status": "safe",
|
||||
"tight_group_pair": 5,
|
||||
"bound_pair_mm": 27.7231,
|
||||
"endpoint_gap_sum_mm": 3.97272,
|
||||
"levels_needed": 3,
|
||||
"queries_needed": 9,
|
||||
"queries_used_batch32": 15
|
||||
},
|
||||
{
|
||||
"i": 180,
|
||||
"start_it": 54,
|
||||
"status": "collision",
|
||||
"tight_group_pair": 5,
|
||||
"bound_pair_mm": 29.09,
|
||||
"endpoint_gap_sum_mm": 1.98854,
|
||||
"levels_needed": 4,
|
||||
"queries_needed": 17,
|
||||
"queries_used_batch32": 5
|
||||
},
|
||||
{
|
||||
"i": 180,
|
||||
"start_it": 94,
|
||||
"status": "safe",
|
||||
"tight_group_pair": 18,
|
||||
"bound_pair_mm": 1.1032,
|
||||
"endpoint_gap_sum_mm": 12.22495,
|
||||
"levels_needed": 0,
|
||||
"queries_needed": 2,
|
||||
"queries_used_batch32": 2
|
||||
},
|
||||
{
|
||||
"i": 197,
|
||||
"start_it": 0,
|
||||
"status": "safe",
|
||||
"tight_group_pair": 18,
|
||||
"bound_pair_mm": 1.0839,
|
||||
"endpoint_gap_sum_mm": 6.87225,
|
||||
"levels_needed": 0,
|
||||
"queries_needed": 2,
|
||||
"queries_used_batch32": 2
|
||||
},
|
||||
{
|
||||
"i": 197,
|
||||
"start_it": 80,
|
||||
"status": "safe",
|
||||
"tight_group_pair": 18,
|
||||
"bound_pair_mm": 3.3325,
|
||||
"endpoint_gap_sum_mm": 0.50053,
|
||||
"levels_needed": 3,
|
||||
"queries_needed": 9,
|
||||
"queries_used_batch32": 29
|
||||
},
|
||||
{
|
||||
"i": 197,
|
||||
"start_it": 82,
|
||||
"status": "unresolved",
|
||||
"tight_group_pair": 18,
|
||||
"bound_pair_mm": 5.7899,
|
||||
"endpoint_gap_sum_mm": 0.16354,
|
||||
"levels_needed": 6,
|
||||
"queries_needed": 65,
|
||||
"queries_used_batch32": 17
|
||||
},
|
||||
{
|
||||
"i": 197,
|
||||
"start_it": 211,
|
||||
"status": "safe",
|
||||
"tight_group_pair": 5,
|
||||
"bound_pair_mm": 18.6183,
|
||||
"endpoint_gap_sum_mm": 5.55945,
|
||||
"levels_needed": 2,
|
||||
"queries_needed": 5,
|
||||
"queries_used_batch32": 11
|
||||
},
|
||||
{
|
||||
"i": 215,
|
||||
"start_it": 0,
|
||||
"status": "safe",
|
||||
"tight_group_pair": 18,
|
||||
"bound_pair_mm": 2.4256,
|
||||
"endpoint_gap_sum_mm": 12.47554,
|
||||
"levels_needed": 0,
|
||||
"queries_needed": 2,
|
||||
"queries_used_batch32": 2
|
||||
},
|
||||
{
|
||||
"i": 215,
|
||||
"start_it": 104,
|
||||
"status": "safe",
|
||||
"tight_group_pair": 18,
|
||||
"bound_pair_mm": 4.7208,
|
||||
"endpoint_gap_sum_mm": 14.00267,
|
||||
"levels_needed": 0,
|
||||
"queries_needed": 2,
|
||||
"queries_used_batch32": 2
|
||||
},
|
||||
{
|
||||
"i": 215,
|
||||
"start_it": 178,
|
||||
"status": "unresolved",
|
||||
"tight_group_pair": 6,
|
||||
"bound_pair_mm": 25.7963,
|
||||
"endpoint_gap_sum_mm": 1.07014,
|
||||
"levels_needed": 5,
|
||||
"queries_needed": 33,
|
||||
"queries_used_batch32": 30
|
||||
},
|
||||
{
|
||||
"i": 215,
|
||||
"start_it": 215,
|
||||
"status": "safe",
|
||||
"tight_group_pair": 6,
|
||||
"bound_pair_mm": 9.2152,
|
||||
"endpoint_gap_sum_mm": 8.57509,
|
||||
"levels_needed": 1,
|
||||
"queries_needed": 3,
|
||||
"queries_used_batch32": 3
|
||||
},
|
||||
{
|
||||
"i": 224,
|
||||
"start_it": 0,
|
||||
"status": "safe",
|
||||
"tight_group_pair": 18,
|
||||
"bound_pair_mm": 6.492,
|
||||
"endpoint_gap_sum_mm": 10.02154,
|
||||
"levels_needed": 0,
|
||||
"queries_needed": 2,
|
||||
"queries_used_batch32": 2
|
||||
},
|
||||
{
|
||||
"i": 224,
|
||||
"start_it": 52,
|
||||
"status": "unresolved",
|
||||
"tight_group_pair": 18,
|
||||
"bound_pair_mm": 3.5989,
|
||||
"endpoint_gap_sum_mm": 0.12354,
|
||||
"levels_needed": 5,
|
||||
"queries_needed": 33,
|
||||
"queries_used_batch32": 32
|
||||
},
|
||||
{
|
||||
"i": 224,
|
||||
"start_it": 61,
|
||||
"status": "safe",
|
||||
"tight_group_pair": 18,
|
||||
"bound_pair_mm": 2.7788,
|
||||
"endpoint_gap_sum_mm": 0.78961,
|
||||
"levels_needed": 2,
|
||||
"queries_needed": 5,
|
||||
"queries_used_batch32": 5
|
||||
},
|
||||
{
|
||||
"i": 224,
|
||||
"start_it": 127,
|
||||
"status": "safe",
|
||||
"tight_group_pair": 18,
|
||||
"bound_pair_mm": 1.4884,
|
||||
"endpoint_gap_sum_mm": 7.29024,
|
||||
"levels_needed": 0,
|
||||
"queries_needed": 2,
|
||||
"queries_used_batch32": 2
|
||||
},
|
||||
{
|
||||
"i": 255,
|
||||
"start_it": 0,
|
||||
"status": "safe",
|
||||
"tight_group_pair": 18,
|
||||
"bound_pair_mm": 1.5332,
|
||||
"endpoint_gap_sum_mm": 11.184,
|
||||
"levels_needed": 0,
|
||||
"queries_needed": 2,
|
||||
"queries_used_batch32": 2
|
||||
},
|
||||
{
|
||||
"i": 255,
|
||||
"start_it": 25,
|
||||
"status": "unresolved",
|
||||
"tight_group_pair": 18,
|
||||
"bound_pair_mm": 2.9831,
|
||||
"endpoint_gap_sum_mm": 0.14423,
|
||||
"levels_needed": 5,
|
||||
"queries_needed": 33,
|
||||
"queries_used_batch32": 29
|
||||
},
|
||||
{
|
||||
"i": 255,
|
||||
"start_it": 50,
|
||||
"status": "safe",
|
||||
"tight_group_pair": 4,
|
||||
"bound_pair_mm": 7.2365,
|
||||
"endpoint_gap_sum_mm": 0.61112,
|
||||
"levels_needed": 4,
|
||||
"queries_needed": 17,
|
||||
"queries_used_batch32": 25
|
||||
},
|
||||
{
|
||||
"i": 255,
|
||||
"start_it": 181,
|
||||
"status": "safe",
|
||||
"tight_group_pair": 18,
|
||||
"bound_pair_mm": 4.2961,
|
||||
"endpoint_gap_sum_mm": 0.74979,
|
||||
"levels_needed": 3,
|
||||
"queries_needed": 9,
|
||||
"queries_used_batch32": 12
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"baseline_commit": "95da491",
|
||||
"poses": "first 64 existing parity poses",
|
||||
"n_poses": 64,
|
||||
"kin_fp": "2ebcbf17",
|
||||
"dtype": "float64",
|
||||
"degenerate_capsules": [
|
||||
686
|
||||
],
|
||||
"pairs_with_second_degenerate": 670,
|
||||
"evaluated_pairs": 42880,
|
||||
"changed_pairs_over_1e_8": 20661,
|
||||
"old_minus_new_min_mm": 0.0,
|
||||
"old_minus_new_max_mm": 86.00309853133399,
|
||||
"new_reverse_max_abs_mm": 0.0,
|
||||
"worst": {
|
||||
"pose": 29,
|
||||
"first_capsule": 430,
|
||||
"second_capsule": 686,
|
||||
"old_distance_mm": 493.354753665718,
|
||||
"new_distance_mm": 407.35165513438403
|
||||
},
|
||||
"scope": "Raw capsule distances; does not imply these pairs win the group minimum."
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"baseline_commit": "95da491",
|
||||
"sample_source": "docs/experiment_round31_certificates.json",
|
||||
"segments": 48,
|
||||
"dense_points": 7586,
|
||||
"max_step_deg": 0.01,
|
||||
"cap_mm": 0.5,
|
||||
"dtype": "float64",
|
||||
"device": "cuda",
|
||||
"kin_fp": "2ebcbf17",
|
||||
"max_group_gap_difference_mm": 0.0,
|
||||
"group_gaps_changed_over_1e_8": 0,
|
||||
"minimum_gaps_changed_over_1e_8": 0,
|
||||
"collision_bool_mismatches": 0,
|
||||
"wall_s": 4.18259759999637
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"n": 2000,
|
||||
"fk_max_abs_mm": 4.547473508864641e-13,
|
||||
"orient_max_abs": 3.3306690738754696e-16,
|
||||
"gap_max_abs_mm": 2.5224267119483557e-13,
|
||||
"collide_bool_mismatch": 0,
|
||||
"feat_max_abs": 5.960463655974024e-08,
|
||||
"grad_rel_err_max": 1.8474076070450906e-06,
|
||||
"PASS": true
|
||||
}
|
||||
@@ -582,6 +582,27 @@
|
||||
- **新增硬约束证据**:before/after 两组256题实际轨迹都出现超速,最大 **1.660004×VMAX**(来源是动作限幅后叠加0.3×gain2.2反馈)。实际各轴最大速度约 `[99.600,99.600,149.400,149.400,199.200,298.800]°/s`,并非只看原始指令推测。行程仍约参考2.425倍;速度、加速度和真实控制时延均未达部署验收。历史 `travel` 累积的是指令而非夹紧后位移,本轮已同时列出实际轨迹重算值,不悄悄换分母。
|
||||
- **结论与下一步**:逐步限位一致性修复有效,未牺牲本批到达率;但加密审计仍检出段内碰撞,**不能部署、不能宣称零碰撞或50 Hz合格**。下一轮先验证更强的段内认证,再单独评估反馈后的速度/加速度约束与真实端到端调度;每次保持小样本单变量对照,继续保留失败证据。
|
||||
|
||||
## 轮次 31:连续段认证的确定界与成本实测(exp: `exp/dagger-deploy`,判据变强,实测进不了 20 ms)
|
||||
|
||||
- **假设与判据**:轮次 30 的端点/中点检查漏掉段内碰撞(第180题 1.08–1.10 s,段内 −0.14927 mm,端点/中点均正)。若用可证明的全姿态运动界对整段做 Lipschitz 判据,应当(a) 在同一个漏检段上给出碰撞或拒绝,(b) 预算不足时只会拒绝而不会放行,(c) 不放宽 `>0` 判据。判据失败或耗时超预算都算负结论,必须实测不能外推。
|
||||
- **确定界替代随机采样界**:JS 侧 `leverTable(4000)`/`leverBounds(20000)` 是随机姿态最大值再 ×1.05,不是全姿态上界。本轮按三角不等式给闭式界:组 g 的胶囊端点到关节 k(<g) 的最大位移 = (Σ_{j=k}^{g-2}‖P[j+1]−P[j]‖ + max_e‖e−P[g−1]‖)·|Δq_k|(k=g−1 时末项换成端点到该轴的垂直距离),组对 (ga,gb) 只累加 ga..gb−1,取组内最小仍保留 Lipschitz 性质。Python 端 `Kin.cert_lever`(15×6)即此表,未改 `lever`,也未改 JS。
|
||||
- **界的量级**(STEP=[1.2,1.2,1.8,1.8,2.4,3.6]°):单整步在各组对上的位移界 **5.93–48.86 mm**,0 层通过要求端点间隙之和 > 界,即单端间隙至少 3.0–24.4 mm。实际执行段的间隙普遍只有 0.001–0.5 mm,所以“必须细分”是结构性的,不是实现缺陷。
|
||||
- **基础求距 bug 与影响范围**:模型里有 1 个退化胶囊(索引 686,组 3)。旧 `seg_seg_dist` 在第二条线段退化成点时固定取该点为第二条起点,没把点投影到第一条线段上——[0,2] 段对点 1 的实测距离旧值 1 mm、反向调用约 1e−9 mm、真值 0。修复后在已有 2000 个对拍姿态的前 64 个姿态上,670 个“退化胶囊作第二条”的细对里 **20661/42880 个距离变小(旧值偏大,最大 86.003 mm)**,正反调用一致。但这批变化没有进入判定:把轮次 30 修复后 48 段的 7586 个 0.01° 加密点分别用修复前(95da491)与修复后内核重算,**组最小间隙逐位相同(最大差 0.0 mm)、碰撞布尔差 0**;`python -m ml.eval.check_parity` 仍 PASS(2000 样本,fk 4.55e−13 mm、间隙 2.52e−13 mm、特征 5.96e−08、梯度相对误差 1.85e−06、碰撞布尔差 0)。即这轮修复是正确的,但不解释轮次 30 的漏检,也不改变既有成绩。
|
||||
- **认证实现**:`Kin.certify_segments` 为 float64 批量前沿二分,每根段独立查询预算,端点间隙用 `min(gap,cap)` 故钳制只会更保守;判据 `ga+gb > bound+2eps` 且两端 >eps,任一子段碰撞即整根判碰撞;超深度、超预算、非有限间隙、越出关节盒一律拒绝(`unresolved`/`outside_box`),不放行未知。宽相位 cap 可按本层界收到 `bound/2+eps`(`adaptive_cap`)。**尚未接入 rollout,未改查看器默认。**
|
||||
- **48 段固定样本**(`docs/experiment_round31_certificates.json`,沿用轮次 30 的 12 题,每题取首/中/末移动段 + 原证书最小间隙段 + 显式指定 180:54;cap=20、eps=1e−4、max_depth=12):
|
||||
| 每根查询预算 | 判安全 | 判碰撞 | 未知拒绝 | 查询数 p50/max |
|
||||
|---|---|---|---|---|
|
||||
| 2 | 20 | 0 | 28 | 2 / 2 |
|
||||
| 8 | 26 | 1 | 21 | 4 / 8 |
|
||||
| 32 | 37 | 1 | 10 | 4 / 32 |
|
||||
- 已知漏检段(180 题执行段 54)**判碰撞,仅用 5 次查询**;该段原证书 `certified=true`、原内部 `col=0`。48 段里 0.01° 加密采样检出碰撞 1 段,认证接受该类段 **0 段**;整批与逐根调用结果逐位一致。
|
||||
- 拒绝不等于安全:预算 2 时 28 段全部落 `unresolved`,实现不会把未知当通过。
|
||||
- **成本模型对上实测**(`docs/experiment_round31_cert_cost_model.json`):按最紧组对的界,所需层数 `L=ceil(log2(bound_pair/(ga+gb)))`、查询数 `2^L+1`。37 个判安全段需要 2–17 次(L=0..4),10 个未知段需要 **33 或 65 次**(L=5..6),与预算 32 恰好卡在中间一致;碰撞段因提前撞到负间隙只用 5 次。结论:认证成本由 `bound/间隙` 的对数决定,**降低界或提高间隙余量**才是可行杠杆,加预算只是线性买单。
|
||||
- **耗时负结论**(CUDA float64、B=1、驻留输入、每段4次预热+9次计时、变体按段交错,`docs/experiment_round31_adaptive_cap.json`):单段认证 **P50 22.93 ms、P90 58.43 ms、P99 101.57 ms,432 次里 225 次 > 20 ms**;自适应 cap 交错重测 P50 23.21 ms、P99 99.10 ms,**决策完全相同、耗时无收益**。先前顺序测得的“1.6×、整批 72→14.8 ms”经交错复测判定为 GPU 时钟/顺序漂移,不作为结论(48 根整批复测 260→212 ms 仍受同一偏差影响,且部署不会出现 48 根同时认证)。因此**连续认证按当前实现不能进 20 ms,更不能宣称 50 Hz**。
|
||||
- **回归与判据灵敏度**:`python -m unittest tests.test_eval_cl` **23 项通过**(轮次 30 为 14 项)。新增段认证 8 项覆盖:薄障碍段内碰撞被界抓到而清晰段被接受、预算/深度耗尽只拒不放、恒间隙合成段上预算 4/8/9 对应查询 3/5/9(验证细分层数与查询数的解析关系)、批量与逐根及乱序一致、空批与全拒前沿、NaN/±Inf 间隙拒绝、越界拒绝、退化距离对称性与梯度。**变异实测**:把类型门去掉后 `max_depth=torch.tensor(255,dtype=torch.uint8)` 会回绕成 `range(0)`、整段判据被跳过,真实间隙 −0.05 mm 的段直接返回 `safe=true`;加门后同一调用报 `certification budgets must be plain ints`。这是本轮抓到的第二个误放行路径(第一个是轮次 30 的未夹紧认证)。
|
||||
- **口径与边界**:认证只在 float64 下进行,浮点求距误差没有形式上界(`seg_seg_dist` 距离下限约 1e−9 mm),eps=1e−4 mm 是工程容差不是证明;48 段取自轮次 30 修复后的执行轨迹,是固定样本不是新策略闭环;连续认证未接入 rollout,实际执行仍走端点/中点离散检查;速度/加速度超限(轮次 30 实测最大 1.66×VMAX)本轮未处理。
|
||||
- **结论与下一步**:判据确实变强(抓到离散检查漏掉的段内碰撞、未知只拒不放),但成本与 `bound/间隙` 的对数绑定,**当前实现不能部署,也不得把“认证通过”说成零碰撞**。下一步两条:(1) 把逐层依赖的二分改成**一次均匀细分**(固定层数、单个 gaps 调用覆盖全部子段),其实时可行性取决于几何查询成本随批大小是否近似平坦;(2) 把余量要求移到规划/速度侧——实际轨迹贴着 0.001–0.02 mm 间隙走,任何认证方案都会退化成几十次查询,而反馈叠加后的速度约束本来就是未完成项。
|
||||
|
||||
## 分支清理(2026-10-20)
|
||||
|
||||
- `feat/fb-mix`(8e5431c):反馈混合方案的中间分支,**已验证是 `exp/dagger-deploy` 的祖先**(所有提交都在后者历史里),本地/远端分支删除,内容保留在 exp/dagger-deploy。
|
||||
|
||||
+71
-1
@@ -38,7 +38,8 @@ def seg_seg_dist(p1, q1, p2, q2, eps=1e-12):
|
||||
s = torch.where(t_lo, (-c / a.clamp_min(eps)).clamp(0, 1), s)
|
||||
s = torch.where(t_hi, ((b - c) / a.clamp_min(eps)).clamp(0, 1), s)
|
||||
t = t.clamp(0, 1)
|
||||
# 退化(线段长度≈0)
|
||||
# 第二条退化成点时,仍须在第一条上投影,而不是固定取其起点。
|
||||
s = torch.where(e <= eps, (-c / a.clamp_min(eps)).clamp(0, 1), s)
|
||||
s = torch.where(a <= eps, torch.zeros_like(s), s)
|
||||
t = torch.where(e <= eps, torch.zeros_like(t), t)
|
||||
c1 = p1 + d1 * s.unsqueeze(-1)
|
||||
@@ -85,6 +86,25 @@ class Kin:
|
||||
gi, gj = self.cap_g[self.pi], self.cap_g[self.pj]
|
||||
self.pair_gp = torch.minimum(gi, gj) * 7 + torch.maximum(gi, gj)
|
||||
self.lever = T(K['lever'])
|
||||
table = [[0.0] * 7 for _ in range(6)]
|
||||
for group in range(1, 7):
|
||||
endpoints = [c[key] for c in caps if c['g'] == group for key in ('a', 'b')]
|
||||
if not endpoints:
|
||||
continue
|
||||
pivot, axis = K['P'][group - 1], K['D'][group - 1]
|
||||
unit = [v / math.hypot(*axis) for v in axis]
|
||||
relative = [[e[j] - pivot[j] for j in range(3)] for e in endpoints]
|
||||
radius = max(math.hypot(*v) for v in relative)
|
||||
radial = max(math.hypot(v[1] * unit[2] - v[2] * unit[1],
|
||||
v[2] * unit[0] - v[0] * unit[2],
|
||||
v[0] * unit[1] - v[1] * unit[0]) for v in relative)
|
||||
for joint in range(group):
|
||||
chain = sum(math.dist(K['P'][j], K['P'][j + 1]) for j in range(joint, group - 1))
|
||||
table[joint][group] = (chain + (radial if joint == group - 1 else radius)) * D2R
|
||||
self.cert_groups = torch.tensor([a * 7 + b for a in range(7) for b in range(a + 2, 7)], device=device)
|
||||
# 三角不等式给出全姿态界,不用随机姿态最大值冒充上界。
|
||||
self.cert_lever = T([[table[k][b] if a <= k < b else 0.0 for k in range(6)]
|
||||
for a in range(7) for b in range(a + 2, 7)])
|
||||
|
||||
# ---------------- 运动学 ----------------
|
||||
def cum_xf(self, q):
|
||||
@@ -250,6 +270,56 @@ class Kin:
|
||||
gm = gm.scatter_reduce(0, rep_b * len(gp) + self._gp49[self.pair_gp[pidx]], gap.clamp(max=cap), reduce='amin', include_self=True)
|
||||
return gm.view(B, len(gp))
|
||||
|
||||
@torch.no_grad()
|
||||
def certify_segments(self, a, b, max_queries=32, max_depth=12, eps=1e-4, cap=20.0, adaptive_cap=True):
|
||||
if self.dtype != torch.float64 or a.dtype != torch.float64 or b.dtype != torch.float64:
|
||||
raise ValueError('segment certification requires float64')
|
||||
# 预算给成张量时 max_depth + 1 会在 uint8 这类窄类型上回绕成 0,循环整个跳过,把碰撞段当安全返回。
|
||||
if type(max_queries) is not int or type(max_depth) is not int:
|
||||
raise ValueError('certification budgets must be plain ints')
|
||||
if max_queries < 2 or max_depth < 0 or eps <= 0 or cap <= eps:
|
||||
raise ValueError('invalid certification budget or tolerance')
|
||||
count = a.shape[0]
|
||||
status = torch.zeros(count, dtype=torch.long, device=a.device)
|
||||
inside = (((a >= self.lo) & (a <= self.hi) & (b >= self.lo) & (b <= self.hi)) | self.cont).all(-1)
|
||||
status[~inside] = 3
|
||||
root = torch.arange(count, device=a.device)
|
||||
gaps = self.gaps(torch.cat([a, b]), cap)['gm'][:, self.cert_groups]
|
||||
ga, gb = gaps[:count], gaps[count:]
|
||||
queries = torch.full((count,), 2, dtype=torch.long, device=a.device)
|
||||
bound = (b - a).abs() @ self.cert_lever.T
|
||||
for depth in range(max_depth + 1):
|
||||
finite = torch.isfinite(ga).all(-1) & torch.isfinite(gb).all(-1)
|
||||
collision = (ga <= 0).any(-1) | (gb <= 0).any(-1)
|
||||
status[root[~finite]] = 2
|
||||
status[root[collision]] = 1
|
||||
positive = (ga > eps).all(-1) & (gb > eps).all(-1)
|
||||
proven = positive & (ga + gb > bound + 2 * eps).all(-1)
|
||||
pending = (status[root] == 0) & ~proven
|
||||
if not bool(pending.any()):
|
||||
break
|
||||
a, b, ga, gb, root, bound = a[pending], b[pending], ga[pending], gb[pending], root[pending], bound[pending]
|
||||
if depth == max_depth:
|
||||
status[root] = 2
|
||||
break
|
||||
requested = torch.bincount(root, minlength=count)
|
||||
status[(status == 0) & (queries + requested > max_queries)] = 2
|
||||
keep = status[root] == 0
|
||||
a, b, ga, gb, root, bound = (t[keep] for t in (a, b, ga, gb, root, bound))
|
||||
if not root.numel():
|
||||
break
|
||||
mid = a + 0.5 * (b - a)
|
||||
# 子段的界是父段一半;宽相位只需覆盖到能被证明的最小间隙,层越深筛得越狠。
|
||||
qc = min(cap, float(bound.max()) / 2 + eps) if adaptive_cap else cap
|
||||
gm = self.gaps(mid, qc)['gm'][:, self.cert_groups]
|
||||
queries += torch.bincount(root, minlength=count)
|
||||
a, b = torch.cat([a, mid]), torch.cat([mid, b])
|
||||
ga, gb = torch.cat([ga, gm]), torch.cat([gm, gb])
|
||||
root = torch.cat([root, root])
|
||||
bound = torch.cat([bound, bound]) / 2
|
||||
return {'safe': status == 0, 'collision': status == 1, 'unresolved': status == 2,
|
||||
'outside_box': status == 3, 'queries': queries}
|
||||
|
||||
def min_gap(self, q, cap=40.0):
|
||||
return self.gaps(q, cap)['gm'].min(-1).values
|
||||
|
||||
|
||||
+126
-3
@@ -153,11 +153,124 @@ def replay_dense(path, indices, max_step_deg=0.05, device='cpu', out=None):
|
||||
return result
|
||||
|
||||
|
||||
@torch.no_grad()
|
||||
def replay_certificates(path, indices, device='cpu', out=None, repeats=5, extra_segments=()):
|
||||
saved = torch.load(path, map_location='cpu', weights_only=True)
|
||||
kin = Kin(device=device, dtype=torch.float64)
|
||||
if saved.get('kin_fp') != kin.fp:
|
||||
raise ValueError('trace/kin fingerprint mismatch')
|
||||
if not indices or min(indices) < 0 or max(indices) >= len(saved['tasks']) or repeats < 1:
|
||||
raise ValueError('valid task indices and positive repeats required')
|
||||
rows, offset = [], 0
|
||||
for _, per, count in saved['batches']:
|
||||
for index in indices:
|
||||
if not offset <= index < offset + count:
|
||||
continue
|
||||
b = index - offset
|
||||
nstep = min(per['traj'].shape[1] - 1, math.floor((float(per['limit'][b]) + 1e-6) / DT))
|
||||
if torch.isfinite(per['t_done'][b]):
|
||||
nstep = min(nstep, round(float(per['t_done'][b]) / DT))
|
||||
traj = per['traj'][b, :nstep + 1]
|
||||
moving = ((traj[1:] - traj[:-1]).abs().max(-1).values > 1e-6).nonzero().flatten().tolist()
|
||||
if not moving:
|
||||
continue
|
||||
plans = per['plans']
|
||||
nearest, nearest_gap = None, float('inf')
|
||||
for it in moving:
|
||||
pi = int(torch.searchsorted(plans['it'], it, right=True)) - 1
|
||||
step = it - int(plans['it'][pi])
|
||||
if 0 <= step < plans['commit'].shape[2] and plans['certified'][b, pi, step]:
|
||||
gap = float(plans['gaps'][b, pi, [step, plans['commit'].shape[2] + step]].min())
|
||||
if gap < nearest_gap:
|
||||
nearest, nearest_gap = it, gap
|
||||
selected = {moving[0], moving[len(moving) // 2], moving[-1]}
|
||||
if nearest is not None:
|
||||
selected.add(nearest)
|
||||
for task_index, it in extra_segments:
|
||||
if task_index == index:
|
||||
if it not in moving:
|
||||
raise ValueError('extra segment must be a moving segment before deadline or arrival')
|
||||
selected.add(it)
|
||||
for it in sorted(selected):
|
||||
rows.append({'i': index, 'start_it': it, 'a': traj[it].tolist(), 'b': traj[it + 1].tolist()})
|
||||
offset += count
|
||||
if not rows:
|
||||
raise ValueError('no moving segments in selected tasks')
|
||||
a = torch.tensor([r['a'] for r in rows], dtype=torch.float64, device=device)
|
||||
b = torch.tensor([r['b'] for r in rows], dtype=torch.float64, device=device)
|
||||
started = time.perf_counter()
|
||||
print('certificate replay: %d tasks, %d fixed moving segments' % (len(indices), len(rows)), flush=True)
|
||||
summaries = {}
|
||||
for budget in (2, 8, 32):
|
||||
result = kin.certify_segments(a, b, max_queries=budget)
|
||||
summaries[str(budget)] = {key: int(result[key].sum()) for key in ('safe', 'collision', 'unresolved', 'outside_box')}
|
||||
summaries[str(budget)]['queries_p50'] = float(result['queries'].double().quantile(0.5))
|
||||
summaries[str(budget)]['queries_max'] = int(result['queries'].max())
|
||||
for j, row in enumerate(rows):
|
||||
row.setdefault('budgets', {})[str(budget)] = {key: value[j].item() for key, value in result.items()}
|
||||
print('budget %d: %s' % (budget, summaries[str(budget)]), flush=True)
|
||||
latencies = []
|
||||
for j, row in enumerate(rows):
|
||||
aa, bb = a[j:j + 1], b[j:j + 1]
|
||||
discrete = kin.gaps(torch.cat([aa, aa + 0.5 * (bb - aa), bb]), 0.5)['gm'].min(-1).values
|
||||
row['previous_midpoint_endpoint_gap_mm'] = discrete.tolist()
|
||||
nseg = max(2, 2 * math.ceil(float((bb - aa).abs().max()) / 0.02))
|
||||
alpha = torch.linspace(0, 1, nseg + 1, dtype=torch.float64, device=device)
|
||||
points = aa + alpha[:, None] * (bb - aa)
|
||||
dense = torch.cat([kin.gaps(chunk, 0.5)['gm'].min(-1).values for chunk in points.split(64)])
|
||||
row['dense_points'] = len(points)
|
||||
row['dense_min_gap_mm'] = float(dense.min())
|
||||
row['dense_min_fraction'] = float(alpha[dense.argmin()])
|
||||
for _ in range(3):
|
||||
kin.certify_segments(aa, bb)
|
||||
elapsed = []
|
||||
for _ in range(repeats):
|
||||
if a.is_cuda:
|
||||
torch.cuda.synchronize(a.device)
|
||||
t0 = time.perf_counter()
|
||||
result = kin.certify_segments(aa, bb)
|
||||
if a.is_cuda:
|
||||
torch.cuda.synchronize(a.device)
|
||||
elapsed.append((time.perf_counter() - t0) * 1000)
|
||||
row['batch_single_match'] = all(value.item() == row['budgets']['32'][key] for key, value in result.items())
|
||||
row['latency_ms'] = elapsed
|
||||
latencies.extend(elapsed)
|
||||
print('segment %d/%d task %d it %d dense %.6f mm queries %d latency %.2f ms' %
|
||||
(j + 1, len(rows), row['i'], row['start_it'], row['dense_min_gap_mm'],
|
||||
row['budgets']['32']['queries'], sum(elapsed) / len(elapsed)), flush=True)
|
||||
elapsed = torch.tensor(latencies, dtype=torch.float64)
|
||||
result = {
|
||||
'trace': str(path), 'tasks_sha256': saved.get('tasks_sha256'), 'kin_fp': kin.fp,
|
||||
'device': str(device), 'gpu': torch.cuda.get_device_name(a.device) if a.is_cuda else None,
|
||||
'torch_version': str(torch.__version__), 'cpu_threads': torch.get_num_threads(), 'dtype': 'float64',
|
||||
'indices': indices, 'extra_segments': list(extra_segments),
|
||||
'selection': 'First/middle/last moving segment and minimum original certified gap per task; plus explicit extra_segments.',
|
||||
'cap_mm': 20.0, 'eps_mm': 1e-4, 'max_depth': 12, 'dense_max_step_deg': 0.01,
|
||||
'timing': {'scope': 'B=1 certificate only, resident inputs, 3 warmups per segment; synchronized wall time, not policy or scheduling latency.',
|
||||
'repeats_per_segment': repeats, 'calls': len(latencies),
|
||||
'p50_ms': float(elapsed.quantile(0.5)), 'p99_ms': float(elapsed.quantile(0.99)),
|
||||
'max_ms': float(elapsed.max()), 'over_20ms': int((elapsed > 20).sum())},
|
||||
'budgets': summaries,
|
||||
'dense_collision_segments': sum(r['dense_min_gap_mm'] <= 0 for r in rows),
|
||||
'accepted_dense_collision_segments': sum(r['dense_min_gap_mm'] <= 0 and r['budgets']['32']['safe'] for r in rows),
|
||||
'batch_single_match': all(r['batch_single_match'] for r in rows),
|
||||
'scope': 'Fixed recorded segments, not a new rollout. Dense replay is finite sampling; floating-point certification error is not formally bounded.',
|
||||
'wall_s': time.perf_counter() - started, 'rows': rows,
|
||||
}
|
||||
if out:
|
||||
with open(out, 'w', encoding='utf-8') as f:
|
||||
json.dump(result, f, indent=2, allow_nan=False)
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument('--ckpt')
|
||||
ap.add_argument('--trace')
|
||||
ap.add_argument('--dense_indices', help='逗号分隔的任务下标;回放实际轨迹,不依赖原 col 标记')
|
||||
ap.add_argument('--cert_indices', help='固定实际段的连续认证、加密对照和单段计时')
|
||||
ap.add_argument('--cert_segments', default='', help='额外指定任务号:零基段号,逗号分隔')
|
||||
ap.add_argument('--repeats', type=int, default=5)
|
||||
ap.add_argument('--max_step_deg', type=float, default=0.05)
|
||||
ap.add_argument('--samples', default='ml/data/samples')
|
||||
ap.add_argument('--split', default='val')
|
||||
@@ -169,14 +282,24 @@ def main():
|
||||
ap.add_argument('--device', default='cuda' if torch.cuda.is_available() else 'cpu')
|
||||
ap.add_argument('--out', default=None)
|
||||
a = ap.parse_args()
|
||||
if a.dense_indices and a.cert_indices:
|
||||
ap.error('--dense_indices and --cert_indices are mutually exclusive')
|
||||
if a.cert_segments and not a.cert_indices:
|
||||
ap.error('--cert_segments requires --cert_indices')
|
||||
if a.trace:
|
||||
if a.dense_indices:
|
||||
if a.cert_indices:
|
||||
extra = [tuple(map(int, segment.split(':'))) for segment in a.cert_segments.split(',') if segment]
|
||||
indices = [int(i) for i in a.cert_indices.split(',')]
|
||||
if any(len(segment) != 2 or segment[0] not in indices for segment in extra):
|
||||
ap.error('--cert_segments requires task:segment pairs from --cert_indices')
|
||||
replay_certificates(a.trace, indices, a.device, a.out, a.repeats, extra)
|
||||
elif a.dense_indices:
|
||||
replay_dense(a.trace, [int(i) for i in a.dense_indices.split(',')], a.max_step_deg, a.device, a.out)
|
||||
else:
|
||||
replay_first_hits(a.trace, a.out)
|
||||
return
|
||||
if a.dense_indices:
|
||||
ap.error('--dense_indices requires --trace')
|
||||
if a.dense_indices or a.cert_indices:
|
||||
ap.error('--dense_indices and --cert_indices require --trace')
|
||||
if not a.ckpt:
|
||||
ap.error('--ckpt or --trace is required')
|
||||
|
||||
|
||||
+137
-1
@@ -9,8 +9,9 @@ from unittest.mock import patch
|
||||
import torch
|
||||
|
||||
from ml.eval.eval_cl import json_finite, run
|
||||
from ml.eval.diag_collision import replay_dense, replay_first_hits
|
||||
from ml.eval.diag_collision import replay_certificates, replay_dense, replay_first_hits
|
||||
from ml.sim.closed_loop import TeacherOracle, rollout
|
||||
from ml.common.kin_torch import Kin, seg_seg_dist
|
||||
|
||||
|
||||
class EvalMetricsTest(unittest.TestCase):
|
||||
@@ -124,6 +125,30 @@ class EvalMetricsTest(unittest.TestCase):
|
||||
self.assertGreater(row['min_gap64_mm'], 0)
|
||||
self.assertEqual(row['steps'], 1)
|
||||
|
||||
def test_certificate_replay_samples_actual_movement_before_deadlines(self):
|
||||
kin = SegmentCertificateTest.ThinObstacle()
|
||||
kin.fp = 'probe'
|
||||
traj = torch.zeros(3, 4, 6)
|
||||
traj[:, :, 0] = torch.tensor([[0, 1, 1, 1], [0, -0.5, -1, -1], [0, 0, 1, 1]])
|
||||
per = {'traj': traj, 'limit': torch.tensor([0.06, 0.02, 0.02]),
|
||||
't_done': torch.tensor([float('nan'), 0.02, float('nan')]),
|
||||
'plans': {'it': torch.arange(3), 'commit': torch.zeros(3, 3, 1, 6),
|
||||
'certified': torch.ones(3, 3, 1, dtype=torch.bool), 'gaps': torch.full((3, 3, 2), 0.5)}}
|
||||
with tempfile.TemporaryDirectory() as folder:
|
||||
trace, output = Path(folder) / 'trace.pt', Path(folder) / 'audit.json'
|
||||
torch.save({'kin_fp': 'probe', 'tasks': [{}] * 3, 'batches': [({}, per, 3)]}, trace)
|
||||
with patch('ml.eval.diag_collision.Kin', return_value=kin), contextlib.redirect_stdout(io.StringIO()):
|
||||
result = replay_certificates(trace, [0, 1, 2], out=output, repeats=1, extra_segments=[(0, 0)])
|
||||
with self.assertRaisesRegex(ValueError, 'extra segment'):
|
||||
replay_certificates(trace, [1], repeats=1, extra_segments=[(1, 1)])
|
||||
self.assertEqual(json.loads(output.read_text())['dense_collision_segments'], 1)
|
||||
self.assertEqual([(r['i'], r['start_it']) for r in result['rows']], [(0, 0), (1, 0)])
|
||||
self.assertEqual(result['budgets']['32']['safe'], 1)
|
||||
self.assertEqual(result['budgets']['32']['collision'], 1)
|
||||
self.assertEqual(result['accepted_dense_collision_segments'], 0)
|
||||
self.assertTrue(result['batch_single_match'])
|
||||
self.assertEqual(result['timing']['calls'], 2)
|
||||
|
||||
|
||||
class DlsSafetyTest(unittest.TestCase):
|
||||
def test_rejected_refinement_retains_safe_candidate(self):
|
||||
@@ -373,5 +398,116 @@ class ClampedPathSafetyTest(unittest.TestCase):
|
||||
self.assertFalse(per['plans']['certified'].any().item())
|
||||
|
||||
|
||||
class SegmentCertificateTest(unittest.TestCase):
|
||||
class ThinObstacle(Kin):
|
||||
def __init__(self):
|
||||
self.dtype = torch.float64
|
||||
self.lo, self.hi = torch.full((6,), -5.0), torch.full((6,), 5.0)
|
||||
self.cont = torch.zeros(6, dtype=torch.bool)
|
||||
self.cert_groups = torch.tensor([0])
|
||||
self.cert_lever = torch.tensor([[1.0, 0, 0, 0, 0, 0]], dtype=self.dtype)
|
||||
|
||||
def gaps(self, q, cap):
|
||||
return {'gm': ((q[:, :1] - 0.25).abs() - 0.05).clamp(max=cap)}
|
||||
|
||||
def test_bound_detects_hidden_collision_and_accepts_clear_segment(self):
|
||||
a, b = torch.zeros(3, 6, dtype=torch.float64), torch.zeros(3, 6, dtype=torch.float64)
|
||||
a[:, 0] = torch.tensor([0.0, -1.0, 0.25])
|
||||
b[:, 0] = torch.tensor([1.0, 0.0, 0.25])
|
||||
result = self.ThinObstacle().certify_segments(a, b)
|
||||
self.assertEqual(result['safe'].tolist(), [False, True, False])
|
||||
self.assertEqual(result['collision'].tolist(), [True, False, True])
|
||||
self.assertEqual(result['queries'].tolist(), [4, 2, 2])
|
||||
|
||||
def test_budget_and_depth_exhaustion_do_not_accept_unknown_segment(self):
|
||||
a, b = torch.zeros(1, 6, dtype=torch.float64), torch.zeros(1, 6, dtype=torch.float64)
|
||||
b[:, 0] = 1.0
|
||||
kin = self.ThinObstacle()
|
||||
for kwargs in ({'max_queries': 2}, {'max_depth': 0}):
|
||||
with self.subTest(**kwargs):
|
||||
result = kin.certify_segments(a, b, **kwargs)
|
||||
self.assertFalse(result['safe'].item())
|
||||
self.assertTrue(result['unresolved'].item())
|
||||
self.assertEqual(result['queries'].item(), 2)
|
||||
|
||||
def test_budget_exhaustion_scales_with_gap_and_bound(self):
|
||||
class ConstantGap(self.ThinObstacle):
|
||||
def gaps(self, q, cap):
|
||||
return {'gm': torch.full((q.shape[0], 1), 0.1, dtype=q.dtype, device=q.device)}
|
||||
|
||||
# 界取 1 mm/度:1° 段要证明无碰撞,必须细分到位移界 < 两端间隙之和(0.2 mm)。
|
||||
kin = ConstantGap()
|
||||
kin.cert_lever = torch.ones(1, 6, dtype=torch.float64)
|
||||
a, b = torch.zeros(1, 6, dtype=torch.float64), torch.zeros(1, 6, dtype=torch.float64)
|
||||
b[:, 0] = 1.0
|
||||
for budget, safe, queries in ((4, False, 3), (8, False, 5), (9, True, 9)):
|
||||
with self.subTest(budget=budget):
|
||||
result = kin.certify_segments(a, b, max_queries=budget)
|
||||
self.assertEqual(bool(result['safe'].item()), safe)
|
||||
self.assertEqual(int(result['queries'].item()), queries)
|
||||
|
||||
def test_tensor_budget_cannot_wrap_depth(self):
|
||||
a, b = torch.zeros(1, 6, dtype=torch.float64), torch.zeros(1, 6, dtype=torch.float64)
|
||||
a[:, 0] = b[:, 0] = 0.25
|
||||
for tensor in (torch.tensor(255, dtype=torch.uint8), torch.tensor(255)):
|
||||
with self.subTest(dtype=tensor.dtype):
|
||||
with self.assertRaisesRegex(ValueError, 'plain ints'):
|
||||
self.ThinObstacle().certify_segments(a, b, max_depth=tensor)
|
||||
|
||||
def test_batch_matches_per_root_and_ignores_order(self):
|
||||
kin = self.ThinObstacle()
|
||||
a = torch.tensor([[0.0, 0, 0, 0, 0, 0], [0.25, 0, 0, 0, 0, 0], [-1.0, 0, 0, 0, 0, 0]], dtype=torch.float64)
|
||||
b = torch.tensor([[1.0, 0, 0, 0, 0, 0], [0.25, 0, 0, 0, 0, 0], [0.0, 0, 0, 0, 0, 0]], dtype=torch.float64)
|
||||
batch = kin.certify_segments(a, b, max_queries=16)
|
||||
self.assertEqual(batch['collision'].tolist(), [True, True, False])
|
||||
self.assertEqual(batch['safe'].tolist(), [False, False, True])
|
||||
for perm in ([0, 1, 2], [2, 1, 0], [1, 0, 2]):
|
||||
index = torch.tensor(perm)
|
||||
moved = kin.certify_segments(a[index], b[index], max_queries=16)
|
||||
for key in ('safe', 'collision', 'unresolved', 'queries'):
|
||||
torch.testing.assert_close(moved[key], batch[key][index], rtol=0, atol=0)
|
||||
|
||||
def test_empty_batch_and_all_rejected_frontier(self):
|
||||
kin = self.ThinObstacle()
|
||||
empty = kin.certify_segments(torch.zeros(0, 6, dtype=torch.float64), torch.zeros(0, 6, dtype=torch.float64))
|
||||
self.assertEqual(empty['safe'].shape, (0,))
|
||||
a = torch.tensor([[0.25, 0, 0, 0, 0, 0], [6.0, 0, 0, 0, 0, 0]], dtype=torch.float64)
|
||||
result = kin.certify_segments(a, a.clone(), max_queries=2)
|
||||
self.assertEqual(result['safe'].tolist(), [False, False])
|
||||
self.assertEqual(result['collision'].tolist(), [True, False])
|
||||
self.assertEqual(result['outside_box'].tolist(), [False, True])
|
||||
|
||||
def test_nonfinite_and_outside_box_are_rejected(self):
|
||||
a, b = torch.zeros(1, 6, dtype=torch.float64), torch.zeros(1, 6, dtype=torch.float64)
|
||||
kin = self.ThinObstacle()
|
||||
with patch.object(kin, 'gaps', side_effect=lambda q, cap: {'gm': torch.full((q.shape[0], 1), float('nan'))}):
|
||||
result = kin.certify_segments(a, b)
|
||||
self.assertFalse(result['safe'].item())
|
||||
self.assertTrue(result['unresolved'].item())
|
||||
for value in (float('inf'), -float('inf')):
|
||||
with self.subTest(value=value):
|
||||
with patch.object(kin, 'gaps',
|
||||
side_effect=lambda q, cap, v=value: {'gm': torch.full((q.shape[0], 1), v)}):
|
||||
result = kin.certify_segments(a, b)
|
||||
self.assertFalse(result['safe'].item())
|
||||
a[:, 0] = b[:, 0] = 6.0
|
||||
result = kin.certify_segments(a, b)
|
||||
self.assertFalse(result['safe'].item())
|
||||
self.assertTrue(result['outside_box'].item())
|
||||
|
||||
def test_point_segment_distance_is_symmetric_and_differentiable(self):
|
||||
p = torch.tensor([[0.0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]], dtype=torch.float64, requires_grad=True)
|
||||
q = torch.tensor([[2.0, 0, 0]] * 4, dtype=torch.float64)
|
||||
point = torch.tensor([[1.0, 0, 0], [1, 3, 0], [4, 0, 0], [-1, 0, 0]], dtype=torch.float64)
|
||||
forward = seg_seg_dist(p, q, point, point)
|
||||
reverse = seg_seg_dist(point, point, p, q)
|
||||
torch.testing.assert_close(forward, torch.tensor([0.0, 3, 2, 1], dtype=torch.float64), atol=1e-8, rtol=0)
|
||||
torch.testing.assert_close(forward, reverse, atol=1e-8, rtol=0)
|
||||
forward.sum().backward()
|
||||
self.assertTrue(torch.isfinite(p.grad).all())
|
||||
torch.testing.assert_close(seg_seg_dist(p.detach(), p.detach(), point, point),
|
||||
(p.detach() - point).norm(dim=-1), atol=1e-8, rtol=0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user