fix+exp: 轮次30——统一认证与执行限位,到达57.4%不变;加密回放仍检出段内碰撞

This commit is contained in:
cbb
2026-10-20 17:26:20 +08:00
parent 9eaff7341f
commit 95da49193f
12 changed files with 2516 additions and 29 deletions
+62
View File
@@ -0,0 +1,62 @@
{
"config": {
"ckpt": "ml/runs/base_dag2_dag1/best.pt",
"samples": "ml/data/samples",
"tasks": null,
"split": "val",
"n": 256,
"K": 8,
"commit": 2,
"fb": 2.2,
"ki": 0.0,
"fb_scale": 0.0,
"fb_near": 0.2,
"fb_fallback": 0.0,
"kdecay": 0.9,
"kimax": 0.5,
"fb_every": 1,
"safety_try": 0,
"sel_w": 0.1,
"dls_safety": 1,
"fb_clear": 0.0,
"fb_cmin": 0.3,
"sub_refresh": 0,
"fb_block_gain": 0.5,
"dump": "ml/runs/round30_after_rows.json",
"out": "docs/experiment_round30_after.json",
"trace": "ml/runs/round30_after.pt",
"select": "endpoint",
"replan": "empty",
"safety": 1,
"partial": 1,
"max_sec": null,
"seed": 0,
"dls": "analytic",
"device": "cuda"
},
"batch_size": 256,
"batch_seed_rule": "same seed per batch",
"checkpoint_sha256": "f7f686fd8ee92fb6948fbcc4ee954636065ad87bb54f661c4f8f575bc16c2ce1",
"tasks_sha256": "249bd93457825cda98c59e64d9a096d3473c69aeb3553de1dea6d9e56be71772",
"kin_fp": "2ebcbf17",
"act_scale": 1.0,
"metrics": {
"n": 256,
"n_ref": 256,
"success": 0.57421875,
"collision_free_success": 0.57421875,
"time_ok_1p3": 0.390625,
"collision_rate": 0.0,
"collision_steps_mean": 0.0,
"travel_ratio_median": 2.4449527263641357,
"time_mean_s": 2.3076188564300537,
"brakes_mean": 19.25,
"start_fail": 0.0,
"goal_ratio_2s": 0.20997506380081177,
"err_fail_med": 229.68746948242188,
"err_success_med": 0.18899640440940857,
"dls_share": 0.625,
"time_budget": "teacher×2(≥3 s)"
},
"simulation_wall_s": 248.83432349999202
}
+62
View File
@@ -0,0 +1,62 @@
{
"config": {
"ckpt": "ml/runs/base_dag2_dag1/best.pt",
"samples": "ml/data/samples",
"tasks": null,
"split": "val",
"n": 256,
"K": 8,
"commit": 2,
"fb": 2.2,
"ki": 0.0,
"fb_scale": 0.0,
"fb_near": 0.2,
"fb_fallback": 0.0,
"kdecay": 0.9,
"kimax": 0.5,
"fb_every": 1,
"safety_try": 0,
"sel_w": 0.1,
"dls_safety": 1,
"fb_clear": 0.0,
"fb_cmin": 0.3,
"sub_refresh": 0,
"fb_block_gain": 0.5,
"dump": "ml/runs/round30_before_rows.json",
"out": "docs/experiment_round30_before.json",
"trace": "ml/runs/round30_before.pt",
"select": "endpoint",
"replan": "empty",
"safety": 1,
"partial": 1,
"max_sec": null,
"seed": 0,
"dls": "analytic",
"device": "cuda"
},
"batch_size": 256,
"batch_seed_rule": "same seed per batch",
"checkpoint_sha256": "f7f686fd8ee92fb6948fbcc4ee954636065ad87bb54f661c4f8f575bc16c2ce1",
"tasks_sha256": "249bd93457825cda98c59e64d9a096d3473c69aeb3553de1dea6d9e56be71772",
"kin_fp": "2ebcbf17",
"act_scale": 1.0,
"metrics": {
"n": 256,
"n_ref": 256,
"success": 0.57421875,
"collision_free_success": 0.56640625,
"time_ok_1p3": 0.390625,
"collision_rate": 0.02734375,
"collision_steps_mean": 0.0625,
"travel_ratio_median": 2.4449527263641357,
"time_mean_s": 2.2993197441101074,
"brakes_mean": 19.23828125,
"start_fail": 0.0,
"goal_ratio_2s": 0.20997506380081177,
"err_fail_med": 229.68746948242188,
"err_success_med": 0.1950554996728897,
"dls_share": 0.625,
"time_budget": "teacher×2(≥3 s)"
},
"simulation_wall_s": 247.51454629999353
}
+880
View File
@@ -0,0 +1,880 @@
{
"trace": "ml/runs/round30_before.pt",
"n": 256,
"reported_collision_tasks": 7,
"replayed_first_hits32": 7,
"replayed_first_hits64": 7,
"scope": "Replay reported first hits only; same geometry kernel at float32/float64, not a full safety audit.",
"rows": [
{
"i": 21,
"task": {
"tid": 1223280991,
"id": "s0001_000564",
"cat": "A",
"mode": "pose",
"q0": [
178.767,
101.917,
25.283,
31.588,
-12.179,
-6.464
],
"T": [
320.779,
79.294,
350.853
],
"R": [
-0.5548626629953249,
-0.1382477148217311,
-0.820374910976869,
-0.29956278115972873,
-0.8867511636570162,
0.35204334093514156,
-0.7761375943462505,
0.44108949556693955,
0.4506112421399604
],
"exec_s": 2.86,
"travel": 581.023,
"reached": true,
"err": 0,
"split": "val"
},
"first_hit_s": 5.659999847412109,
"dls_it": -1,
"point_order": [
"q0",
"previous",
"midpoint",
"endpoint"
],
"gap32_mm": [
0.5,
0.5,
0.35770511627197266,
-0.3957242965698242
],
"gap64_mm": [
0.5,
0.5,
0.3577231796451059,
-0.39572636491366353
],
"pair32": [
0,
0,
5,
5
],
"pair64": [
0,
0,
5,
5
],
"boundary_joints": [
5
],
"previous": [
139.78468322753906,
87.256591796875,
6.928672790527344,
103.78778839111328,
-94.0,
19.4688720703125
],
"endpoint": [
139.50450134277344,
87.4175796508789,
6.71871280670166,
104.29376983642578,
-94.0,
21.218488693237305
],
"dense_nsegments": 175,
"dense_gap64_min_mm": -0.39572636491366353,
"certificate": {
"plan_start_it": 282,
"step": 0,
"checked": true,
"endpoint": [
139.50450134277344,
87.4175796508789,
6.71871280670166,
104.29376983642578,
-95.717529296875,
21.218488693237305
],
"midpoint": [
139.64459228515625,
87.33708190917969,
6.823692798614502,
104.04077911376953,
-94.8587646484375,
20.34368133544922
],
"recorded_gap32_mm": [
0.1606760025024414,
0.5
],
"replayed_gap64_mm": [
0.16066912536394184,
0.5
],
"command_delta": [
-0.2801789343357086,
0.16098570823669434,
-0.20995977520942688,
0.5059784054756165,
-1.717526912689209,
1.7496168613433838
],
"clamped_reconstruction_max_abs_deg": 0.0,
"certified_vs_executed_max_abs_deg": 1.717529296875
}
},
{
"i": 84,
"task": {
"tid": 1268963905,
"id": "s0002_000538",
"cat": "B",
"mode": "pose",
"q0": [
-49.198,
52.395,
108.796,
75.534,
82.302,
-121.823
],
"T": [
-85.062,
275.378,
888.929
],
"R": [
0.5139412462799992,
-0.4285630099475821,
-0.7431003578769356,
-0.5533754886400825,
-0.8275489878032067,
0.09454227286372051,
-0.6554692700230204,
0.36262435010704513,
-0.6624678231921397
],
"exec_s": 3.2600000000000002,
"travel": 349.09,
"reached": true,
"err": 0,
"split": "val"
},
"first_hit_s": 5.980000019073486,
"dls_it": -1,
"point_order": [
"q0",
"previous",
"midpoint",
"endpoint"
],
"gap32_mm": [
0.5,
0.030836105346679688,
-0.1565418243408203,
-0.11790847778320312
],
"gap64_mm": [
0.5,
0.030838235678215753,
-0.15655898804830848,
-0.11791701257236653
],
"pair32": [
0,
6,
6,
6
],
"pair64": [
0,
6,
6,
6
],
"boundary_joints": [
5
],
"previous": [
-69.87329864501953,
36.559112548828125,
2.094588279724121,
102.22724151611328,
89.0,
-212.1793212890625
],
"endpoint": [
-69.79582214355469,
37.351837158203125,
-0.033895254135131836,
102.45509338378906,
89.0,
-214.75440979003906
],
"dense_nsegments": 258,
"dense_gap64_min_mm": -0.16882067924099076,
"certificate": {
"plan_start_it": 298,
"step": 0,
"checked": true,
"endpoint": [
-69.79582214355469,
37.351837158203125,
-0.033895254135131836,
102.45509338378906,
89.79981231689453,
-214.75440979003906
],
"midpoint": [
-69.83456420898438,
36.955474853515625,
1.0303465127944946,
102.34117126464844,
89.39990234375,
-213.4668731689453
],
"recorded_gap32_mm": [
0.5,
0.20721912384033203
],
"replayed_gap64_mm": [
0.5,
0.2071966639119509
],
"command_delta": [
0.07747504115104675,
0.7927232980728149,
-2.128483533859253,
0.22785267233848572,
0.7998088598251343,
-2.5750951766967773
],
"clamped_reconstruction_max_abs_deg": 0.0,
"certified_vs_executed_max_abs_deg": 0.7998123168945312
}
},
{
"i": 145,
"task": {
"tid": 593213945,
"id": "s0003_000414",
"cat": "B",
"mode": "pose",
"q0": [
-49.395,
84.783,
2.744,
14.841,
-86.783,
81.932
],
"T": [
5.391,
506.723,
383.367
],
"R": [
-0.5269859333114162,
-0.7554822816510317,
-0.38927156099983334,
0.6402732880654818,
-0.05172298162073713,
-0.7664038424760652,
0.5588702377848656,
-0.6531242265324508,
0.5109724082909718
],
"exec_s": 2.96,
"travel": 560.55,
"reached": true,
"err": 0,
"split": "val"
},
"first_hit_s": 5.599999904632568,
"dls_it": -1,
"point_order": [
"q0",
"previous",
"midpoint",
"endpoint"
],
"gap32_mm": [
0.5,
0.5,
0.3180522918701172,
-0.19827747344970703
],
"gap64_mm": [
0.5,
0.5,
0.318071359587325,
-0.19825289671655533
],
"pair32": [
0,
0,
5,
5
],
"pair64": [
0,
0,
5,
5
],
"boundary_joints": [
5
],
"previous": [
-36.05080032348633,
49.852294921875,
22.054914474487305,
67.3380126953125,
-94.0,
79.87274932861328
],
"endpoint": [
-35.60316848754883,
48.9223747253418,
21.386226654052734,
67.87915802001953,
-94.0,
79.27398681640625
],
"dense_nsegments": 93,
"dense_gap64_min_mm": -0.19825289671655533,
"certificate": {
"plan_start_it": 278,
"step": 1,
"checked": true,
"endpoint": [
-35.60316848754883,
48.9223747253418,
21.3862247467041,
67.87915802001953,
-97.17477416992188,
79.27399444580078
],
"midpoint": [
-35.82698440551758,
49.387332916259766,
21.720569610595703,
67.60858154296875,
-96.70620727539062,
79.57337188720703
],
"recorded_gap32_mm": [
0.2614564895629883,
0.5
],
"replayed_gap64_mm": [
0.2614702651611598,
0.5
],
"command_delta": [
0.4476320445537567,
-0.929920494556427,
-0.6686887741088867,
0.5411425828933716,
-0.9371238946914673,
-0.5987589359283447
],
"clamped_reconstruction_max_abs_deg": 0.0,
"certified_vs_executed_max_abs_deg": 3.174774169921875
}
},
{
"i": 164,
"task": {
"tid": 1743026973,
"id": "s0003_000814",
"cat": "C",
"mode": "pos",
"q0": [
-65.982,
5.595,
-64.669,
-72.912,
85.732,
-86.377
],
"T": [
-189.354,
221.034,
302.457
],
"R": null,
"exec_s": 2.3000000000000003,
"travel": 306.824,
"reached": true,
"err": 0,
"split": "val"
},
"first_hit_s": 3.0399999618530273,
"dls_it": -1,
"point_order": [
"q0",
"previous",
"midpoint",
"endpoint"
],
"gap32_mm": [
0.5,
0.5,
0.5,
-0.12672901153564453
],
"gap64_mm": [
0.5,
0.5,
0.5,
-0.12673094091899273
],
"pair32": [
0,
0,
0,
5
],
"pair64": [
0,
0,
0,
5
],
"boundary_joints": [
5
],
"previous": [
-33.6672248840332,
39.63850021362305,
-60.174774169921875,
-108.2870864868164,
88.27082061767578,
-74.56200408935547
],
"endpoint": [
-32.8936882019043,
39.478729248046875,
-58.639915466308594,
-107.65913391113281,
89.0,
-74.92655944824219
],
"dense_nsegments": 154,
"dense_gap64_min_mm": -0.12673094091899273,
"certificate": {
"plan_start_it": 150,
"step": 1,
"checked": true,
"endpoint": [
-32.89369201660156,
39.478729248046875,
-58.639915466308594,
-107.65913391113281,
89.33978271484375,
-74.92655944824219
],
"midpoint": [
-33.28045654296875,
39.55861282348633,
-59.407344818115234,
-107.97311401367188,
88.8052978515625,
-74.7442855834961
],
"recorded_gap32_mm": [
0.1356372833251953,
0.5
],
"replayed_gap64_mm": [
0.13560383338968585,
0.5
],
"command_delta": [
0.773534893989563,
-0.15977251529693604,
1.534859299659729,
0.6279491186141968,
1.068959355354309,
-0.3645569384098053
],
"clamped_reconstruction_max_abs_deg": 0.0,
"certified_vs_executed_max_abs_deg": 0.33978271484375
}
},
{
"i": 175,
"task": {
"tid": 444406422,
"id": "s0003_001100",
"cat": "B",
"mode": "pos",
"q0": [
-28.854,
43.33,
-137.874,
-6.895,
-88.001,
-43.745
],
"T": [
379.105,
293.88,
653.541
],
"R": null,
"exec_s": 2.58,
"travel": 465.057,
"reached": true,
"err": 0,
"split": "val"
},
"first_hit_s": 0.8399999737739563,
"dls_it": 144,
"point_order": [
"q0",
"previous",
"midpoint",
"endpoint"
],
"gap32_mm": [
0.5,
0.4698514938354492,
-0.6612262725830078,
-0.9359521865844727
],
"gap64_mm": [
0.5,
0.4698356780126254,
-0.6612412101671552,
-0.9359581587234
],
"pair32": [
0,
6,
6,
6
],
"pair64": [
0,
6,
6,
6
],
"boundary_joints": [
5
],
"previous": [
-42.48794937133789,
11.756612777709961,
-124.7530288696289,
-46.12489700317383,
-94.0,
-54.57075500488281
],
"endpoint": [
-42.73639678955078,
10.6148681640625,
-124.47537231445312,
-48.52891159057617,
-94.0,
-55.451263427734375
],
"dense_nsegments": 241,
"dense_gap64_min_mm": -0.9544276230224238,
"certificate": {
"plan_start_it": 40,
"step": 1,
"checked": true,
"endpoint": [
-42.73639678955078,
10.6148681640625,
-124.47537231445312,
-48.52891159057617,
-98.31655883789062,
-55.45126724243164
],
"midpoint": [
-42.61217498779297,
11.18574047088623,
-124.61420440673828,
-47.326904296875,
-97.59821319580078,
-55.011009216308594
],
"recorded_gap32_mm": [
0.5,
0.5
],
"replayed_gap64_mm": [
0.5,
0.5
],
"command_delta": [
-0.24844767153263092,
-1.1417450904846191,
0.27765607833862305,
-2.4040162563323975,
-1.4366930723190308,
-0.8805096745491028
],
"clamped_reconstruction_max_abs_deg": 0.0,
"certified_vs_executed_max_abs_deg": 4.316558837890625
}
},
{
"i": 180,
"task": {
"tid": 737760595,
"id": "s0003_001280",
"cat": "B",
"mode": "pos",
"q0": [
41.213,
49.353,
-149.808,
-8.981,
-88.566,
83.45
],
"T": [
118.145,
2.733,
266.082
],
"R": null,
"exec_s": 1.78,
"travel": 228.67,
"reached": true,
"err": 0,
"split": "val"
},
"first_hit_s": 1.1399999856948853,
"dls_it": 102,
"point_order": [
"q0",
"previous",
"midpoint",
"endpoint"
],
"gap32_mm": [
0.5,
0.06517887115478516,
-0.0172576904296875,
0.013133049011230469
],
"gap64_mm": [
0.5,
0.06521150921177998,
-0.017227151210720493,
0.013145878955246104
],
"pair32": [
0,
5,
5,
5
],
"pair64": [
0,
5,
5,
5
],
"boundary_joints": [
5
],
"previous": [
1.8212896585464478,
-9.327469825744629,
-150.87704467773438,
-23.00735855102539,
-94.0,
93.73664855957031
],
"endpoint": [
1.3598560094833374,
-9.66404914855957,
-150.52992248535156,
-23.200042724609375,
-94.0,
92.69589233398438
],
"dense_nsegments": 105,
"dense_gap64_min_mm": -0.020246891242656773,
"certificate": {
"plan_start_it": 56,
"step": 0,
"checked": true,
"endpoint": [
1.3598560094833374,
-9.66404914855957,
-150.52992248535156,
-23.200042724609375,
-95.86970520019531,
92.69589233398438
],
"midpoint": [
1.5905728340148926,
-9.495759010314941,
-150.70347595214844,
-23.103700637817383,
-94.93485260009766,
93.21627044677734
],
"recorded_gap32_mm": [
0.5,
0.20821666717529297
],
"replayed_gap64_mm": [
0.5,
0.20822553884000428
],
"command_delta": [
-0.46143364906311035,
-0.33657926321029663,
0.3471294939517975,
-0.19268345832824707,
-1.8697090148925781,
-1.0407572984695435
],
"clamped_reconstruction_max_abs_deg": 0.0,
"certified_vs_executed_max_abs_deg": 1.8697052001953125
}
},
{
"i": 215,
"task": {
"tid": 169730876,
"id": "s0004_000544",
"cat": "B",
"mode": "pos",
"q0": [
47.051,
-1.179,
-94.31,
-43.128,
67.067,
-119.867
],
"T": [
-142.696,
-139.159,
726.163
],
"R": null,
"exec_s": 2.16,
"travel": 461.684,
"reached": true,
"err": 0,
"split": "val"
},
"first_hit_s": 3.5399999618530273,
"dls_it": -1,
"point_order": [
"q0",
"previous",
"midpoint",
"endpoint"
],
"gap32_mm": [
0.5,
0.5,
0.5,
-0.08817481994628906
],
"gap64_mm": [
0.5,
0.5,
0.5,
-0.08817155079043282
],
"pair32": [
0,
0,
0,
6
],
"pair64": [
0,
0,
0,
6
],
"boundary_joints": [
5
],
"previous": [
34.57734298706055,
32.23564910888672,
-71.76512908935547,
10.783411979675293,
88.44934844970703,
-105.16633605957031
],
"endpoint": [
34.31901931762695,
33.24732208251953,
-72.12022399902344,
10.171412467956543,
89.0,
-106.89080810546875
],
"dense_nsegments": 173,
"dense_gap64_min_mm": -0.08817155079043282,
"certificate": {
"plan_start_it": 176,
"step": 0,
"checked": true,
"endpoint": [
34.31901931762695,
33.24732208251953,
-72.12022399902344,
10.171412467956543,
89.79435729980469,
-106.89080810546875
],
"midpoint": [
34.44818115234375,
32.741485595703125,
-71.94267272949219,
10.477412223815918,
89.12185668945312,
-106.02857208251953
],
"recorded_gap32_mm": [
0.2809743881225586,
0.5
],
"replayed_gap64_mm": [
0.28098007564065064,
0.5
],
"command_delta": [
-0.2583238482475281,
1.011674404144287,
-0.3550943434238434,
-0.6119998693466187,
1.3450126647949219,
-1.7244750261306763
],
"clamped_reconstruction_max_abs_deg": 0.0,
"certified_vs_executed_max_abs_deg": 0.7943572998046875
}
}
]
}
+65
View File
@@ -0,0 +1,65 @@
{
"tasks_sha256": "249bd93457825cda98c59e64d9a096d3473c69aeb3553de1dea6d9e56be71772",
"checkpoint_sha256": "f7f686fd8ee92fb6948fbcc4ee954636065ad87bb54f661c4f8f575bc16c2ce1",
"same_config_except_output_paths": true,
"instrumentation_vs_round29_first_batch_max_abs_deg": 0.0,
"reached_only_after": [],
"reached_only_before": [],
"collision_tasks_before": [
21,
84,
145,
164,
175,
180,
215
],
"collision_tasks_after": [],
"statistics": {
"before": {
"active_certified_steps": 34069,
"certificate_endpoint_mismatch_gt_0p001deg": 5298,
"certificate_endpoint_max_abs_deg": 7.587158203125,
"certificate_midpoint_max_abs_deg": 5.78558349609375,
"uncertified_moving_steps": 0,
"executed_speed_ratio_max": 1.660003662109375,
"executed_speed_limit_violating_tasks": 256,
"executed_speed_max_deg_s": [
99.6002197265625,
99.60002899169922,
149.40032958984375,
149.400146484375,
199.20005798339844,
298.80029296875
],
"executed_travel_ratio_median_success": 2.4250845909118652,
"commanded_travel_ratio_median_success": 2.4449527263641357
},
"after": {
"active_certified_steps": 34107,
"certificate_endpoint_mismatch_gt_0p001deg": 0,
"certificate_endpoint_max_abs_deg": 0.0,
"certificate_midpoint_max_abs_deg": 0.0,
"uncertified_moving_steps": 0,
"executed_speed_ratio_max": 1.660003662109375,
"executed_speed_limit_violating_tasks": 256,
"executed_speed_max_deg_s": [
99.6002197265625,
99.60002899169922,
149.40032958984375,
149.400146484375,
199.20005798339844,
298.80029296875
],
"executed_travel_ratio_median_success": 2.4250845909118652,
"commanded_travel_ratio_median_success": 2.4449527263641357
}
},
"cpu_prediction_microbenchmark": {
"scope": "B=1, commit=2, 100 warmups then 1000 serial calls, CPU one thread; not full planning latency",
"before_p50_ms": 0.0388,
"before_p99_ms": 0.069102,
"after_p50_ms": 0.0518,
"after_p99_ms": 0.093933
}
}
+485
View File
@@ -0,0 +1,485 @@
{
"trace": "ml/runs/round30_after.pt",
"tasks_sha256": "249bd93457825cda98c59e64d9a096d3473c69aeb3553de1dea6d9e56be71772",
"kin_fp": "2ebcbf17",
"indices": [
0,
21,
84,
128,
145,
164,
175,
180,
197,
215,
224,
255
],
"max_step_deg": 0.05,
"dtype": "float64",
"device": "cuda",
"scope": "Executed trajectory through each task deadline or arrival, including initial state; finite sampling, not continuous certification.",
"collision_tasks": 1,
"rows": [
{
"i": 0,
"task": {
"tid": 919549409,
"id": "s0001_000001",
"cat": "B",
"mode": "pos",
"q0": [
-129.897,
46.68,
43.494,
-97.461,
-85.102,
-139.71
],
"T": [
-116.172,
268.408,
179.282
],
"R": null,
"exec_s": 3.48,
"travel": 345.324,
"reached": true,
"err": 0,
"split": "val"
},
"steps": 174,
"sampled_points": 6211,
"initial_gap64_mm": 0.5,
"min_gap64_mm": 0.02673023799951224,
"collision_segments": 0,
"first_collision_s": null
},
{
"i": 21,
"task": {
"tid": 1223280991,
"id": "s0001_000564",
"cat": "A",
"mode": "pose",
"q0": [
178.767,
101.917,
25.283,
31.588,
-12.179,
-6.464
],
"T": [
320.779,
79.294,
350.853
],
"R": [
-0.5548626629953249,
-0.1382477148217311,
-0.820374910976869,
-0.29956278115972873,
-0.8867511636570162,
0.35204334093514156,
-0.7761375943462505,
0.44108949556693955,
0.4506112421399604
],
"exec_s": 2.86,
"travel": 581.023,
"reached": true,
"err": 0,
"split": "val"
},
"steps": 286,
"sampled_points": 6059,
"initial_gap64_mm": 0.5,
"min_gap64_mm": 0.009057896844284485,
"collision_segments": 0,
"first_collision_s": null
},
{
"i": 84,
"task": {
"tid": 1268963905,
"id": "s0002_000538",
"cat": "B",
"mode": "pose",
"q0": [
-49.198,
52.395,
108.796,
75.534,
82.302,
-121.823
],
"T": [
-85.062,
275.378,
888.929
],
"R": [
0.5139412462799992,
-0.4285630099475821,
-0.7431003578769356,
-0.5533754886400825,
-0.8275489878032067,
0.09454227286372051,
-0.6554692700230204,
0.36262435010704513,
-0.6624678231921397
],
"exec_s": 3.2600000000000002,
"travel": 349.09,
"reached": true,
"err": 0,
"split": "val"
},
"steps": 326,
"sampled_points": 6251,
"initial_gap64_mm": 0.5,
"min_gap64_mm": 0.006206321604070197,
"collision_segments": 0,
"first_collision_s": null
},
{
"i": 128,
"task": {
"tid": 408952743,
"id": "s0002_001588",
"cat": "A",
"mode": "pos",
"q0": [
-153.539,
100.454,
108.434,
3.379,
70.254,
-179.848
],
"T": [
-324.009,
70.778,
919.477
],
"R": null,
"exec_s": 0.8200000000000001,
"travel": 121.056,
"reached": true,
"err": 0,
"split": "val"
},
"steps": 63,
"sampled_points": 2403,
"initial_gap64_mm": 0.5,
"min_gap64_mm": 0.5,
"collision_segments": 0,
"first_collision_s": null
},
{
"i": 145,
"task": {
"tid": 593213945,
"id": "s0003_000414",
"cat": "B",
"mode": "pose",
"q0": [
-49.395,
84.783,
2.744,
14.841,
-86.783,
81.932
],
"T": [
5.391,
506.723,
383.367
],
"R": [
-0.5269859333114162,
-0.7554822816510317,
-0.38927156099983334,
0.6402732880654818,
-0.05172298162073713,
-0.7664038424760652,
0.5588702377848656,
-0.6531242265324508,
0.5109724082909718
],
"exec_s": 2.96,
"travel": 560.55,
"reached": true,
"err": 0,
"split": "val"
},
"steps": 296,
"sampled_points": 4509,
"initial_gap64_mm": 0.5,
"min_gap64_mm": 0.002145835308116517,
"collision_segments": 0,
"first_collision_s": null
},
{
"i": 164,
"task": {
"tid": 1743026973,
"id": "s0003_000814",
"cat": "C",
"mode": "pos",
"q0": [
-65.982,
5.595,
-64.669,
-72.912,
85.732,
-86.377
],
"T": [
-189.354,
221.034,
302.457
],
"R": null,
"exec_s": 2.3000000000000003,
"travel": 306.824,
"reached": true,
"err": 0,
"split": "val"
},
"steps": 230,
"sampled_points": 3985,
"initial_gap64_mm": 0.5,
"min_gap64_mm": 0.008857213584009926,
"collision_segments": 0,
"first_collision_s": null
},
{
"i": 175,
"task": {
"tid": 444406422,
"id": "s0003_001100",
"cat": "B",
"mode": "pos",
"q0": [
-28.854,
43.33,
-137.874,
-6.895,
-88.001,
-43.745
],
"T": [
379.105,
293.88,
653.541
],
"R": null,
"exec_s": 2.58,
"travel": 465.057,
"reached": true,
"err": 0,
"split": "val"
},
"steps": 143,
"sampled_points": 5547,
"initial_gap64_mm": 0.5,
"min_gap64_mm": 0.18812628629569694,
"collision_segments": 0,
"first_collision_s": null
},
{
"i": 180,
"task": {
"tid": 737760595,
"id": "s0003_001280",
"cat": "B",
"mode": "pos",
"q0": [
41.213,
49.353,
-149.808,
-8.981,
-88.566,
83.45
],
"T": [
118.145,
2.733,
266.082
],
"R": null,
"exec_s": 1.78,
"travel": 228.67,
"reached": true,
"err": 0,
"split": "val"
},
"steps": 95,
"sampled_points": 3153,
"initial_gap64_mm": 0.5,
"min_gap64_mm": -0.1492959159660998,
"collision_segments": 1,
"first_collision_s": 1.0915789473684212
},
{
"i": 197,
"task": {
"tid": 1433345185,
"id": "s0004_000020",
"cat": "B",
"mode": "pose",
"q0": [
20.112,
-20.013,
-177.85,
32.746,
-70.888,
-30.111
],
"T": [
-288.364,
63.606,
672.54
],
"R": [
-0.5335375732231524,
-0.7509294450860695,
0.38915630081225366,
-0.8443344656004893,
0.4997538915602813,
-0.19324947107169785,
-0.049365657681080755,
-0.4316839311036288,
-0.9006730902323173
],
"exec_s": 2.14,
"travel": 634.022,
"reached": true,
"err": 0,
"split": "val"
},
"steps": 214,
"sampled_points": 4641,
"initial_gap64_mm": 0.5,
"min_gap64_mm": 0.0010684324191032601,
"collision_segments": 0,
"first_collision_s": null
},
{
"i": 215,
"task": {
"tid": 169730876,
"id": "s0004_000544",
"cat": "B",
"mode": "pos",
"q0": [
47.051,
-1.179,
-94.31,
-43.128,
67.067,
-119.867
],
"T": [
-142.696,
-139.159,
726.163
],
"R": null,
"exec_s": 2.16,
"travel": 461.684,
"reached": true,
"err": 0,
"split": "val"
},
"steps": 216,
"sampled_points": 6605,
"initial_gap64_mm": 0.5,
"min_gap64_mm": 0.2112382958686112,
"collision_segments": 0,
"first_collision_s": null
},
{
"i": 224,
"task": {
"tid": 1884410379,
"id": "s0004_000721",
"cat": "A",
"mode": "pose",
"q0": [
-127.447,
7.845,
-124.228,
-20.968,
-21.51,
-35.505
],
"T": [
-233.158,
-59.78,
648.987
],
"R": [
0.3964618999442966,
-0.020495209950317686,
0.9178223729358808,
-0.8179090164279511,
0.4461640898622928,
0.3632663564978371,
-0.41694460392049726,
-0.8947164641865291,
0.1601238457417738
],
"exec_s": 1.48,
"travel": 356.988,
"reached": true,
"err": 0,
"split": "val"
},
"steps": 128,
"sampled_points": 4707,
"initial_gap64_mm": 0.5,
"min_gap64_mm": 0.0041216155493710716,
"collision_segments": 0,
"first_collision_s": null
},
{
"i": 255,
"task": {
"tid": 712677274,
"id": "s0004_001686",
"cat": "C",
"mode": "pos",
"q0": [
-65.548,
71.865,
27.462,
-75.048,
60.325,
151.872
],
"T": [
-53.154,
50.204,
397.489
],
"R": null,
"exec_s": 1.84,
"travel": 238.182,
"reached": true,
"err": 0,
"split": "val"
},
"steps": 184,
"sampled_points": 2495,
"initial_gap64_mm": 0.5,
"min_gap64_mm": 0.004616787795093558,
"collision_segments": 0,
"first_collision_s": null
}
],
"audit_wall_s": 15.056756100006169
}
+485
View File
@@ -0,0 +1,485 @@
{
"trace": "ml/runs/round30_before.pt",
"tasks_sha256": "249bd93457825cda98c59e64d9a096d3473c69aeb3553de1dea6d9e56be71772",
"kin_fp": "2ebcbf17",
"indices": [
0,
21,
84,
128,
145,
164,
175,
180,
197,
215,
224,
255
],
"max_step_deg": 0.05,
"dtype": "float64",
"device": "cuda",
"scope": "Executed trajectory through each task deadline or arrival, including initial state; finite sampling, not continuous certification.",
"collision_tasks": 7,
"rows": [
{
"i": 0,
"task": {
"tid": 919549409,
"id": "s0001_000001",
"cat": "B",
"mode": "pos",
"q0": [
-129.897,
46.68,
43.494,
-97.461,
-85.102,
-139.71
],
"T": [
-116.172,
268.408,
179.282
],
"R": null,
"exec_s": 3.48,
"travel": 345.324,
"reached": true,
"err": 0,
"split": "val"
},
"steps": 174,
"sampled_points": 6211,
"initial_gap64_mm": 0.5,
"min_gap64_mm": 0.02673023799951224,
"collision_segments": 0,
"first_collision_s": null
},
{
"i": 21,
"task": {
"tid": 1223280991,
"id": "s0001_000564",
"cat": "A",
"mode": "pose",
"q0": [
178.767,
101.917,
25.283,
31.588,
-12.179,
-6.464
],
"T": [
320.779,
79.294,
350.853
],
"R": [
-0.5548626629953249,
-0.1382477148217311,
-0.820374910976869,
-0.29956278115972873,
-0.8867511636570162,
0.35204334093514156,
-0.7761375943462505,
0.44108949556693955,
0.4506112421399604
],
"exec_s": 2.86,
"travel": 581.023,
"reached": true,
"err": 0,
"split": "val"
},
"steps": 286,
"sampled_points": 6099,
"initial_gap64_mm": 0.5,
"min_gap64_mm": -0.39572636491366353,
"collision_segments": 2,
"first_collision_s": 5.655
},
{
"i": 84,
"task": {
"tid": 1268963905,
"id": "s0002_000538",
"cat": "B",
"mode": "pose",
"q0": [
-49.198,
52.395,
108.796,
75.534,
82.302,
-121.823
],
"T": [
-85.062,
275.378,
888.929
],
"R": [
0.5139412462799992,
-0.4285630099475821,
-0.7431003578769356,
-0.5533754886400825,
-0.8275489878032067,
0.09454227286372051,
-0.6554692700230204,
0.36262435010704513,
-0.6624678231921397
],
"exec_s": 3.2600000000000002,
"travel": 349.09,
"reached": true,
"err": 0,
"split": "val"
},
"steps": 326,
"sampled_points": 6037,
"initial_gap64_mm": 0.5,
"min_gap64_mm": -0.16878710314368206,
"collision_segments": 3,
"first_collision_s": 5.961153846153847
},
{
"i": 128,
"task": {
"tid": 408952743,
"id": "s0002_001588",
"cat": "A",
"mode": "pos",
"q0": [
-153.539,
100.454,
108.434,
3.379,
70.254,
-179.848
],
"T": [
-324.009,
70.778,
919.477
],
"R": null,
"exec_s": 0.8200000000000001,
"travel": 121.056,
"reached": true,
"err": 0,
"split": "val"
},
"steps": 63,
"sampled_points": 2403,
"initial_gap64_mm": 0.5,
"min_gap64_mm": 0.5,
"collision_segments": 0,
"first_collision_s": null
},
{
"i": 145,
"task": {
"tid": 593213945,
"id": "s0003_000414",
"cat": "B",
"mode": "pose",
"q0": [
-49.395,
84.783,
2.744,
14.841,
-86.783,
81.932
],
"T": [
5.391,
506.723,
383.367
],
"R": [
-0.5269859333114162,
-0.7554822816510317,
-0.38927156099983334,
0.6402732880654818,
-0.05172298162073713,
-0.7664038424760652,
0.5588702377848656,
-0.6531242265324508,
0.5109724082909718
],
"exec_s": 2.96,
"travel": 560.55,
"reached": true,
"err": 0,
"split": "val"
},
"steps": 296,
"sampled_points": 4489,
"initial_gap64_mm": 0.5,
"min_gap64_mm": -0.19825289671655533,
"collision_segments": 5,
"first_collision_s": 5.597
},
{
"i": 164,
"task": {
"tid": 1743026973,
"id": "s0003_000814",
"cat": "C",
"mode": "pos",
"q0": [
-65.982,
5.595,
-64.669,
-72.912,
85.732,
-86.377
],
"T": [
-189.354,
221.034,
302.457
],
"R": null,
"exec_s": 2.3000000000000003,
"travel": 306.824,
"reached": true,
"err": 0,
"split": "val"
},
"steps": 230,
"sampled_points": 3865,
"initial_gap64_mm": 0.5,
"min_gap64_mm": -0.12673094091895543,
"collision_segments": 4,
"first_collision_s": 3.0387500000000003
},
{
"i": 175,
"task": {
"tid": 444406422,
"id": "s0003_001100",
"cat": "B",
"mode": "pos",
"q0": [
-28.854,
43.33,
-137.874,
-6.895,
-88.001,
-43.745
],
"T": [
379.105,
293.88,
653.541
],
"R": null,
"exec_s": 2.58,
"travel": 465.057,
"reached": true,
"err": 0,
"split": "val"
},
"steps": 145,
"sampled_points": 5769,
"initial_gap64_mm": 0.5,
"min_gap64_mm": -0.954427674326439,
"collision_segments": 2,
"first_collision_s": 0.8236
},
{
"i": 180,
"task": {
"tid": 737760595,
"id": "s0003_001280",
"cat": "B",
"mode": "pos",
"q0": [
41.213,
49.353,
-149.808,
-8.981,
-88.566,
83.45
],
"T": [
118.145,
2.733,
266.082
],
"R": null,
"exec_s": 1.78,
"travel": 228.67,
"reached": true,
"err": 0,
"split": "val"
},
"steps": 103,
"sampled_points": 3385,
"initial_gap64_mm": 0.5,
"min_gap64_mm": -0.1492959159660998,
"collision_segments": 2,
"first_collision_s": 1.0915789473684212
},
{
"i": 197,
"task": {
"tid": 1433345185,
"id": "s0004_000020",
"cat": "B",
"mode": "pose",
"q0": [
20.112,
-20.013,
-177.85,
32.746,
-70.888,
-30.111
],
"T": [
-288.364,
63.606,
672.54
],
"R": [
-0.5335375732231524,
-0.7509294450860695,
0.38915630081225366,
-0.8443344656004893,
0.4997538915602813,
-0.19324947107169785,
-0.049365657681080755,
-0.4316839311036288,
-0.9006730902323173
],
"exec_s": 2.14,
"travel": 634.022,
"reached": true,
"err": 0,
"split": "val"
},
"steps": 214,
"sampled_points": 4317,
"initial_gap64_mm": 0.5,
"min_gap64_mm": 0.0010684324191032601,
"collision_segments": 0,
"first_collision_s": null
},
{
"i": 215,
"task": {
"tid": 169730876,
"id": "s0004_000544",
"cat": "B",
"mode": "pos",
"q0": [
47.051,
-1.179,
-94.31,
-43.128,
67.067,
-119.867
],
"T": [
-142.696,
-139.159,
726.163
],
"R": null,
"exec_s": 2.16,
"travel": 461.684,
"reached": true,
"err": 0,
"split": "val"
},
"steps": 216,
"sampled_points": 6341,
"initial_gap64_mm": 0.5,
"min_gap64_mm": -0.08817155079042394,
"collision_segments": 3,
"first_collision_s": 3.538888888888889
},
{
"i": 224,
"task": {
"tid": 1884410379,
"id": "s0004_000721",
"cat": "A",
"mode": "pose",
"q0": [
-127.447,
7.845,
-124.228,
-20.968,
-21.51,
-35.505
],
"T": [
-233.158,
-59.78,
648.987
],
"R": [
0.3964618999442966,
-0.020495209950317686,
0.9178223729358808,
-0.8179090164279511,
0.4461640898622928,
0.3632663564978371,
-0.41694460392049726,
-0.8947164641865291,
0.1601238457417738
],
"exec_s": 1.48,
"travel": 356.988,
"reached": true,
"err": 0,
"split": "val"
},
"steps": 128,
"sampled_points": 4707,
"initial_gap64_mm": 0.5,
"min_gap64_mm": 0.0041216155493710716,
"collision_segments": 0,
"first_collision_s": null
},
{
"i": 255,
"task": {
"tid": 712677274,
"id": "s0004_001686",
"cat": "C",
"mode": "pos",
"q0": [
-65.548,
71.865,
27.462,
-75.048,
60.325,
151.872
],
"T": [
-53.154,
50.204,
397.489
],
"R": null,
"exec_s": 1.84,
"travel": 238.182,
"reached": true,
"err": 0,
"split": "val"
},
"steps": 184,
"sampled_points": 2495,
"initial_gap64_mm": 0.5,
"min_gap64_mm": 0.004616787795093558,
"collision_segments": 0,
"first_collision_s": null
}
],
"audit_wall_s": 14.869853199998033
}
@@ -0,0 +1,71 @@
{
"task_index": 180,
"task": {
"tid": 737760595,
"id": "s0003_001280",
"cat": "B",
"mode": "pos",
"q0": [
41.213,
49.353,
-149.808,
-8.981,
-88.566,
83.45
],
"T": [
118.145,
2.733,
266.082
],
"R": null,
"exec_s": 1.78,
"travel": 228.67,
"reached": true,
"err": 0,
"split": "val"
},
"segment_start_it": 54,
"start_s": 1.08,
"end_s": 1.1,
"original_discrete_col": 0,
"certified": true,
"recorded_endpoint_midpoint_gap32_mm": [
0.0651998519897461,
0.11720466613769531
],
"previous_midpoint_endpoint_gap64_mm": [
0.5,
0.11723191087430074,
0.06521150921177998
],
"previous": [
3.0118656158447266,
-7.4926042556762695,
-151.498291015625,
-22.7498836517334,
-94.0,
93.50477600097656
],
"endpoint": [
1.8212896585464478,
-9.327469825744629,
-150.87704467773438,
-23.00735855102539,
-94.0,
93.73664855957031
],
"dense_max_step_deg": 0.01,
"dense_points": 185,
"min_gap64_mm": -0.14927288174659914,
"min_at_fraction": 0.7663043478260869,
"min_at_q": [
2.09952208334985,
-8.898669719696045,
-151.0222272458284,
-22.947187786516935,
-94.0,
93.68246095076852
],
"scope": "One executed segment; finite sampling demonstrates a collision between certified checkpoints."
}
+31
View File
@@ -551,6 +551,37 @@
- 双精度运动学/间隙另与已有 JS 2000 个样本对拍:`python -m ml.eval.check_parity` PASS,碰撞布尔差 0、间隙最大差 2.53e−13 mm。首撞回放仍共用碰撞模型,不替代真机或独立连续认证。
- **结论与下一步**:减少连续刹停确有小样本收益,但碰撞、行程和真实时延仍未过门槛;保留为实验选项,不更改查看器默认。下一轮记录原认证点与 J5 夹紧后实际点,修复后用同批同 seed A/B 和独立细采样复核,禁止靠关掉认证提速。
## 轮次 30:认证与执行统一逐步限位(exp: `exp/dagger-deploy`,局部修复有效,仍未零碰撞)
- **假设与判据**:安全层认证的是未夹紧累加轨迹,执行器却逐步夹紧 J5,导致认证通过后碰撞。必须同时证明原证书安全、执行点碰撞、记录命令经执行算式能准确重建实际点;修复后保持同任务、同种子、同模型、同增益和同排序,到达不下降,并用不依赖原 `col` 标记的加密回放检验。
- **固定配方**:`python -u -m ml.eval.eval_cl --n 256 --commit 2 --fb 2.2 --partial 1 --replan empty --select endpoint --fb_block_gain 0.5 --dls_safety 1`;K=8、seed=0、analytic DLS、dag2 checkpoint、fallback=0,其余参数默认;分别附 `--out docs/experiment_round30_before.json --trace ml/runs/round30_before.pt --dump ml/runs/round30_before_rows.json` 和对应的 `after` 路径。未训练、未扩题、未改 JS 默认。
- **可比性**:取证版 before 与轮次29首批256题完整轨迹逐元素完全相同(最大差 0°),ok/col/brakes/err/t_done/first_hit 也完全相同;before/after 配置仅输出路径不同。任务 SHA256=`249bd93457825cda98c59e64d9a096d3473c69aeb3553de1dea6d9e56be71772`,checkpoint SHA256=`f7f686fd8ee92fb6948fbcc4ee954636065ad87bb54f661c4f8f575bc16c2ce1`,kin_fp=`2ebcbf17`。
- **直接证据**(`docs/experiment_round30_certificates.json`):旧版7次首撞的原认证端点/中点在 float32 与 float64 均为正间隙,实际执行段均有负间隙;`clamp(previous + recorded_command)` 与实际端点逐位一致。原证书与实际点主要差在 J5,首撞段差 0.340°–4.317°;其他轴仅有最高约 7.6e-6° 的累加舍入差,不足以解释毫米级穿入。全部有效认证步中,5298/34069 步偏差 >0.001°,最大 7.587°。
- **修复范围**:`execution_points` 按执行器相同顺序逐步夹紧,端点和中点都由实际路径计算;整段、部分前缀和近目标 DLS 共用。r3 的旧队列终点和特征前缀改为真实执行结果。fallback 保持实验默认关闭,但两分支统一为“认证一步后补零”,不再把只验过一步的动作重复执行;端点和中点都认证。候选排序和反馈增益未改,0.5 仍只是间隙截顶,正间隙判据没有放宽。
| 指标(同256题) | 修复前 | 修复后 |
|---|---|---|
| 按时到达 | 147/256 = 57.4% | 同147题,57.4% |
| 原端点/中点口径无碰撞到达 | 145/256 = 56.6% | 147/256 = 57.4%(下述加密审计否定其“全程无碰撞”含义) |
| time_ok_1p3 | 39.1% | 39.1% |
| 原端点/中点口径碰撞题 | 7/256 | 0/256 |
| 成功题末端误差中位数 | 0.1951 mm | 0.1890 mm |
| 成功题平均时间 | 2.2993 s | 2.3076 s |
| 2 s 误差比中位数 | 0.2100 | 0.2100 |
| 刹停/题 | 19.2383 | 19.2500 |
| 成功题指令行程比中位数 | 2.4450 | 2.4450 |
| 从实际轨迹重算成功题行程比 | 2.4251 | 2.4251 |
| 整批同步仿真墙钟 | 247.515 s | 248.834 s |
- **执行一致性复核**(`docs/experiment_round30_comparison.json`):修复后34107个有效认证步,认证端点/中点与实际执行点均逐位一致,最大差 0°;有效时段没有未经认证的移动步。这里“认证”仍仅指离散端点/中点,不能推出连续安全。CPU B=1、commit=2 的预测函数单独测量:100次预热、1000次采样、单线程,P50 0.0388→0.0518 ms,P99 0.0691→0.0939 ms;不是完整规划延迟,不能证明50 Hz。
- **独立加密审计的正反对照**:预先固定12题 `0,21,84,128,145,164,175,180,197,215,224,255`,其中包含全部7题旧首撞及5题其他任务;不按新版 `col` 过滤。逐题回放至自身时限或到达,含起点,每个插值段最大轴步距≤0.05°并包含中点,float64、每批64点。命令:`python -u -m ml.eval.diag_collision --trace ml/runs/round30_before.pt --dense_indices 0,21,84,128,145,164,175,180,197,215,224,255 --max_step_deg 0.05 --device cuda --out docs/experiment_round30_dense_before.json`,after 同理;串行运行。
- before:56118点,**7/12题检出碰撞**,14.870 s;证明探针确实能抓旧碰撞。
- after:56566点,**仍有1/12题碰撞**,15.057 s;其余11题采样点间隙为正。这是小样本有限采样,不是完整256题的连续认证。
- 剩余第180题,执行段1.08–1.10 s:上一点/中点/端点 float64 间隙为 **0.5 / 0.11723 / 0.06521 mm**,都安全,但段内约76.6%处为 **−0.14927 mm**;该段0.01°再加密185点仍检出,见 `docs/experiment_round30_missed_segment.json`。因此这次残余是端点/中点漏检,而非认证与执行错位;它比旧离散首次碰撞1.14 s还早。不得把内部计数0当成修好了;已知真实无碰撞到达至多146/256=57.0%,其余未加密任务仍未知。
- **回归与判据灵敏度**:`python -m unittest tests.test_eval_cl -v` 14项通过。覆盖整段/部分限位拒绝、只保留真实安全前缀、DLS被拒后保留候选、r3旧队列起点和特征前缀及逐点证书、fallback单步/中点拒绝。把预测函数在内存中替回旧算式,限位、DLS和r3测试重新失败。加密探针另有合成正对照:原端点/中点全安全且 `col=0`,仍检出四分之一处薄障碍;安全负对照、截止后/到达后碰撞排除均通过。
- **新增硬约束证据**: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合格**。下一轮先验证更强的段内认证,再单独评估反馈后的速度/加速度约束与真实端到端调度;每次保持小样本单变量对照,继续保留失败证据。
## 分支清理(2026-10-20)
- `feat/fb-mix`(8e5431c):反馈混合方案的中间分支,**已验证是 `exp/dagger-deploy` 的祖先**(所有提交都在后者历史里),本地/远端分支删除,内容保留在 exp/dagger-deploy。
+88 -1
View File
@@ -7,7 +7,7 @@
口径与 closed_loop.rollout 一致:每步同时检查当前状态 q 与相邻状态中点 mid 的间隙(cap=0.5 mm);
间隙组对索引 = gA*7+gB(gA<gB,g 为连杆组 0..5 → J1..J6)。
"""
import argparse, json, math, os, sys
import argparse, json, math, os, sys, time
from collections import Counter
import torch
@@ -54,6 +54,25 @@ def replay_first_hits(path, out=None):
'previous': prev.tolist(), 'endpoint': q.tolist(),
'dense_nsegments': nseg, 'dense_gap64_min_mm': float(dense_gap.min()),
}
if 'plans' in per:
plans = per['plans']
pi = int(torch.searchsorted(plans['it'], it - 1, right=True)) - 1
step = it - 1 - int(plans['it'][pi])
ncommit = plans['commit'].shape[2]
command = plans['commit'][b, pi, step]
cp, cm = plans['points'][b, pi, step], plans['midpoints'][b, pi, step]
checked = bool(plans['certified'][b, pi, step])
predicted = k32.clamp_box((prev + command)[None])[0]
row['certificate'] = {
'plan_start_it': int(plans['it'][pi]), 'step': step, 'checked': checked,
'endpoint': cp.tolist() if checked else None,
'midpoint': cm.tolist() if checked else None,
'recorded_gap32_mm': plans['gaps'][b, pi, [step, ncommit + step]].tolist() if checked else None,
'replayed_gap64_mm': k64.gaps(torch.stack([cp, cm]).double(), 0.5)['gm'].min(-1).values.tolist() if checked else None,
'command_delta': command.tolist(),
'clamped_reconstruction_max_abs_deg': float((predicted - q).abs().max()),
'certified_vs_executed_max_abs_deg': float((cp - q).abs().max()) if checked else None,
}
rows.append(row)
print('task %d first_hit %.2fs gap32 %s gap64 %s boundary %s' %
(row['i'], row['first_hit_s'], row['gap32_mm'], row['gap64_mm'], row['boundary_joints']), flush=True)
@@ -73,10 +92,73 @@ def replay_first_hits(path, out=None):
return result
@torch.no_grad()
def replay_dense(path, indices, max_step_deg=0.05, device='cpu', out=None):
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 max_step_deg <= 0:
raise ValueError('valid task indices and positive max_step_deg required')
started = time.perf_counter()
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))
trajectory = per['traj'][b, :nstep + 1].double()
points, segment_ids, fractions = [trajectory[:1]], [torch.tensor([-1])], [torch.zeros(1)]
for it in range(nstep):
prev, end = trajectory[it:it + 2]
nseg = max(2, 2 * math.ceil(float((end - prev).abs().max()) / (2 * max_step_deg)))
alpha = torch.arange(1, nseg + 1, dtype=torch.float64) / nseg
points.append(prev + alpha[:, None] * (end - prev))
segment_ids.append(torch.full((nseg,), it))
fractions.append(alpha)
points = torch.cat(points)
segments, alpha = torch.cat(segment_ids), torch.cat(fractions)
print('dense task %d steps %d points %d start' % (index, nstep, len(points)), flush=True)
gaps = []
for start in range(0, len(points), 64):
gaps.append(kin.gaps(points[start:start + 64].to(device), 0.5)['gm'].min(-1).values.cpu())
if (start // 64 + 1) % 64 == 0:
print('dense task %d points %d/%d wall %.1fs' %
(index, min(start + 64, len(points)), len(points), time.perf_counter() - started), flush=True)
gaps = torch.cat(gaps)
hit = gaps <= 0
first = int(hit.nonzero()[0]) if hit.any() else None
rows.append({
'i': index, 'task': saved['tasks'][index], 'steps': nstep, 'sampled_points': len(points),
'initial_gap64_mm': float(gaps[0]), 'min_gap64_mm': float(gaps.min()),
'collision_segments': int(segments[hit & (segments >= 0)].unique().numel()),
'first_collision_s': max(0.0, float(segments[first] + alpha[first]) * DT) if first is not None else None,
})
print('dense task %d min_gap %.9f mm collision_segments %d' %
(index, rows[-1]['min_gap64_mm'], rows[-1]['collision_segments']), flush=True)
offset += count
result = {
'trace': str(path), 'tasks_sha256': saved.get('tasks_sha256'), 'kin_fp': kin.fp,
'indices': indices, 'max_step_deg': max_step_deg, 'dtype': 'float64', 'device': device,
'scope': 'Executed trajectory through each task deadline or arrival, including initial state; finite sampling, not continuous certification.',
'collision_tasks': sum(r['min_gap64_mm'] <= 0 for r in rows), 'rows': rows,
'audit_wall_s': time.perf_counter() - started,
}
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('--max_step_deg', type=float, default=0.05)
ap.add_argument('--samples', default='ml/data/samples')
ap.add_argument('--split', default='val')
ap.add_argument('--n', type=int, default=512)
@@ -88,8 +170,13 @@ def main():
ap.add_argument('--out', default=None)
a = ap.parse_args()
if a.trace:
if 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 not a.ckpt:
ap.error('--ckpt or --trace is required')
+1 -1
View File
@@ -20,7 +20,7 @@ def run(model, kin, tasks, device, bs=256, **kw):
r, p = rollout(model, kin, tasks[i:i + bs], device, progress=progress, **kw)
per.append((r, p, len(tasks[i:i + bs])))
print('batch %d done wall %.1fs' % (i // bs + 1, time.perf_counter() - t0), flush=True)
flat = {k: torch.cat([p[k] for _, p, _ in per]) for k in per[0][1] if k != 'traj'}
flat = {k: torch.cat([p[k] for _, p, _ in per]) for k in per[0][1] if k not in ('traj', 'plans')}
_, _, _, ex, tr = tasks_to_tensors(tasks, 'cpu')
ok, col = flat['ok'], flat['col'] > 0
has_ref = torch.isfinite(ex)
+69 -25
View File
@@ -48,6 +48,16 @@ def goal_err(kin, q, T, R):
return e, oe
def execution_points(kin, q, increments):
points, midpoints = [], []
for delta in increments.unbind(1):
nxt = kin.clamp_box(q + delta)
midpoints.append(q + 0.5 * (nxt - q))
points.append(nxt)
q = nxt
return torch.stack(points, 1), torch.stack(midpoints, 1)
def dls_steps(kin, q, T, R, n=3, scale=0.3, lam=0.5, analytic=True):
"""局部 DLS 精修:n 步增量 [B,n,6](analytic:几何雅可比,1 次 FK;否则 7 次批量有限差分)"""
step = torch.tensor(STEP, device=q.device)
@@ -181,6 +191,7 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None,
col = torch.zeros(B, device=device); travel = torch.zeros(B, device=device); brakes = torch.zeros(B, device=device)
first_hit = torch.full((B,), float('nan'), device=device); dls_it = torch.full((B,), -1, dtype=torch.long, device=device)
traj = [q.clone()] if return_traj else None
plans = {k: [] for k in ('it', 'qe', 'commit', 'points', 'midpoints', 'gaps', 'certified')} if return_traj else None
n_start = int(round(0.5 / DT)); moved_start = None # R2 起步失败:0.5 s 内关节总行程 < 1°
k2s = int(round(2.0 / DT)); e2 = None
e0, _ = goal_err(kin, q0, T, R)
@@ -192,8 +203,12 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None,
tnow = it * DT
if (queue.shape[1] <= 3) if replan == 'r3' else (queue.shape[1] == 0):
r = queue.shape[1]
pre = torch.zeros(B, 3, 6, device=device); pre[:, :r] = queue[:, :r]
qe = q + queue.sum(1)
pre = torch.zeros(B, 3, 6, device=device)
qe = q
if r:
prefix_points, _ = execution_points(kin, q, queue)
pre[:, :r] = prefix_points - torch.cat([q[:, None], prefix_points[:, :-1]], 1)
qe = prefix_points[:, -1]
e, oe = goal_err(kin, qe, T, R)
near = (e < 5) & ((R.abs().sum(-1) == 0) | (oe < 5))
subs, nsub, sub_prev, sub_t = sub_advance(kin, subs, nsub, sub_prev, sub_t, qe, tnow, sub_rule)
@@ -297,31 +312,34 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None,
collect.append((feat.detach().float().cpu(), inc[ar, pick].detach().float().cpu()))
committed = torch.ones(B, dtype=torch.bool, device=device)
braked = torch.zeros(B, dtype=torch.bool, device=device)
if return_traj:
cert_pts = torch.full((B, commit_n, 6), float('nan'), device=device)
cert_mids = cert_pts.clone()
cert_gaps = torch.full((B, 2 * commit_n), float('nan'), device=device)
certified = torch.zeros(B, commit_n, dtype=torch.bool, device=device)
if safety and safety_partial: # 2026-10-20:允许「部分安全前缀」——执行到第一个不安全步为止,而不是全拒刹停
maxlen = torch.zeros(B, dtype=torch.long, device=device)
chosen = torch.zeros(B, commit_n, 6, device=device); chosen_k = pick.clone()
ntry = min(K_eff, safety_try) if safety_try > 0 else K_eff
for kk in range(ntry):
ck = inc[ar, order[:, kk], r:r + commit_n]
pts = qe[:, None] + torch.cumsum(ck, 1)
mids = torch.cat([qe[:, None], pts[:, :-1]], 1) + 0.5 * ck
pts, mids = execution_points(kin, qe, ck)
allq = torch.cat([pts, mids], 1).reshape(-1, 6)
ok6 = (kin.gaps(allq, 0.5)['gm'].min(-1).values > 0).view(B, 2 * commit_n)
okstep = ok6[:, :commit_n] & ok6[:, commit_n:] # 每步:步点与其中点都安全
plen = torch.cumprod(okstep.long(), 1).sum(1) # 安全前缀长度 0..3
gaps6 = kin.gaps(allq, 0.5)['gm'].min(-1).values.view(B, 2 * commit_n)
ok6 = gaps6 > 0
okstep = ok6[:, :commit_n] & ok6[:, commit_n:]
plen = torch.cumprod(okstep.long(), 1).sum(1)
take = plen > maxlen
chosen = torch.where(take[:, None, None], ck, chosen); chosen_k = torch.where(take, order[:, kk], chosen_k)
if return_traj:
cert_pts = torch.where(take[:, None, None], pts, cert_pts)
cert_mids = torch.where(take[:, None, None], mids, cert_mids)
cert_gaps = torch.where(take[:, None], gaps6, cert_gaps)
maxlen = torch.maximum(maxlen, plen)
if B == 1 and int(maxlen[0]) >= commit_n: break # 实时单题:首个全安全候选即停(安全层 16 点→2-4 点)
msk = (torch.arange(commit_n, device=device)[None] < maxlen[:, None])
commit = torch.where(msk[..., None], chosen, torch.zeros_like(chosen))
if fb_gain > 0 and fb_fallback > 0: # 全拒时兜底:半步朝目标(须通过间隙认证),避免原地停满整个周期
fb1 = dls_steps(kin, qe, T, R, n=1) * fb_fallback
ok1 = kin.gaps(qe + fb1[:, 0], 0.5)['gm'].min(-1).values > 0
fbcom = torch.zeros(B, commit_n, 6, device=device); fbcom[:, 0] = fb1[:, 0] # 只走一步(重复会被第二步未经认证的位移带进碰撞)
mskf = (maxlen == 0) & ok1
commit = torch.where(mskf[:, None, None], fbcom, commit)
msk = msk | mskf[:, None]
if return_traj: certified = msk.clone()
brakes += (maxlen == 0).float(); braked = maxlen == 0
pick = chosen_k; committed = maxlen > 0
elif safety:
@@ -330,28 +348,48 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None,
ntry = min(K_eff, safety_try) if safety_try > 0 else K_eff
for kk in range(ntry):
ck = inc[ar, order[:, kk], r:r + commit_n]
pts = qe[:, None] + torch.cumsum(ck, 1)
mids = torch.cat([qe[:, None], pts[:, :-1]], 1) + 0.5 * ck
pts, mids = execution_points(kin, qe, ck)
allq = torch.cat([pts, mids], 1).reshape(-1, 6)
okk = (kin.gaps(allq, 0.5)['gm'].min(-1).values > 0).view(B, 2 * commit_n).all(-1)
gaps6 = kin.gaps(allq, 0.5)['gm'].min(-1).values.view(B, 2 * commit_n)
okk = (gaps6 > 0).all(-1)
take = okk & ~ok_any
chosen = torch.where(take[:, None, None], ck, chosen); chosen_k = torch.where(take, order[:, kk], chosen_k); ok_any |= okk
if return_traj:
cert_pts = torch.where(take[:, None, None], pts, cert_pts)
cert_mids = torch.where(take[:, None, None], mids, cert_mids)
cert_gaps = torch.where(take[:, None], gaps6, cert_gaps)
commit = torch.where(ok_any[:, None, None], chosen, torch.zeros_like(chosen))
if fb_gain > 0 and fb_fallback > 0: # 全拒时的兜底:半速朝目标走一步(须通过安全认证),避免原地停滞
fb1 = dls_steps(kin, qe, T, R, n=1) * fb_fallback
ok1 = kin.gaps(qe + fb1[:, 0], 0.5)['gm'].min(-1).values > 0
commit = torch.where((~ok_any & ok1)[:, None, None], fb1.expand(-1, commit_n, -1), commit)
if return_traj: certified = ok_any[:, None].expand(-1, commit_n).clone()
brakes += (~ok_any).float(); braked = ~ok_any
pick = chosen_k; committed = ok_any
if safety and fb_gain > 0 and fb_fallback > 0:
fbcom = torch.zeros(B, commit_n, 6, device=device)
fbcom[:, :1] = dls_steps(kin, qe, T, R, n=1) * fb_fallback
pts, mids = execution_points(kin, qe, fbcom)
allq = torch.cat([pts, mids], 1).reshape(-1, 6)
gaps6 = kin.gaps(allq, 0.5)['gm'].min(-1).values.view(B, 2 * commit_n)
takef = braked & (gaps6 > 0).all(-1)
commit = torch.where(takef[:, None, None], fbcom, commit)
if return_traj:
cert_pts = torch.where(takef[:, None, None], pts, cert_pts)
cert_mids = torch.where(takef[:, None, None], mids, cert_mids)
cert_gaps = torch.where(takef[:, None], gaps6, cert_gaps)
certified = certified | takef[:, None]
if near.any():
ref = dls_steps(kin, qe, T, R, n=commit_n)
take = near
if dls_safety and safety: # 2026-10-20:近目标 DLS 接管也要过安全层(部署端 _certCommit 本来就会验;不过则退回候选计划)
pts = qe[:, None] + torch.cumsum(ref, 1)
mids = torch.cat([qe[:, None], pts[:, :-1]], 1) + 0.5 * ref
pts, mids = execution_points(kin, qe, ref)
allq = torch.cat([pts, mids], 1).reshape(-1, 6)
okd = (kin.gaps(allq, 0.5)['gm'].min(-1).values > 0).view(B, 2 * commit_n).all(-1)
gaps6 = kin.gaps(allq, 0.5)['gm'].min(-1).values.view(B, 2 * commit_n)
okd = (gaps6 > 0).all(-1)
take = near & okd
if return_traj:
cert_pts = torch.where(take[:, None, None], pts, cert_pts)
cert_mids = torch.where(take[:, None, None], mids, cert_mids)
cert_gaps = torch.where(take[:, None], gaps6, cert_gaps)
if return_traj:
certified = torch.where(take[:, None], torch.full_like(certified, bool(dls_safety and safety)), certified)
commit = torch.where(take[:, None, None], ref, commit)
dls_it = torch.where(take & (dls_it < 0), torch.full_like(dls_it, it), dls_it)
committed = committed & ~take
@@ -359,6 +397,10 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None,
if fb_ki > 0: # 刹停/精修接管时清积分(防止原地累积到限幅后突然冲出去)
fb_i = torch.where(committed[:, None], fb_i, torch.zeros_like(fb_i))
last = torch.cat([qe[:, None], fut[ar, pick]], 1); last_ok = committed; last_brake = braked
if return_traj:
plans['it'].append(it + r)
for key, value in (('qe', qe), ('commit', commit), ('points', cert_pts), ('midpoints', cert_mids), ('gaps', cert_gaps), ('certified', certified)):
plans[key].append(value)
queue = torch.cat([queue, commit], 1)
cold = False
d = queue[:, 0]; queue = queue[:, 1:]
@@ -403,7 +445,9 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None,
per = {'done': done.cpu(), 'ok': ok.cpu(), 't_done': t_done.cpu(), 'limit': limit.cpu(), 'col': col.cpu(), 'travel': travel.cpu(), 'err': e_end.cpu(),
'side': side.cpu(), 'start_fail': start_fail.cpu(), 'brakes': brakes.cpu(), 'first_hit': first_hit.cpu(), 'dls_it': dls_it.cpu(),
'goal_ratio_2s': ratio2.cpu()}
if return_traj: per['traj'] = torch.stack(traj, 1).cpu()
if return_traj:
per['traj'] = torch.stack(traj, 1).cpu()
per['plans'] = {k: torch.tensor(v) if k == 'it' else torch.stack(v, 1).cpu() for k, v in plans.items()}
return res, per
+216 -1
View File
@@ -9,7 +9,7 @@ from unittest.mock import patch
import torch
from ml.eval.eval_cl import json_finite, run
from ml.eval.diag_collision import replay_first_hits
from ml.eval.diag_collision import replay_dense, replay_first_hits
from ml.sim.closed_loop import TeacherOracle, rollout
@@ -91,6 +91,39 @@ class EvalMetricsTest(unittest.TestCase):
self.assertEqual(result['rows'][0]['gap64_mm'], [0.5, 0.5, 0.0, -1.0])
self.assertEqual(result['rows'][0]['dense_gap64_min_mm'], -1.0)
def test_dense_probe_detects_between_checkpoints_and_respects_deadline(self):
class ThinObstacle:
fp = 'probe'
def __init__(self, **kwargs):
pass
def gaps(self, q, cap):
return {'gm': ((q[:, :1] - 0.25).abs() - 0.05).clamp(max=cap)}
traj = torch.zeros(4, 3, 6)
traj[:, :, 0] = torch.tensor([[0, 1, 1], [0, -0.5, -1], [0, 0, 0.25], [0, 0, 0.25]])
per = {'traj': traj, 'col': torch.zeros(4), 'limit': torch.tensor([0.04, 0.04, 0.02, 0.04]),
't_done': torch.tensor([float('nan')] * 3 + [0.02])}
with tempfile.TemporaryDirectory() as folder:
trace = Path(folder) / 'trace.pt'
output = Path(folder) / 'audit.json'
torch.save({'kin_fp': 'probe', 'tasks': [{}] * 4, 'batches': [({}, per, 4)]}, trace)
with patch('ml.eval.diag_collision.Kin', ThinObstacle), contextlib.redirect_stdout(io.StringIO()):
result = replay_dense(trace, [0, 1, 2, 3], 0.01, out=output)
with self.assertRaisesRegex(ValueError, 'valid task indices'):
replay_dense(trace, [4])
with self.assertRaisesRegex(ValueError, 'positive max_step_deg'):
replay_dense(trace, [0], 0.0)
self.assertEqual(json.loads(output.read_text())['collision_tasks'], 1)
self.assertEqual(result['collision_tasks'], 1)
self.assertEqual(result['rows'][0]['collision_segments'], 1)
self.assertAlmostEqual(result['rows'][0]['min_gap64_mm'], -0.05)
self.assertGreater(result['rows'][1]['min_gap64_mm'], 0)
for row in result['rows'][2:]:
self.assertGreater(row['min_gap64_mm'], 0)
self.assertEqual(row['steps'], 1)
class DlsSafetyTest(unittest.TestCase):
def test_rejected_refinement_retains_safe_candidate(self):
@@ -158,5 +191,187 @@ class DlsSafetyTest(unittest.TestCase):
self.assertEqual(results[1]['col'].item(), 0.0)
class ClampedPathSafetyTest(unittest.TestCase):
def rollout_candidate(self, increments, safety_partial, goal_error=10.0, **kwargs):
class ObstacleKin:
def clamp_box(self, q):
clamped = q.clone()
clamped[..., 4] = clamped[..., 4].clamp(max=0.0)
return clamped
def gaps(self, q, cap):
collision = (q[:, :1] > 0.1) & (q[:, 4:5] < 0.2)
return {'gm': torch.where(collision, -torch.ones_like(q[:, :1]), torch.ones_like(q[:, :1]))}
task = {'q0': [0] * 6, 'T': [goal_error, 0, 0]}
commit_n = len(increments)
traj = torch.zeros(1, commit_n + 1, 6)
traj[0, 1:] = torch.tensor(increments).cumsum(0)
patches = {
'features': lambda *args: torch.zeros(1, 83),
'goal_err': lambda kin, q, T, R: (torch.full((q.shape[0],), goal_error), torch.zeros(q.shape[0])),
}
with patch.multiple('ml.sim.closed_loop', **patches):
_, per = rollout(None, ObstacleKin(), [task], 'cpu', K=1, safety=True,
max_sec=0.02 * commit_n, return_traj=True, use_sub=False, select='first',
oracle=TeacherOracle(traj), replan='empty', commit_n=commit_n,
safety_partial=safety_partial, **kwargs)
return per
def test_full_safety_rejects_collision_after_joint_clamp(self):
# Raw endpoint/midpoint are safe, but the J5 upper limit puts the endpoint at (0.2, 0).
per = self.rollout_candidate([[0.2, 0, 0, 0, 0.6, 0]], safety_partial=0)
self.assertEqual(per['col'].item(), 0.0)
torch.testing.assert_close(per['traj'], torch.zeros(1, 2, 6), rtol=0, atol=0)
def test_partial_safety_rejects_collision_after_joint_clamp(self):
per = self.rollout_candidate([[0.2, 0, 0, 0, 0.6, 0]], safety_partial=1)
self.assertEqual(per['col'].item(), 0.0)
torch.testing.assert_close(per['traj'], torch.zeros(1, 2, 6), rtol=0, atol=0)
def test_partial_safety_keeps_only_clamped_safe_prefix(self):
per = self.rollout_candidate([
[0.05, 0, 0, 0, 0.6, 0],
[0.2, 0, 0, 0, 0.6, 0],
], safety_partial=1)
self.assertAlmostEqual(per['traj'][0, -1, 0].item(), 0.05)
self.assertEqual(per['traj'][0, -1, 4].item(), 0.0)
self.assertEqual(per['col'].item(), 0.0)
expected = torch.zeros(1, 3, 6)
expected[0, 1:, 0] = 0.05
torch.testing.assert_close(per['traj'], expected)
torch.testing.assert_close(per['plans']['commit'][0, 0, 1], torch.zeros(6), rtol=0, atol=0)
def test_near_dls_rejects_clamped_collision_and_keeps_candidate(self):
candidate = [[0.05, 0, 0, 0, 0.6, 0]]
ref = torch.tensor([[[0.2, 0, 0, 0, 0.6, 0]]])
for safety_partial in (0, 1):
with self.subTest(safety_partial=safety_partial):
with patch('ml.sim.closed_loop.dls_steps', return_value=ref) as dls:
per = self.rollout_candidate(candidate, safety_partial, goal_error=2.0, dls_safety=1)
dls.assert_called_once()
self.assertEqual(per['col'].item(), 0.0)
self.assertEqual(per['dls_it'].item(), -1)
self.assertEqual(per['brakes'].item(), 0.0)
expected = torch.zeros(1, 2, 6)
expected[0, 1, 0] = 0.05
torch.testing.assert_close(per['traj'], expected)
torch.testing.assert_close(per['plans']['commit'], torch.tensor(candidate)[None, None])
self.assertTrue(per['plans']['certified'].all().item())
def test_r3_clamped_queue_origin_prefix_and_certificates_match_execution(self):
class SafeKin:
def clamp_box(self, q):
clamped = q.clone()
clamped[..., 4] = clamped[..., 4].clamp(max=0.0)
return clamped
def gaps(self, q, cap):
return {'gm': torch.ones_like(q[:, :1])}
prefixes = []
def capture_features(kin, q, qp, T, R, pre, r, sub, sf):
prefixes.append((pre.clone(), int(r.item())))
return torch.zeros(1, 83)
teacher = torch.zeros(1, 25, 6)
teacher[0, :, 0] = 0.02 * torch.arange(25)
teacher[0, :, 4] = 0.6 * torch.arange(25)
task = {'q0': [0] * 6, 'T': [10, 0, 0]}
patches = {
'features': capture_features,
'goal_err': lambda kin, q, T, R: (torch.full((q.shape[0],), 10.0), torch.zeros(q.shape[0])),
}
with patch.multiple('ml.sim.closed_loop', **patches):
_, per = rollout(None, SafeKin(), [task], 'cpu', K=1, safety=True,
max_sec=0.16, return_traj=True, use_sub=False, select='first',
oracle=TeacherOracle(teacher), replan='r3', commit_n=3, safety_partial=1)
self.assertEqual([r for _, r in prefixes], [0, 2, 3, 3])
for pre, r in prefixes[1:]:
torch.testing.assert_close(pre[..., 4], torch.zeros(1, 3), rtol=0, atol=0)
torch.testing.assert_close(pre[:, :r, 0], torch.full((1, r), 0.02))
self.assertEqual(per['col'].item(), 0.0)
expected = torch.zeros(1, 9, 6)
expected[0, :, 0] = 0.02 * torch.arange(9)
torch.testing.assert_close(per['traj'], expected)
plans = per['plans']
self.assertEqual(plans['it'].tolist(), [0, 3, 6, 9])
torch.testing.assert_close(plans['commit'][..., 4], torch.full((1, 4, 3), 0.6))
self.assertTrue(plans['certified'].all().item())
self.assertTrue((plans['gaps'] > 0).all().item())
checked = 0
for i, start in enumerate(plans['it'].tolist()):
n = min(3, 8 - start)
if n <= 0:
continue
torch.testing.assert_close(plans['qe'][:, i], per['traj'][:, start], rtol=0, atol=0)
before = per['traj'][:, start:start + n]
after = per['traj'][:, start + 1:start + n + 1]
torch.testing.assert_close(plans['points'][:, i, :n], after, rtol=0, atol=0)
torch.testing.assert_close(plans['midpoints'][:, i, :n], before + 0.5 * (after - before),
rtol=0, atol=0)
checked += n
self.assertEqual(checked, 8)
def rollout_fallback(self, safety_partial, midpoint_collision=False):
class ObstacleKin:
def clamp_box(self, q):
return q
def gaps(self, q, cap):
collision = q[:, :1] > 0.3
if midpoint_collision:
collision |= (q[:, :1] > 0.09) & (q[:, :1] < 0.11)
return {'gm': torch.where(collision, -torch.ones_like(q[:, :1]), torch.ones_like(q[:, :1]))}
teacher = torch.zeros(1, 4, 6)
teacher[0, :, 0] = torch.arange(4)
ref = torch.zeros(1, 1, 6)
ref[0, 0, 0] = 0.2
task = {'q0': [0] * 6, 'T': [10, 0, 0]}
patches = {
'features': lambda *args: torch.zeros(1, 83),
'goal_err': lambda kin, q, T, R: (torch.full((q.shape[0],), 10.0), torch.zeros(q.shape[0])),
'dls_steps': lambda *args, **kwargs: ref,
}
with patch.multiple('ml.sim.closed_loop', **patches):
_, per = rollout(None, ObstacleKin(), [task], 'cpu', K=1, safety=True,
max_sec=0.06, return_traj=True, use_sub=False, select='first',
oracle=TeacherOracle(teacher), replan='empty', commit_n=3,
safety_partial=safety_partial, fb_gain=1.0, fb_fallback=1.0)
return per
def test_fallback_executes_one_safe_dls_step_then_certified_zeros(self):
for safety_partial in (0, 1):
with self.subTest(safety_partial=safety_partial):
per = self.rollout_fallback(safety_partial)
self.assertEqual(per['col'].item(), 0.0)
self.assertEqual(per['brakes'].item(), 1.0)
expected = torch.zeros(1, 4, 6)
expected[0, 1:, 0] = 0.2
torch.testing.assert_close(per['traj'], expected)
commit = torch.zeros(1, 1, 3, 6)
commit[0, 0, 0, 0] = 0.2
plans = per['plans']
self.assertEqual(plans['it'].tolist(), [0])
torch.testing.assert_close(plans['commit'], commit, rtol=0, atol=0)
self.assertTrue(plans['certified'].all().item())
self.assertTrue((plans['gaps'] > 0).all().item())
torch.testing.assert_close(plans['points'][:, 0], expected[:, 1:])
torch.testing.assert_close(plans['midpoints'][:, 0],
expected[:, :-1] + 0.5 * (expected[:, 1:] - expected[:, :-1]))
def test_fallback_rejects_safe_endpoint_with_colliding_midpoint(self):
for safety_partial in (0, 1):
with self.subTest(safety_partial=safety_partial):
per = self.rollout_fallback(safety_partial, midpoint_collision=True)
self.assertEqual(per['col'].item(), 0.0)
self.assertEqual(per['brakes'].item(), 1.0)
torch.testing.assert_close(per['traj'], torch.zeros(1, 4, 6), rtol=0, atol=0)
torch.testing.assert_close(per['plans']['commit'], torch.zeros(1, 1, 3, 6), rtol=0, atol=0)
self.assertFalse(per['plans']['certified'].any().item())
if __name__ == '__main__':
unittest.main()