# 2D 绕柱玩具实验:比较不同动作头在"左右(上下)双模态绕行"下的闭环表现 import torch, torch.nn as nn, numpy as np, math, time, sys, json torch.manual_seed(0); np.random.seed(0); torch.set_num_threads(2) R=1.0; H=16; STEP=0.05 def demo(s,g,mode): via=np.array([0.0, 1.35*mode]) pts=[s, np.array([-0.9,0.95*mode]) if s[0]<-0.9 else s, via, np.array([0.9,0.95*mode]), g] P=np.array(pts); L=np.r_[0,np.cumsum(np.linalg.norm(np.diff(P,axis=0),axis=1))] n=int(L[-1]/STEP)+1; u=np.linspace(0,L[-1],n) x=np.interp(u,L,P[:,0]); y=np.interp(u,L,P[:,1]); Q=np.c_[x,y] # 平滑 for _ in range(20): Q[1:-1]=0.5*Q[1:-1]+0.25*(Q[:-2]+Q[2:]) return Q def sample_task(hard=False): sy=np.random.uniform(-0.3,0.3) if hard else np.random.uniform(-1.5,1.5) gy=np.random.uniform(-0.3,0.3) if hard else np.random.uniform(-1.5,1.5) return np.array([np.random.uniform(-2.5,-1.6),sy]), np.array([np.random.uniform(1.6,2.5),gy]) def collide_path(Q): return np.any(np.linalg.norm(Q,axis=1)0.3 else (1 if np.random.rand()<0.5 else -1) Q=demo(s,g,mode) if collide_path(Q): continue D=np.diff(Q,axis=0); D=np.r_[D,np.zeros((H,2))] for i in range(len(Q)-1): O.append(np.r_[Q[i],g]); A.append(D[i:i+H].reshape(-1)/STEP) return torch.tensor(np.array(O),dtype=torch.float32), torch.tensor(np.array(A),dtype=torch.float32) O,A=build(); print('samples',len(O),flush=True) AD=2*H def mlp(i,o,h=256): return nn.Sequential(nn.Linear(i,h),nn.SiLU(),nn.Linear(h,h),nn.SiLU(),nn.Linear(h,h),nn.SiLU(),nn.Linear(h,o)) class MSE(nn.Module): def __init__(s): super().__init__(); s.f=mlp(4,AD) def loss(s,o,a): return ((s.f(o)-a)**2).mean() def sample(s,o,k=1): return s.f(o).unsqueeze(1).expand(-1,k,-1) class WTA(nn.Module): K=4 def __init__(s): super().__init__(); s.f=mlp(4,AD*s.K+s.K) def out(s,o): y=s.f(o); return y[:,:AD*s.K].view(-1,s.K,AD), y[:,AD*s.K:] def loss(s,o,a): p,l=s.out(o); e=(p-a[:,None]).abs().mean(-1); k=e.argmin(1) return e.gather(1,k[:,None]).mean()+0.5*nn.functional.cross_entropy(l,k) def sample(s,o,k=1): p,l=s.out(o) if k==1: return p.gather(1,l.argmax(1)[:,None,None].expand(-1,1,AD)) o_=l.argsort(1,descending=True); return p.gather(1,o_[:,:,None].expand(-1,-1,AD)) # 按概率排序 class FM(nn.Module): def __init__(s,steps): super().__init__(); s.f=mlp(4+AD+1,AD); s.steps=steps def loss(s,o,a): e=torch.randn_like(a); t=torch.rand(len(a),1); z=(1-t)*a+t*e return ((s.f(torch.cat([o,z,t],1))-(e-a))**2).mean() def sample(s,o,k=1): B=len(o); oo=o.repeat_interleave(k,0); z=torch.randn(B*k,AD); dt=1/s.steps for i in range(s.steps): t=torch.full((B*k,1),1-i*dt); z=z-dt*s.f(torch.cat([oo,z,t],1)) return z.view(B,k,AD) class MF(nn.Module): # MeanFlow (Geng et al. 2025) def __init__(s): super().__init__(); s.f=mlp(4+AD+2,AD) def u(s,o,z,r,t): return s.f(torch.cat([o,z,r,t],1)) def loss(s,o,a): B=len(a); e=torch.randn_like(a) t=torch.rand(B,1); r=torch.rand(B,1); t,r=torch.maximum(t,r),torch.minimum(t,r) eq=(torch.rand(B,1)<0.75); r=torch.where(eq,t,r) z=(1-t)*a+t*e; v=e-a fn=lambda z_,r_,t_: s.u(o,z_,r_,t_) u,dudt=torch.func.jvp(fn,(z,r,t),(v,torch.zeros_like(r),torch.ones_like(t))) tgt=(v-(t-r)*dudt).detach() return ((u-tgt)**2).mean() def sample(s,o,k=1): B=len(o); oo=o.repeat_interleave(k,0); e=torch.randn(B*k,AD) x=e-s.u(oo,e,torch.zeros(B*k,1),torch.ones(B*k,1)); return x.view(B,k,AD) def train(m,iters=6000,bs=512): opt=torch.optim.AdamW(m.parameters(),1e-3,weight_decay=1e-4) sch=torch.optim.lr_scheduler.CosineAnnealingLR(opt,iters) for it in range(iters): idx=torch.randint(0,len(O),(bs,)); l=m.loss(O[idx],A[idx]) opt.zero_grad(); l.backward(); opt.step(); sch.step() return m def seg_collide(p,q): for a in np.linspace(0,1,6): if np.linalg.norm(p+(q-p)*a)0 for r in res]) br=np.mean([r['brakes'] for r in res]); up=np.mean([r['mode']>0 for r in res if r['ok']]) if ok>0 else float('nan') return dict(reach=round(ok,3),collide_ep=round(col,3),brakes=round(br,1),frac_up=round(up,2)) methods={'MSE':lambda:MSE(),'WTA4':lambda:WTA(),'FM10':lambda:FM(10),'FM1':lambda:FM(1),'MeanFlow1':lambda:MF()} which=sys.argv[1:] or list(methods) out={} for name in which: torch.manual_seed(0); t0=time.time(); m=train(methods[name]()); tt=time.time()-t0 o=torch.tensor(np.r_[[-2,0],[2,0]],dtype=torch.float32)[None] t1=time.time(); [m.sample(o,8) for _ in range(50)]; lat=(time.time()-t1)/50*1000 r={'train_s':round(tt),'infer_ms_k8':round(lat,2)} r['hard_raw_k1']=evaluate(m,1,False); r['hard_safe_k8']=evaluate(m,4 if name=='WTA4' else (1 if name=='MSE' else 8),True) r['easy_raw_k1']=evaluate(m,1,False,hard=False) out[name]=r; print(name,json.dumps(r,ensure_ascii=False),flush=True) json.dump(out,open(f'/workspace/work/toy/res_{"_".join(which)}.json','w'),indent=1)