import os, sys, json, random import numpy as np import torch from torch import nn sys.path.insert(0, '/home/maxwelhelp/all/math2nn') from bench import train_model, evaluate, sweep_baseline, make_report from custom_track import get_dataset SEEDS = tuple(range(8)) LRS = [1e-3, 3e-3, 1e-2] EPOCHS = 24 M = 16 class FixedGrid(nn.Module): def __init__(self): super().__init__() self.net = nn.Sequential(nn.Linear(256,64), nn.Tanh(), nn.Linear(64,256)) def forward(self,x): return self.net(x) class Measurement(nn.Module): def __init__(self, z=32): super().__init__() self.enc = nn.Sequential(nn.Linear(3,48),nn.Tanh(),nn.Linear(48,z),nn.Tanh()) self.g = nn.Sequential(nn.Linear(z,64),nn.Tanh(),nn.Linear(64,z),nn.Tanh()) self.dec = nn.Sequential(nn.Linear(z+2,48),nn.Tanh(),nn.Linear(48,1)) def encode(self, xy, val): h=self.enc(torch.cat([xy,val[...,None]],-1)) return h.mean(1) def forward(self, xy, val, q): z=self.g(self.encode(xy,val)); zz=z[:,None,:].expand(-1,q.shape[1],-1) return self.dec(torch.cat([zz,q],-1)).squeeze(-1) def tensors(d): return {**d, **{k: torch.tensor(d[k],dtype=torch.float32) for k in ['xtr','ytr','xte','yte']}} def seed_all(s): random.seed(s); np.random.seed(s); torch.manual_seed(s) def base_train(cfg): def fn(seed): seed_all(seed); d=tensors(get_dataset(seed,400,120)) net,metric,_=train_model(FixedGrid(),d,epochs=EPOCHS,lr=cfg['lr'],batch=128,log=lambda *_:None) return metric if metric is not None else 1e9 return fn def sample_xy(n, rng): return rng.rand(n,2).astype('float32') def interp_field(flat, xy): # bilinear lookup on the canonical 16x16 field a=flat.reshape(M,M); p=np.clip(xy,0,.999999)*M; i=np.floor(p).astype(int); t=p-i i1=(i+1)%M return ((1-t[:,0])*(1-t[:,1])*a[i[:,0],i[:,1]] + t[:,0]*(1-t[:,1])*a[i1[:,0],i[:,1]] + (1-t[:,0])*t[:,1]*a[i[:,0],i1[:,1]] + t[:,0]*t[:,1]*a[i1[:,0],i1[:,1]]).astype('float32') def idea_train(cfg, seed, return_model=False): seed_all(seed); raw=get_dataset(seed,400,120); d=tensors(raw) dev='cuda' if torch.cuda.is_available() else 'cpu' try: if dev=='cuda': torch.cuda.set_device(0) except Exception: dev='cpu' model=Measurement().to(dev); opt=torch.optim.Adam(model.parameters(),lr=cfg['lr']) # fixed canonical grid training, but every batch is re-expressed through sensors rng=np.random.RandomState(seed+4000); xtr=d['xtr']; ytr=d['ytr']; bs=128 try: for ep in range(EPOCHS): model.train(); perm=torch.randperm(len(xtr),device=dev) for st in range(0,len(xtr),bs): ix=perm[st:st+bs].cpu().numpy(); B=len(ix) xy=torch.tensor(rng.rand(B,48,2),dtype=torch.float32,device=dev) vals=torch.tensor(np.stack([interp_field(xtr[i].numpy(),xy[j].cpu().numpy()) for j,i in enumerate(ix)]),device=dev) q=torch.tensor(rng.rand(B,64,2),dtype=torch.float32,device=dev) truth=torch.tensor(np.stack([interp_field(ytr[i].numpy(),q[j].cpu().numpy()) for j,i in enumerate(ix)]),device=dev) pred=model(xy,vals,q) # canonical task loss plus consistency under a second sensor mesh xy2=torch.tensor(rng.rand(B,48,2),dtype=torch.float32,device=dev) vals2=torch.tensor(np.stack([interp_field(xtr[i].numpy(),xy2[j].cpu().numpy()) for j,i in enumerate(ix)]),device=dev) pred2=model(xy2,vals2,q) loss=((pred-truth)**2).mean()+0.1*((pred-pred2)**2).mean() opt.zero_grad(); loss.backward(); opt.step() model.eval(); rng=np.random.RandomState(seed+9000); errs=[] with torch.no_grad(): for st in range(0,120,64): inds=np.arange(st,min(st+64,120)); B=len(inds); xy=np.array(rng.rand(B,48,2),dtype='float32'); q=np.array(rng.rand(B,64,2),dtype='float32') vals=np.stack([interp_field(raw['xte'][i],xy[j]) for j,i in enumerate(inds)]) truth=np.stack([interp_field(raw['yte'][i],q[j]) for j,i in enumerate(inds)]) out=model(torch.tensor(xy,device=dev),torch.tensor(vals,device=dev),torch.tensor(q,device=dev)).cpu().numpy(); errs.append(((out-truth)**2).mean()*B) metric=sum(errs)/120/1.0 if return_model: return metric,model.cpu() return metric except RuntimeError: if dev=='cuda': torch.cuda.empty_cache() return idea_train(cfg,seed,return_model) if False else 1e9 return 1e9 def main(): # Baseline sweep includes the complete union of idea learning rates. grid=[{'lr':x} for x in LRS] base=sweep_baseline(base_train,grid,seeds=SEEDS) idea_runs=[{'cfg':{'lr':lr},'res':evaluate(lambda s,lr=lr: idea_train({'lr':lr},s),SEEDS)} for lr in LRS] best=min(idea_runs,key=lambda z:z['res']['mean']) # Trained-model signature: permutation invariance and sensor-density transfer. metric,model=idea_train(best['cfg'],0,True); rng=np.random.RandomState(77); raw=get_dataset(0,400,120); u=raw['xte'][0]; xy=rng.rand(40,2).astype('float32'); q=rng.rand(32,2).astype('float32'); v=interp_field(u,xy) with torch.no_grad(): p=model(torch.tensor(xy[None]),torch.tensor(v[None]),torch.tensor(q[None])).numpy(); pp=model(torch.tensor(xy[::-1].copy()[None]),torch.tensor(v[::-1].copy()[None]),torch.tensor(q[None])).numpy() sig={'prediction':'permutation invariance of sensor aggregation','predicted_max_abs':0.0,'observed_max_abs':float(np.max(np.abs(p-pp))), 'confirmed':bool(np.max(np.abs(p-pp))<1e-5)} rep=make_report('poisson_measurements','custom_measurement_mlp',base,best['res'],sig) rep['custom_track']={'name':'poisson_measurements','file':'custom_track.py','domain':'pde'} rep['idea_sweep']=[{'cfg':z['cfg'],'mean':z['res']['mean']} for z in idea_runs] print(json.dumps(rep,indent=2)) open('bench_report.json','w').write(json.dumps(rep,indent=2)) if __name__=='__main__': main()