exp+feat: 轮次36(续)——时间账证明限幅后的损失是"站住"不是巡航;全拒缩步顶上(backoff,默认关)
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
[
|
||||
{
|
||||
"file": "ml/runs/round33_cap.pt",
|
||||
"n": 256,
|
||||
"all": {
|
||||
"n": 256,
|
||||
"mean_window_s": 3.73765625,
|
||||
"zero_share": 0.20881234062121148,
|
||||
"low_share": 0.0812884076752644,
|
||||
"mid_share": 0.27427783119434807,
|
||||
"hi_share": 0.435621420509176,
|
||||
"zero_s_per_task": 0.78046875,
|
||||
"runs_per_task": 18.5078125,
|
||||
"run_len_mean": 2.1084845926551288,
|
||||
"run_len_max": 41
|
||||
},
|
||||
"arrived_time_ok": {
|
||||
"n": 68,
|
||||
"mean_window_s": 1.9370588235294117,
|
||||
"zero_share": 0.013817187974491345,
|
||||
"low_share": 0.033404190707561496,
|
||||
"mid_share": 0.17901609474643185,
|
||||
"hi_share": 0.7737625265715153,
|
||||
"zero_s_per_task": 0.02676470588235294,
|
||||
"runs_per_task": 0.7647058823529411,
|
||||
"run_len_mean": 1.75,
|
||||
"run_len_max": 4
|
||||
},
|
||||
"arrived_slow": {
|
||||
"n": 66,
|
||||
"mean_window_s": 3.052727272727273,
|
||||
"zero_share": 0.049632717887631524,
|
||||
"low_share": 0.06293428628151677,
|
||||
"mid_share": 0.22553107008139764,
|
||||
"hi_share": 0.6619019257494541,
|
||||
"zero_s_per_task": 0.15151515151515152,
|
||||
"runs_per_task": 4.590909090909091,
|
||||
"run_len_mean": 1.6501650165016502,
|
||||
"run_len_max": 7
|
||||
},
|
||||
"not_arrived": {
|
||||
"n": 122,
|
||||
"mean_window_s": 5.111803278688525,
|
||||
"zero_share": 0.3014238984029248,
|
||||
"low_share": 0.09733179398370856,
|
||||
"mid_share": 0.31014687961003146,
|
||||
"hi_share": 0.29109742800333527,
|
||||
"zero_s_per_task": 1.5408196721311478,
|
||||
"runs_per_task": 35.92622950819672,
|
||||
"run_len_mean": 2.144421629021218,
|
||||
"run_len_max": 41
|
||||
}
|
||||
},
|
||||
{
|
||||
"file": "ml/runs/round33_base.pt",
|
||||
"n": 256,
|
||||
"all": {
|
||||
"n": 256,
|
||||
"mean_window_s": 3.482578125,
|
||||
"zero_share": 0.22394957040626332,
|
||||
"low_share": 0.08179105816901093,
|
||||
"mid_share": 0.2871435942302084,
|
||||
"hi_share": 0.4071157771945174,
|
||||
"zero_s_per_task": 0.779921875,
|
||||
"runs_per_task": 18.7421875,
|
||||
"run_len_mean": 2.0806586077532305,
|
||||
"run_len_max": 21
|
||||
},
|
||||
"arrived_time_ok": {
|
||||
"n": 100,
|
||||
"mean_window_s": 1.8818000000000001,
|
||||
"zero_share": 0.019236900839621637,
|
||||
"low_share": 0.039642895100435754,
|
||||
"mid_share": 0.19353810181740885,
|
||||
"hi_share": 0.7475821022425337,
|
||||
"zero_s_per_task": 0.0362,
|
||||
"runs_per_task": 1.2,
|
||||
"run_len_mean": 1.5083333333333333,
|
||||
"run_len_max": 5
|
||||
},
|
||||
"arrived_slow": {
|
||||
"n": 47,
|
||||
"mean_window_s": 3.2136170212765958,
|
||||
"zero_share": 0.10474046610169492,
|
||||
"low_share": 0.07216631355932204,
|
||||
"mid_share": 0.25529661016949157,
|
||||
"hi_share": 0.5677966101694916,
|
||||
"zero_s_per_task": 0.33659574468085107,
|
||||
"runs_per_task": 10.063829787234043,
|
||||
"run_len_mean": 1.6723044397463003,
|
||||
"run_len_max": 7
|
||||
},
|
||||
"not_arrived": {
|
||||
"n": 109,
|
||||
"mean_window_s": 5.067155963302753,
|
||||
"zero_share": 0.3262963499420625,
|
||||
"low_share": 0.09878331402085747,
|
||||
"mid_share": 0.32774478563151793,
|
||||
"hi_share": 0.247175550405562,
|
||||
"zero_s_per_task": 1.6533944954128441,
|
||||
"runs_per_task": 38.57798165137615,
|
||||
"run_len_mean": 2.142925089179548,
|
||||
"run_len_max": 21
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -754,6 +754,22 @@
|
||||
- **顺带记下的实测**:同一批 32 题上 empty/commit2 到达 0.469、r3/commit3 0.438、加 inc_cap 后 0.406(n=32,只作方向参考,不足以定论;256 题口径见轮次 33)。规划次数差 1.5 倍(175 vs 118)——r3 每次只新增 commit 步却已含 3 步前缀,所以同样墙钟时间内规划得更少,这正是它更贴近部署的原因。
|
||||
- 测试 35 项仍全过(`plans['r']` 只增不改)。
|
||||
|
||||
## 时间账:限幅之后 time_ok 的损失在"站住"上,不在巡航速度上(2026-10-20,轮次 36 续)
|
||||
|
||||
- **口径**:直接吃轮次 33 的 256 题轨迹转储(`ml/runs/round33_cap.pt`、`round33_base.pt`,离线分析 `ml/runs/_time_audit.py`,产物 `docs/experiment_round36_time_audit.json`),按每题的活动窗把每一步按「该步最大关节位移 / VMAX·DT」分四档:零运动、<0.5、0.5–0.95、≥0.95。切片时踩了一个自己的坑:`per['traj']` 是 `torch.stack(traj, 1)` 出来的 **[B,T,6]** 而不是 [T,B,6],按列切成了"同一时刻不同题",比值全是 70~230 的 nonsense;加了 `traj[i][0] == q0` 的断言之后才挡住这类错。
|
||||
- **实测(占活动窗的比例)**
|
||||
|
||||
| 组 | 题数 | 零运动 | <0.5 | 0.5–0.95 | ≥0.95 | 刹停段/题 | 平均段长 |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| base 到达且按时 | 100 | 1.9% | 4.0% | 19.4% | 74.8% | 1.2 | 1.5 步 |
|
||||
| base 到达但超时 | 47 | 10.5% | 7.2% | 25.5% | 56.8% | 10.1 | 1.7 步 |
|
||||
| base 未到达 | 109 | 32.6% | 9.9% | 32.8% | 24.7% | 38.6 | 2.1 步 |
|
||||
| cap(限幅) 未到达 | 122 | 30.1% | 9.7% | 31.0% | 29.1% | 35.9 | 2.1 步 |
|
||||
|
||||
- **判读**:限幅并没有把动作整体拖慢——未到达组的 ≥0.95 档反而从 24.7% 升到 29.1%;真正吃掉时限的是**零运动**(占活动窗 30%,平均每题 1.54 s)和**长刹停段**(最长 41 步 = 0.82 s 原地不动)。所以 #17 不该往"打分里加剩余时限"这种间接方向做,直接打"全拒⇒站住"这一条。
|
||||
- **改动(默认关,`--backoff`)**:全拒时不再站住,把首选候选按 1/2、1/4、1/8…(档数 = backoff)**并进同一次批量认证**(零额外几何调用),只在原候选连第 1 步都不安全时顶上;缩步候选不参与"前缀更长者优先"的比较,所以正常路径的选择逐位不变。新增 `backoff_mean` 指标记录缩步次数。测试 35→38 项(全拒→慢爬、有安全前缀时插手无效、未开批量认证时报错)。
|
||||
- **待测**:256 题 A/B(`--inc_cap 1 --cert_batch 1 --backoff 0/1/2/3`)——GPU 先让给 K-MF E1,这轮排在其后。
|
||||
|
||||
## 分支清理(2026-10-20)
|
||||
|
||||
- `feat/fb-mix`(8e5431c):反馈混合方案的中间分支,**已验证是 `exp/dagger-deploy` 的祖先**(所有提交都在后者历史里),本地/远端分支删除,内容保留在 exp/dagger-deploy。
|
||||
|
||||
+2
-1
@@ -93,6 +93,7 @@ def main():
|
||||
ap.add_argument('--inc_cap', type=int, default=0, help='1=反馈叠加后再按每轴 VMAX·DT 限幅')
|
||||
ap.add_argument('--gap_margin', type=float, default=0.0, help='安全层要求的间隙余量 mm(>0 时宽相位 cap 自动抬到 margin+0.5)')
|
||||
ap.add_argument('--cert_batch', type=int, default=0, help='1=一次 gaps 调用覆盖全部候选(压最坏情况,典型路径略贵)')
|
||||
ap.add_argument('--backoff', type=int, default=0, help='全拒时把首选候选按 1/2、1/4…缩步重试的档数(需 --cert_batch 1)')
|
||||
ap.add_argument('--max_sec', type=float, default=None)
|
||||
ap.add_argument('--seed', type=int, default=0)
|
||||
ap.add_argument('--dls', default='analytic', choices=['analytic', 'fd'])
|
||||
@@ -119,7 +120,7 @@ def main():
|
||||
safety_partial=(a.commit if (a.safety and a.partial) else 0), commit_n=a.commit,
|
||||
select=a.select, replan=a.replan, fb_gain=a.fb, fb_scale=a.fb_scale, fb_near=a.fb_near, fb_fallback=a.fb_fallback, fb_ki=a.ki, fb_decay=a.kdecay, fb_imax=a.kimax,
|
||||
fb_every=a.fb_every, safety_try=a.safety_try, sel_w=a.sel_w, dls_safety=a.dls_safety, fb_clear=a.fb_clear, fb_cmin=a.fb_cmin, sub_refresh=a.sub_refresh, fb_block_gain=a.fb_block_gain,
|
||||
inc_cap=a.inc_cap, gap_margin=a.gap_margin, cert_batch=a.cert_batch, return_traj=bool(a.trace))
|
||||
inc_cap=a.inc_cap, gap_margin=a.gap_margin, cert_batch=a.cert_batch, backoff=a.backoff, return_traj=bool(a.trace))
|
||||
wall = time.perf_counter() - t0
|
||||
result = {**meta, 'metrics': r, 'simulation_wall_s': wall}
|
||||
print(json.dumps(json_finite(result), ensure_ascii=False, allow_nan=False), flush=True)
|
||||
|
||||
+31
-10
@@ -163,7 +163,7 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None,
|
||||
qp0=None, steps=1, z_scale=1.0, select='consist', oracle=None, replan='r3', sub_rule='cross',
|
||||
fb_gain=0.0, fb_scale=0.0, fb_near=0.2, fb_fallback=0.0, safety_partial=0, commit_n=3, collect=None,
|
||||
fb_exec=0.0, exec_safety=0, fb_ki=0.0, fb_decay=0.9, fb_imax=0.5, fb_every=1, safety_try=0, sel_w=0.1, dls_safety=0, fb_clear=0.0, fb_cmin=0.3, sub_refresh=0, fb_block_gain=1.0,
|
||||
inc_cap=0, gap_margin=0.0, cert_batch=0, progress=None):
|
||||
inc_cap=0, gap_margin=0.0, cert_batch=0, backoff=0, progress=None):
|
||||
"""use_sub=False:不用子目标(sf=0);sub_override=(subs [B,M,6] 绝对关节角, n [B] 个数 或 need [B] bool):冷启动时用给定途经点代替子目标头;
|
||||
qp0 [B,6]:起点的上一帧(默认 = q0,即静止起步)。steps:MeanFlow 采样段数;z_scale:噪声缩放;
|
||||
select:consist(部署口径:冷启动价值前瞻、之后与上一条已提交计划的一致性)/ value(每次都价值前瞻)/ first(不选,取第 0 条)。
|
||||
@@ -172,7 +172,10 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None,
|
||||
sub_rule:子目标推进规则,见 sub_advance。除默认值外均为诊断用。
|
||||
inc_cap=1:把「策略动作 + 目标反馈」的合计增量再按每轴 VMAX·DT 限一次幅(默认关;关时反馈是在 ±1 限幅之后叠加的,第30轮实测最大 1.66×VMAX)。
|
||||
gap_margin>0:安全层要求间隙大于该毫米余量而不是 >0;宽相位 cap 自动抬到 margin+0.5,否则 0.5 的截顶值会让余量门限恒假。
|
||||
backoff>0:全拒(连第 1 步都不安全)时不再站住,把首选候选按 1/2、1/4…(共 backoff 档)缩步一起送进同一次认证,
|
||||
取能通过的第 1 档执行;缩步候选不参与"前缀更长者优先"的比较,只在原候选全被拒时顶上。需 cert_batch=1。
|
||||
两者默认值下行为与第32轮逐位相同。"""
|
||||
if backoff and not cert_batch: raise ValueError('backoff 目前只实现在 cert_batch=1 口径')
|
||||
g = torch.Generator(device=device); g.manual_seed(seed)
|
||||
q0, T, R, ex, tr = tasks_to_tensors(tasks, device)
|
||||
B = q0.shape[0]
|
||||
@@ -193,6 +196,7 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None,
|
||||
sub_t = torch.zeros(B, device=device); sub_prev = q0.clone()
|
||||
done = torch.zeros(B, dtype=torch.bool, device=device); t_done = torch.full((B,), float('nan'), device=device)
|
||||
col = torch.zeros(B, device=device); travel = torch.zeros(B, device=device); brakes = torch.zeros(B, device=device)
|
||||
bo = torch.zeros(B, device=device) # 缩步顶上的次数(backoff>0 才会计)
|
||||
first_hit = torch.full((B,), float('nan'), device=device); dls_it = torch.full((B,), -1, dtype=torch.long, device=device)
|
||||
# 2026-10-20(轮次33):速度/加速度按「实际执行位移」算,不看指令——第30轮证明反馈是在限幅之后叠加的
|
||||
vratio = torch.zeros(B, device=device); aratio = torch.zeros(B, device=device); prev_exec = torch.zeros(B, 6, device=device)
|
||||
@@ -330,18 +334,34 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None,
|
||||
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
|
||||
if cert_batch: # 一次 gaps 覆盖全部候选:典型路径略贵,但把「前面候选被拒→串行调用 K 次」的最坏情况封成 1 次
|
||||
cks = inc[ar[:, None], order[:, :ntry], r:r + commit_n].reshape(B * ntry, commit_n, 6)
|
||||
pts, mids = execution_points(kin, qe.repeat_interleave(ntry, 0), cks)
|
||||
gaps6 = kin.gaps(torch.cat([pts, mids], 1).reshape(-1, 6), gcap)['gm'].min(-1).values.view(B, ntry, 2 * commit_n)
|
||||
plen = torch.cumprod(((gaps6 > gap_margin)[:, :, :commit_n] &
|
||||
(gaps6 > gap_margin)[:, :, commit_n:]).long(), -1).sum(-1)
|
||||
cks = inc[ar[:, None], order[:, :ntry], r:r + commit_n] # [B,ntry,cn,6]
|
||||
if backoff: # 缩步档与正常候选并进同一次认证,不额外调用几何
|
||||
sc = (0.5 ** torch.arange(1, backoff + 1, device=device, dtype=inc.dtype))[None, :, None, None]
|
||||
cands = torch.cat([cks, inc[ar, order[:, 0], r:r + commit_n][:, None] * sc], 1)
|
||||
else:
|
||||
cands = cks
|
||||
nc = cands.shape[1]
|
||||
pts, mids = execution_points(kin, qe.repeat_interleave(nc, 0), cands.reshape(B * nc, commit_n, 6))
|
||||
gaps6 = kin.gaps(torch.cat([pts, mids], 1).reshape(-1, 6), gcap)['gm'].min(-1).values.view(B, nc, 2 * commit_n)
|
||||
stepok = (gaps6 > gap_margin)[:, :, :commit_n] & (gaps6 > gap_margin)[:, :, commit_n:]
|
||||
pl_all = torch.cumprod(stepok.long(), -1).sum(-1) # [B,nc] 每条候选的安全前缀长度
|
||||
plen = pl_all[:, :ntry]
|
||||
maxlen = plen.max(-1).values
|
||||
best = plen.argmax(-1) # 与串行同口径:严格更大者胜 ⇒ 首个最大值
|
||||
chosen = cks.view(B, ntry, commit_n, 6)[ar, best]
|
||||
if backoff: # 缩步候选只在全拒时顶上:不参与「前缀更长者优先」的比较,所以正常路径逐位不变
|
||||
full0 = maxlen == 0
|
||||
bb = pl_all[:, ntry:].argmax(-1) # 平手取缩得最少的(档 0 = 1/2)
|
||||
mv = pl_all[ar, ntry + bb]
|
||||
bo += (full0 & (mv > 0))
|
||||
best = torch.where(full0, ntry + bb, best)
|
||||
maxlen = torch.where(full0, mv, maxlen)
|
||||
chosen_k = torch.where(full0, order[:, 0], order[ar, best.clamp(max=ntry - 1)])
|
||||
else:
|
||||
chosen_k = order[ar, best]
|
||||
chosen = cands[ar, best]
|
||||
if return_traj:
|
||||
cert_pts = pts.view(B, ntry, commit_n, 6)[ar, best]
|
||||
cert_mids = mids.view(B, ntry, commit_n, 6)[ar, best]
|
||||
cert_pts = pts.view(B, nc, commit_n, 6)[ar, best]
|
||||
cert_mids = mids.view(B, nc, commit_n, 6)[ar, best]
|
||||
cert_gaps = gaps6[ar, best]
|
||||
else:
|
||||
for kk in range(ntry):
|
||||
@@ -466,6 +486,7 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None,
|
||||
'collision_rate': (col > 0).float().mean().item(), 'collision_steps_mean': col.mean().item(),
|
||||
'travel_ratio_median': (travel[okr] / tr[okr].clamp(min=1)).median().item() if okr.any() else None,
|
||||
'time_mean_s': t_done[ok].mean().item() if ok.any() else None, 'brakes_mean': brakes.mean().item(),
|
||||
'backoff_mean': bo.mean().item(),
|
||||
'start_fail': start_fail.float().mean().item(), 'goal_ratio_2s': ratio2.median().item(),
|
||||
'speed_ratio_mean': vratio.mean().item(), 'over_vmax_rate': (vratio > 1.001).float().mean().item(),
|
||||
'time_budget': 'teacher×2(≥3 s)' if bool(has_ref.all()) else ('fixed %.0f s' % (max_sec or NOREF_SEC) if not has_ref.any() else 'mixed')}
|
||||
@@ -473,7 +494,7 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None,
|
||||
# 首次方向;全程没偏离到 FIRST_DEG 的(基本不绕 J1)退回净方向
|
||||
side = torch.where(j1_first != 0, j1_first, torch.where(j1_max >= -j1_min, torch.ones_like(j1_max), -torch.ones_like(j1_max)))
|
||||
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(),
|
||||
'side': side.cpu(), 'start_fail': start_fail.cpu(), 'brakes': brakes.cpu(), 'backoff': bo.cpu(), 'first_hit': first_hit.cpu(), 'dls_it': dls_it.cpu(),
|
||||
'goal_ratio_2s': ratio2.cpu(), 'speed_ratio': vratio.cpu(), 'accel_ratio': aratio.cpu()}
|
||||
if return_traj:
|
||||
per['traj'] = torch.stack(traj, 1).cpu()
|
||||
|
||||
@@ -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