diff --git a/docs/experiment_round30_after.json b/docs/experiment_round30_after.json new file mode 100644 index 0000000..5dc5f69 --- /dev/null +++ b/docs/experiment_round30_after.json @@ -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 +} diff --git a/docs/experiment_round30_before.json b/docs/experiment_round30_before.json new file mode 100644 index 0000000..a517bf6 --- /dev/null +++ b/docs/experiment_round30_before.json @@ -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 +} diff --git a/docs/experiment_round30_certificates.json b/docs/experiment_round30_certificates.json new file mode 100644 index 0000000..49ef815 --- /dev/null +++ b/docs/experiment_round30_certificates.json @@ -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 + } + } + ] +} \ No newline at end of file diff --git a/docs/experiment_round30_comparison.json b/docs/experiment_round30_comparison.json new file mode 100644 index 0000000..8f4715d --- /dev/null +++ b/docs/experiment_round30_comparison.json @@ -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 + } +} diff --git a/docs/experiment_round30_dense_after.json b/docs/experiment_round30_dense_after.json new file mode 100644 index 0000000..e16f075 --- /dev/null +++ b/docs/experiment_round30_dense_after.json @@ -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 +} \ No newline at end of file diff --git a/docs/experiment_round30_dense_before.json b/docs/experiment_round30_dense_before.json new file mode 100644 index 0000000..0eb4e46 --- /dev/null +++ b/docs/experiment_round30_dense_before.json @@ -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 +} \ No newline at end of file diff --git a/docs/experiment_round30_missed_segment.json b/docs/experiment_round30_missed_segment.json new file mode 100644 index 0000000..ebd5858 --- /dev/null +++ b/docs/experiment_round30_missed_segment.json @@ -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." +} diff --git a/docs/探索日志.md b/docs/探索日志.md index d65a574..87e055b 100644 --- a/docs/探索日志.md +++ b/docs/探索日志.md @@ -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。 diff --git a/ml/eval/diag_collision.py b/ml/eval/diag_collision.py index cede8ba..9b0ec3e 100644 --- a/ml/eval/diag_collision.py +++ b/ml/eval/diag_collision.py @@ -7,7 +7,7 @@ 口径与 closed_loop.rollout 一致:每步同时检查当前状态 q 与相邻状态中点 mid 的间隙(cap=0.5 mm); 间隙组对索引 = gA*7+gB(gA= 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: - replay_first_hits(a.trace, a.out) + 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') diff --git a/ml/eval/eval_cl.py b/ml/eval/eval_cl.py index 990dd43..04e143c 100644 --- a/ml/eval/eval_cl.py +++ b/ml/eval/eval_cl.py @@ -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) diff --git a/ml/sim/closed_loop.py b/ml/sim/closed_loop.py index 3110675..b0677af 100644 --- a/ml/sim/closed_loop.py +++ b/ml/sim/closed_loop.py @@ -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 diff --git a/tests/test_eval_cl.py b/tests/test_eval_cl.py index 6c97318..d2291e2 100644 --- a/tests/test_eval_cl.py +++ b/tests/test_eval_cl.py @@ -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()