exp+feat: 轮次36(续)——时间账证明限幅后的损失是"站住"不是巡航;全拒缩步顶上(backoff,默认关)
This commit is contained in:
@@ -713,5 +713,73 @@ class CertBatchEquivalenceTest(unittest.TestCase):
|
||||
self.assertEqual(kin.gm_groups(q5, [(0, 2)], 40.0, xf=xf5).shape, (5, 1))
|
||||
|
||||
|
||||
class BackoffTest(unittest.TestCase):
|
||||
"""#17:全拒时把首选候选缩步顶上——不许改变「还有安全前缀」的题,但要把站住变成慢爬。"""
|
||||
LIM = 0.5 # 桩里 J1 超过它就是碰撞(一步满速 = VMAX 60°/s × DT 0.02 = 1.2°)
|
||||
|
||||
class StubModel:
|
||||
act_scale = 1.0
|
||||
|
||||
def __init__(self, act):
|
||||
self.act = act
|
||||
|
||||
def sample_k(self, feat, K, z, steps=1):
|
||||
B = feat.shape[0]
|
||||
return (self.act.expand(B, K, 16, 6).clone(), torch.zeros(B, device=feat.device),
|
||||
torch.zeros(B, 13, device=feat.device))
|
||||
|
||||
def run1(self, acts, backoff, cert_batch=1):
|
||||
class StepKin:
|
||||
def clamp_box(self, q):
|
||||
return q
|
||||
|
||||
def gaps(self, q, cap):
|
||||
bad = q[:, 0] > BackoffTest.LIM
|
||||
return {'gm': torch.where(bad[:, None], -torch.ones(len(q), 49, device=q.device),
|
||||
torch.full((len(q), 49), 1.0, device=q.device))}
|
||||
|
||||
model = self.StubModel(torch.tensor(acts, dtype=torch.float32))
|
||||
tasks = [{'q0': [0] * 6, 'T': [10, 0, 0]}]
|
||||
with patch('ml.sim.closed_loop.features', return_value=torch.zeros(1, 83)), \
|
||||
patch('ml.sim.closed_loop.goal_err',
|
||||
side_effect=lambda kin, q, T, R: (torch.full((q.shape[0],), 10.0), torch.zeros(q.shape[0]))):
|
||||
res, per = rollout(model, StepKin(), tasks, 'cpu', K=len(acts), safety=True, max_sec=0.06,
|
||||
return_traj=True, use_sub=False, select='first', replan='empty', commit_n=2,
|
||||
safety_partial=1, cert_batch=cert_batch, backoff=backoff)
|
||||
return res, per
|
||||
|
||||
def test_full_rejection_creeps_instead_of_standing_still(self):
|
||||
acts = [[[1.0, 0, 0, 0, 0, 0], [1.0, 0, 0, 0, 0, 0]] + [[0, 0, 0, 0, 0, 0]] * 14]
|
||||
_, p0 = self.run1(acts, 0)
|
||||
self.assertEqual(p0['traj'][0, :, 0].tolist(), [0.0] * 4) # 0.06 s 内规划 2 次,两次都全拒
|
||||
self.assertEqual(float(p0['brakes'][0]), 2.0)
|
||||
self.assertEqual(float(p0['backoff'][0]), 0.0)
|
||||
_, p1 = self.run1(acts, 1) # 1/2 档 = 0.6° 仍然越界 ⇒ 与不开缩步逐位相同
|
||||
torch.testing.assert_close(p1['traj'], p0['traj'], rtol=0, atol=0)
|
||||
torch.testing.assert_close(p1['brakes'], p0['brakes'], rtol=0, atol=0)
|
||||
_, p2 = self.run1(acts, 2) # 1/4 档 = 0.3° 安全 ⇒ 先爬一步,再规划时原地缩步也被拒
|
||||
self.assertEqual([round(v, 5) for v in p2['traj'][0, :, 0].tolist()], [0.0, 0.3, 0.3, 0.3])
|
||||
self.assertEqual(float(p2['backoff'][0]), 1.0)
|
||||
self.assertEqual(float(p2['brakes'][0]), 1.0)
|
||||
|
||||
def test_backoff_never_overrides_a_candidate_with_a_safe_prefix(self):
|
||||
# 第 1 步安全(0.24°)、第 2 步越界(0.84°)⇒ 前缀 1 步就够,缩步档不该插手
|
||||
acts = [[[0.2, 0, 0, 0, 0, 0], [0.5, 0, 0, 0, 0, 0]] + [[0, 0, 0, 0, 0, 0]] * 14]
|
||||
_, p0 = self.run1(acts, 0)
|
||||
_, p2 = self.run1(acts, 2)
|
||||
for key in ('traj', 'brakes', 'backoff'):
|
||||
with self.subTest(key=key):
|
||||
torch.testing.assert_close(p2[key], p0[key], rtol=0, atol=0)
|
||||
for key in ('commit', 'certified', 'points', 'gaps'):
|
||||
with self.subTest(key=key):
|
||||
torch.testing.assert_close(p2['plans'][key], p0['plans'][key], rtol=0, atol=0)
|
||||
self.assertGreater(float(p2['traj'][0, 1, 0]), 0.0)
|
||||
|
||||
def test_backoff_requires_batched_certification(self):
|
||||
acts = [[[1.0, 0, 0, 0, 0, 0], [1.0, 0, 0, 0, 0, 0]] + [[0, 0, 0, 0, 0, 0]] * 14]
|
||||
with self.assertRaisesRegex(ValueError, 'cert_batch'):
|
||||
self.run1(acts, 1, cert_batch=0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user