import json, math, time import numpy as np import torch from torch import nn SEED = 7 np.random.seed(SEED) torch.manual_seed(SEED) torch.set_num_threads(4) device = "cuda" if torch.cuda.is_available() else "cpu" try: if device == "cuda": torch.cuda.empty_cache() except Exception: device = "cpu" # ---------- exact Gaussian probability-flow sanity checks ---------- def gaussian_sweep(): # p=N(mu,S), score=-(x-mu)/S. With v=0, u=gamma*(x-mu)/S, # hence dS/dt=2 gamma and one Euler step has # S_new=S+2 gamma dt + gamma^2 dt^2/S. S0, dt = 0.8, 0.01 gammas = np.array([0.0, .05, .10, .20, .40]) slope_obs, slope_pred, corr_obs, corr_pred = [], [], [], [] for g in gammas: # Exact velocity evaluated on a large deterministic Gaussian quantile cloud. z = np.linspace(-3.5, 3.5, 200001) z = (z - z.mean()) / z.std() x = math.sqrt(S0) * z score = -(x / S0) u = -g * score # E[2 (x-mu) u] gives variance derivative. slope = np.mean(2*x*u) slope_obs.append(float(slope)); slope_pred.append(float(2*g)) s1 = np.mean((x + dt*u)**2) corr = s1 - S0 - 2*g*dt corr_obs.append(float(corr)); corr_pred.append(float(g*g*dt*dt/S0)) # Verify identity for a nonzero drift and a non-Gaussian numerical cloud: # empirical continuity velocity gives the same density evolution as SDE diffusion. identity = [] for g in [.05, .2, .4]: identity.append(float(abs(slope_obs[list(gammas).index(g)] - 2*g))) return { "gamma": gammas.tolist(), "variance_slope_observed": slope_obs, "variance_slope_predicted_2gamma": slope_pred, "euler_excess_variance_observed": corr_obs, "euler_excess_variance_predicted": corr_pred, "identity_abs_errors": identity, "zero_gamma_correction": corr_obs[0], } # ---------- direct-cost amortized controller toy ---------- class Controller(nn.Module): def __init__(self): super().__init__() self.net = nn.Sequential(nn.Linear(8, 48), nn.Tanh(), nn.Linear(48, 48), nn.Tanh(), nn.Linear(48, 2)) def forward(self, x, t, prompt): # prompt=(target mean x/y, target variance x/y, gamma) b, n, _ = x.shape tt = torch.full((b,n,1), t, device=x.device) p = prompt[:,None,:].expand(b,n,-1) return self.net(torch.cat([x,tt,p], dim=-1)) def rollout(model, prompt, z, steps=30, T=1.0): # score estimated from current particle covariance, exactly as a finite-cloud proxy. x = z.clone() dt = T / steps running = torch.zeros(x.shape[0], device=x.device) for k in range(steps): t = k * dt v = model(x, t, prompt) mu = x.mean(1, keepdim=True) centered = x - mu cov = (centered.square().mean(1, keepdim=True) + 1e-3) score = -centered / cov gamma = prompt[:,4].view(-1,1,1) u = v - gamma * score running = running + dt * (0.5*v.square().sum(-1).mean(-1)) x = x + dt*u target_mu = prompt[:,:2] target_var = prompt[:,2:4] final_mu = x.mean(1) final_var = ((x-final_mu[:,None,:])**2).mean(1) terminal = 12.0*(final_mu-target_mu).square().sum(-1) + 8.0*(final_var-target_var).square().sum(-1) return (running + terminal).mean(), (final_mu-target_mu).abs().mean().detach(), (final_var-target_var).abs().mean().detach() def make_tasks(n, dev): # Compact task distribution: diagonal Gaussian targets and varying diffusion. mu = torch.empty(n,2,device=dev).uniform_(-1.0,1.0) var = torch.empty(n,2,device=dev).uniform_(0.45,1.5) gamma = torch.empty(n,1,device=dev).uniform_(0.03,0.25) return torch.cat([mu,var,gamma],1) def amortized_experiment(): dev=device N, B, particles, steps = 64, 16, 96, 30 tasks=make_tasks(N,dev) z=torch.randn(N,particles,2,device=dev) # common initial N(0,I) model=Controller().to(dev) opt=torch.optim.Adam(model.parameters(),lr=3e-3) t0=time.time() for it in range(700): ix=torch.randint(0,N,(B,),device=dev) loss,_,_=rollout(model,tasks[ix],z[ix],steps) opt.zero_grad(); loss.backward(); torch.nn.utils.clip_grad_norm_(model.parameters(),10); opt.step() train_time=time.time()-t0 with torch.no_grad(): amort=[]; mm=[]; vv=[] for i in range(N): l,m,v=rollout(model,tasks[i:i+1],z[i:i+1],steps) amort.append(l.item()); mm.append(m.item()); vv.append(v.item()) # Independent baseline: optimize one copy per task for a short, equal total budget. # This represents per-task test-time solving with the same controller class. indep=[]; im=[]; iv=[]; t1=time.time() for i in range(8): # enough to estimate per-task behavior without inflating runtime local=Controller().to(dev); op=torch.optim.Adam(local.parameters(),lr=3e-3) for _ in range(90): l,_,_=rollout(local,tasks[i:i+1],z[i:i+1],steps) op.zero_grad(); l.backward(); op.step() with torch.no_grad(): l,m,v=rollout(local,tasks[i:i+1],z[i:i+1],steps) indep.append(l.item()); im.append(m.item()); iv.append(v.item()) indep_time=(time.time()-t1)/8 return { "device":dev, "amortized_pretraining_seconds":train_time, "amortized_mean_cost":float(np.mean(amort)), "amortized_mean_mean_error":float(np.mean(mm)), "amortized_mean_variance_error":float(np.mean(vv)), "independent_90step_mean_cost":float(np.mean(indep)), "independent_mean_error":float(np.mean(im)), "independent_variance_error":float(np.mean(iv)), "independent_seconds_per_task":indep_time, "note":"Costs are direct Monte Carlo rollout objectives; no target trajectories are used." } def main(): out={"math_check":gaussian_sweep(),"controller_experiment":amortized_experiment()} print(json.dumps(out,indent=2)) if __name__ == '__main__': main()