import json, math, time, sys import numpy as np import torch import torch.nn as nn sys.path.insert(0, '/home/maxwelhelp/all/math2nn') from bench import make_model, make_report from bench.protocol import evaluate, sweep_baseline TRACK='poisson_hodge_periodic'; MODEL='mlp_tiny'; N=8; H=1.0/N EPS=torch.tensor([[2.0,0.0],[0.0,1.0]],dtype=torch.float32) EINV=torch.linalg.inv(EPS) def dd(u, axis): return (torch.roll(u,-1,axis)-torch.roll(u,1,axis))/(2*H) def div(p): return dd(p[...,0],-2)+dd(p[...,1],-1) def curl2(a): return torch.stack((dd(a,-1),-dd(a,-2)),dim=-1) def grad(u): return torch.stack((dd(u,-2),dd(u,-1)),dim=-1) def make_data(seed,n): rng=np.random.default_rng(seed); yy,xx=np.meshgrid(np.arange(N)/N,np.arange(N)/N,indexing='ij') modes=[(1,0),(0,1),(1,1),(2,1)] B=np.asarray([np.sin(2*np.pi*(k*xx+l*yy)) for k,l in modes],np.float32) a=rng.normal(0,.8,(n,len(modes))).astype(np.float32) phi=np.einsum('sm,mij->sij',a,B) ph=torch.tensor(phi); g=grad(ph); p=torch.einsum('ij,sxyj->sxyi',EPS,g) rho=-div(p).numpy() # With diagonal EPS, these sine modes are exact discrete eigenmodes. rcoef=np.einsum('sij,mij->sm',rho,B)/(N*N/2) return {'xtr':rcoef.astype(np.float32),'ytr':phi.reshape(n,-1).astype(np.float32), 'xte':rcoef.astype(np.float32),'yte':phi.reshape(n,-1).astype(np.float32), 'task':'regression','metric':'mse','input_shape':(len(modes),),'out_dim':N*N, 'phi_basis':B,'modes':modes} def dataset(seed): return make_data(seed,96) def tensors(d): return tuple(torch.tensor(d[k]) for k in ('xtr','ytr','xte','yte')) def eigenvalues(): modes=[(1,0),(0,1),(1,1),(2,1)] return [2*(math.sin(2*math.pi*k/N)/H)**2+(math.sin(2*math.pi*l/N)/H)**2 for k,l in modes] def p0_from_x(x,B): lam=torch.tensor(eigenvalues(),dtype=x.dtype,device=x.device) a=x/lam ph=torch.einsum('sm,mij->sij',a,B.to(x.device)) return torch.einsum('ij,sxyj->sxyi',EPS.to(x.device),grad(ph)),ph def reconstruct_phi(p,B): # Recover coefficients by projection onto the known task basis; this is only # an evaluation readout, while both systems are trained end-to-end separately. q=torch.einsum('ij,sxyj->sxyi',EINV.to(p.device),p) modes=[(1,0),(0,1),(1,1),(2,1)] gb=[] for k,l in modes: b=torch.tensor(B[len(gb)],device=p.device) gb.append(grad(b)) G=torch.stack(gb) # Least-squares coefficient of q against each mode's discrete gradient. coeff=[] for m in range(len(modes)): num=(q*G[m]).sum(dim=(-3,-2,-1)); den=(G[m]*G[m]).sum() coeff.append(num/den) return torch.einsum('sm,mij->sij',torch.stack(coeff,1),B.to(p.device)) def train_one(seed,lr,idea,epochs=35,return_sig=False): torch.manual_seed(1000+seed); np.random.seed(1000+seed) d=dataset(seed); B=torch.tensor(d['phi_basis']); x,y,xt,yt=tensors(d) net=make_model(MODEL,d['input_shape'],d['out_dim']); opt=torch.optim.Adam(net.parameters(),lr=lr) p0, _=p0_from_x(x,B); p0t,_=p0_from_x(xt,B); hist=[] for _ in range(epochs): out=net(x).reshape(-1,N,N) if idea: p=p0+curl2(out); loss=.5*torch.einsum('sxyi,ij,sxyj->sxy',p,EINV,p).mean() else: g=grad(out); rho=-div(p0) loss=(.5*torch.einsum('sxyi,ij,sxyj->sxy',g,EPS,g)-rho*out).mean() opt.zero_grad(); loss.backward(); opt.step(); hist.append(float(loss)) with torch.no_grad(): out=net(xt).reshape(-1,N,N) pred=reconstruct_phi(p0t+curl2(out),B) if idea else out metric=float(((pred-yt.reshape(-1,N,N))**2).mean()) dual_res=float(div(curl2(net(x).reshape(-1,N,N))).abs().max()) primal_res=float((div(grad(net(x).reshape(-1,N,N)))+div(p0)).abs().mean()) return (metric, dual_res if idea else primal_res) if return_sig else metric def make_fn(cfg,idea): return lambda seed: train_one(seed,cfg['lr'],idea) def main(): grid=[{'lr':1e-3},{'lr':3e-3},{'lr':1e-2}] base=sweep_baseline(lambda c:make_fn(c,False),grid) idea=evaluate(make_fn(base['best_cfg'],True)); idea['selected_cfg']=base['best_cfg'] for c in grid: if c==base['best_cfg']: continue r=evaluate(make_fn(c,True)); if r['mean']