import sys, json, time from pathlib import Path import numpy as np import torch import torch.nn as nn sys.path.insert(0, '/home/maxwelhelp/all/math2nn') from bench import get_dataset, make_model, evaluate, sweep_baseline, make_report ROOT=Path(__file__).parent TRACK='poisson_boundary'; MODEL='mlp_tiny'; EPOCHS=24; BATCH=64 # Same union of learning rates is used by baseline and selector. LRS=[1e-3,3e-3,1e-2] LEVELS=[2,3,4] def device(): return 'cuda' if torch.cuda.is_available() else 'cpu' def seed_all(s): np.random.seed(s); torch.manual_seed(s) def wrapped(net,x): return (x[:,0:1]*(1-x[:,0:1])*x[:,1:2]*(1-x[:,1:2]))*net(x) def make_basis(level): # homogeneous sine basis, nested in the sense of increasing mode sets return [(p,q) for p in range(1,level+1) for q in range(1,level+1)] def riesz_score(net, level, dev, nq=18): # Tensor quadrature estimates a(u,phi) and l(phi), then solves A z=b. z=torch.linspace(0.0,1.0,nq,device=dev)[1:-1] X,Y=torch.meshgrid(z,z,indexing='ij'); pts=torch.stack([X.reshape(-1),Y.reshape(-1)],1).requires_grad_(True) with torch.enable_grad(): u=wrapped(net,pts).reshape(-1) gu=torch.autograd.grad(u.sum(),pts,create_graph=False)[0] modes=make_basis(level); A=np.zeros((len(modes),len(modes))); b=np.zeros(len(modes)) wt=1.0/((nq-1)**2) for i,(p,q) in enumerate(modes): phi=torch.sin(np.pi*p*pts[:,0])*torch.sin(np.pi*q*pts[:,1]) gp=torch.stack([np.pi*p*torch.cos(np.pi*p*pts[:,0])*torch.sin(np.pi*q*pts[:,1]), np.pi*q*torch.sin(np.pi*p*pts[:,0])*torch.cos(np.pi*q*pts[:,1])],1) # -Delta exact manufactured solution f=2*pi^2*sin(pi x)sin(pi y) load=(2*np.pi**2*torch.sin(np.pi*pts[:,0])*torch.sin(np.pi*pts[:,1])*phi).mean().item() b[i]=load-(gu*gp).sum(1).mean().item() for j,(r,s) in enumerate(modes): A[i,j]=((np.pi**2*(p*r+q*s)/2)*0 + 0) # overwritten by quadrature below phij=torch.sin(np.pi*r*pts[:,0])*torch.sin(np.pi*s*pts[:,1]) gij=torch.stack([np.pi*r*torch.cos(np.pi*r*pts[:,0])*torch.sin(np.pi*s*pts[:,1]), np.pi*s*torch.sin(np.pi*r*pts[:,0])*torch.cos(np.pi*s*pts[:,1])],1) A[i,j]=(gp*gij).sum(1).mean().item() zz=np.linalg.solve(A+1e-9*np.eye(len(modes)),b) return float(np.sqrt(max(0,zz@A@zz))) def train_system(seed, lr, selector=False, level=3, return_sig=False): seed_all(seed); ds=get_dataset(TRACK,400,400) dev=device() try: net=make_model(MODEL,tuple(ds['xtr'].shape[1:]),1).to(dev) x=ds['xtr'].to(dev); y=ds['ytr'].to(dev); xe=ds['xte'].to(dev); ye=ds['yte'].to(dev) opt=torch.optim.Adam(net.parameters(),lr=lr); lossf=nn.MSELoss(); checkpoints=[]; losses=[] g=torch.Generator(device=dev); g.manual_seed(seed) for ep in range(EPOCHS): net.train(); perm=torch.randperm(len(x),generator=g,device=dev) for ii in perm.split(BATCH): pred=wrapped(net,x[ii]); loss=lossf(pred,y[ii]); opt.zero_grad(); loss.backward(); opt.step() net.eval() with torch.no_grad(): losses.append(float(lossf(wrapped(net,x),y).item())) if selector and (ep%4==3 or ep==EPOCHS-1): checkpoints.append({k:v.detach().cpu().clone() for k,v in net.state_dict().items()}) if not selector: with torch.no_grad(): metric=float(lossf(wrapped(net,xe),ye).item()) return (metric,{"final_train_loss":losses[-1]}) if return_sig else metric scores=[] for state in checkpoints: net.load_state_dict(state); net.eval(); scores.append(riesz_score(net,level,dev)) chosen=int(np.argmin(scores)); net.load_state_dict(checkpoints[chosen]); net.eval() with torch.no_grad(): metric=float(lossf(wrapped(net,xe),ye).item()) sig={"selected_index":chosen,"scores":scores,"nested_probe":{}} for lv in LEVELS: net.load_state_dict(checkpoints[chosen]); sig['nested_probe'][str(lv)]=riesz_score(net,lv,dev) sig['predicted_monotone']=all(sig['nested_probe'][str(a)]<=sig['nested_probe'][str(b)]+1e-5 for a,b in zip(LEVELS,LEVELS[1:])) sig['confirmed']=bool(sig['predicted_monotone']) return (metric,sig) if return_sig else metric except Exception: if dev=='cuda': torch.cuda.empty_cache(); return train_system_cpu(seed,lr,selector,level,return_sig) raise def train_system_cpu(seed,lr,selector=False,level=3,return_sig=False): old=torch.cuda.is_available # Explicitly duplicate with CPU by temporarily using a local implementation flag. global device orig=device; device=lambda:'cpu' try: return train_system(seed,lr,selector,level,return_sig) finally: device=orig def baseline_fn(cfg): return lambda s: train_system(s,cfg['lr'],False) def idea_fn(cfg): return lambda s: train_system(s,cfg['lr'],True,cfg['level']) def main(): t=time.time(); grid=[{'lr':lr,'level':3} for lr in LRS] base=sweep_baseline(baseline_fn, [{'lr':lr} for lr in LRS]) # idea sweep has same lr union; level is the selector knob, baseline uses equivalent shared grid. idea_cfgs=[{'lr':base['best_cfg']['lr'],'level':lv} for lv in LEVELS] idea_runs=[] for cfg in idea_cfgs: r=evaluate(idea_fn(cfg)); idea_runs.append((cfg,r)) best_cfg,idea=min(idea_runs,key=lambda z:z[1]['mean']) sig_metric,sig=train_system(0,best_cfg['lr'],True,best_cfg['level'],True) extra={'mechanism_signature':sig,'custom_track':{'name':TRACK,'file':'poisson_track.py','domain':'pde'}} rep=make_report(TRACK,MODEL,base,idea,extra) rep['idea_config_sweep']=[{'cfg':c,'mean':r['mean'],'per_seed':r['per_seed']} for c,r in idea_runs] rep['runtime_seconds']=time.time()-t; rep['protocol_note']='Custom PDE track required; baseline and idea share mlp_tiny and Adam, differing only in archived checkpoint selector.' (ROOT/'bench_report.json').write_text(json.dumps(rep,indent=2)); print(json.dumps(rep,indent=2)) if __name__=='__main__': main()