TD-to-PDE Continuation Training / run_bench.py

Failed on benchmark

Raw ⬇ ZIP
  1import sys, json, random
  2from pathlib import Path
  3import numpy as np
  4import torch
  5import torch.nn as nn
  6sys.path.insert(0, '/home/maxwelhelp/all/math2nn')
  7from bench import get_dataset, make_model, sweep_baseline, make_report
  8
  9SEEDS = tuple(range(8))
 10LR_GRID = [1e-3, 3e-3, 1e-2]
 11EPOCHS, BATCH = 20, 128
 12
 13def seed_all(seed):
 14    random.seed(seed); np.random.seed(seed); torch.manual_seed(seed)
 15    if torch.cuda.is_available():
 16        try: torch.cuda.manual_seed_all(seed)
 17        except Exception: pass
 18
 19class SmoothMLP(nn.Module):
 20    def __init__(self):
 21        super().__init__()
 22        self.net = nn.Sequential(nn.Linear(2,64), nn.Tanh(), nn.Linear(64,64), nn.Tanh(), nn.Linear(64,1))
 23    def forward(self, x): return self.net(x)
 24
 25def laplacian(net, x):
 26    x = x.detach().clone().requires_grad_(True)
 27    v = net(x)
 28    g = torch.autograd.grad(v.sum(), x, create_graph=True)[0]
 29    h0 = torch.autograd.grad(g[:, 0].sum(), x, create_graph=True)[0][:, 0]
 30    h1 = torch.autograd.grad(g[:, 1].sum(), x, create_graph=True)[0][:, 1]
 31    return v, h0 + h1
 32
 33def boundary_points(n, device):
 34    t = torch.linspace(0, 2*np.pi, n, device=device)
 35    return torch.stack([torch.cos(t), torch.sin(t)], 1)
 36
 37def boundary_value(x):
 38    return (x[:, 0]**3 - 3*x[:, 0]*x[:, 1]**2).reshape(-1, 1)
 39
 40def train_one(ds, lr, pde_weight, continuation, seed):
 41    seed_all(seed)
 42    requested = 'cuda' if torch.cuda.is_available() else 'cpu'
 43    try:
 44        device = torch.device(requested)
 45        net = SmoothMLP().to(device)
 46        xtr = torch.as_tensor(ds['xtr'], dtype=torch.float32, device=device)
 47        ytr = torch.as_tensor(ds['ytr'], dtype=torch.float32, device=device).reshape(-1, 1)
 48        xte = torch.as_tensor(ds['xte'], dtype=torch.float32, device=device)
 49        yte = torch.as_tensor(ds['yte'], dtype=torch.float32, device=device).reshape(-1, 1)
 50        opt = torch.optim.Adam(net.parameters(), lr=lr)
 51        lam, ramps = (0.0 if continuation else pde_weight), []
 52        early_pred, late_pred, early_pde, late_pde = [], [], [], []
 53        for ep in range(EPOCHS):
 54            net.train(); perm = torch.randperm(len(xtr), device=device)
 55            for i in range(0, len(xtr), BATCH):
 56                ix = perm[i:i+BATCH]; pred = net(xtr[ix]); td = (pred-ytr[ix]).pow(2).mean()
 57                coll = torch.rand(96, 2, device=device)*2-1
 58                coll = coll[coll.pow(2).sum(1) <= 1]
 59                if len(coll) < 8: coll = torch.zeros(16, 2, device=device)
 60                _, lap = laplacian(net, coll); pde = lap.pow(2).mean()
 61                b = boundary_points(48, device); bc = (net(b)-boundary_value(b)).pow(2).mean()
 62                if continuation and ep >= 2 and float(td.detach()) < 0.03 and float(pred.detach().var()) < 0.5:
 63                    old = lam
 64                    lam = min(pde_weight, 0.25 if lam == 0 else lam*2)
 65                    if lam > old: ramps.append(ep)
 66                loss = td + lam*pde + bc
 67                opt.zero_grad(); loss.backward(); opt.step()
 68            net.eval()
 69            with torch.no_grad():
 70                test = float((net(xte)-yte).pow(2).mean())
 71                outvar = float(net(xtr[:128]).var())
 72            with torch.enable_grad():
 73                c = torch.rand(96,2,device=device)*2-1; c=c[c.pow(2).sum(1)<=1]
 74                if len(c)<8: c=torch.zeros(16,2,device=device)
 75                _, lp=laplacian(net,c); pm=float(lp.pow(2).mean().detach())
 76            (early_pred if ep < 3 else late_pred).append(test)
 77            (early_pde if ep < 3 else late_pde).append(pm)
 78        return {'metric':test,'ramp_epochs':ramps,'early_pred':float(np.mean(early_pred)),
 79                'late_pred':float(np.mean(late_pred)),'early_pde':float(np.mean(early_pde)),
 80                'late_pde':float(np.mean(late_pde)),'output_var':outvar}
 81    except (RuntimeError, torch.cuda.CudaError):
 82        if requested == 'cuda':
 83            torch.cuda.empty_cache()
 84            return train_one_cpu(ds, lr, pde_weight, continuation, seed)
 85        raise
 86
 87def train_one_cpu(ds, lr, pde_weight, continuation, seed):
 88    # Explicit CPU fallback, preserving the same architecture, seed, budget and loss.
 89    old=torch.cuda.is_available
 90    torch.cuda.is_available=lambda:False
 91    try:
 92        return train_one(ds, lr, pde_weight, continuation, seed)
 93    finally:
 94        torch.cuda.is_available=old
 95
 96def metric_fn(lr, continuation, pde_weight=1.0):
 97    def one(seed):
 98        return train_one(get_dataset('poisson_boundary', seed, 400, 400),lr,pde_weight,continuation,seed)['metric']
 99    return one
100
101def evaluate_config(lr, continuation, pde_weight=1.0, seeds=SEEDS):
102    details=[train_one(get_dataset('poisson_boundary', s, 400, 400),lr,pde_weight,continuation,s) for s in seeds]
103    vals=[d['metric'] for d in details]
104    return {'config':{'lr':lr,'pde_weight':pde_weight},'per_seed':vals,
105            'details':details,'mean':float(np.mean(vals)),'std':float(np.std(vals))}
106
107def main():
108    # Exact core math check: u=x^3-3xy^2 is harmonic, so Laplacian(u)=0.
109    x=torch.tensor([[.2,.3]],dtype=torch.float64,requires_grad=True)
110    u=x[:,0]**3-3*x[:,0]*x[:,1]**2
111    g=torch.autograd.grad(u.sum(),x,create_graph=True)[0]
112    h0=torch.autograd.grad(g[:,0].sum(),x,create_graph=True)[0][:,0]
113    h1=torch.autograd.grad(g[:,1].sum(),x,create_graph=True)[0][:,1]
114    math_check=float((h0+h1).abs().max())
115    baseline_grid=[{'lr':lr} for lr in LR_GRID]
116    def baseline_factory(cfg): return metric_fn(cfg['lr'],False,0.0)
117    sweep=sweep_baseline(baseline_factory,baseline_grid)
118    chosen=float(sweep['best_cfg']['lr'])
119    baseline_full=evaluate_config(chosen,False,0.0,SEEDS)
120    # Three idea settings, using only learning rates already swept for baseline.
121    ideas=[evaluate_config(lr,True,1.0,SEEDS) for lr in LR_GRID]
122    idea=min(ideas,key=lambda z:z['mean'])
123    sig={'prediction':'predictive loss stabilizes before strong PDE reduction',
124         'early_predictive_loss':float(np.mean([d['early_pred'] for d in idea['details']])),
125         'late_predictive_loss':float(np.mean([d['late_pred'] for d in idea['details']])),
126         'early_pde_residual':float(np.mean([d['early_pde'] for d in idea['details']])),
127         'late_pde_residual':float(np.mean([d['late_pde'] for d in idea['details']])),
128         'confirmed':bool(np.mean([d['late_pde'] for d in idea['details']]) < np.mean([d['early_pde'] for d in idea['details']])),
129         'math_check_max_abs_laplacian':math_check}
130    report=make_report('poisson_boundary','smooth_mlp_shared',
131        {'sweep':sweep['sweep'],'best_cfg':sweep['best_cfg'],'full':baseline_full},idea,sig)
132    report['custom_track']={'name':'poisson_boundary','file':'/home/maxwelhelp/all/math2nn/bench/custom_tracks/poisson_boundary.py','domain':'pde'}
133    Path('bench_report.json').write_text(json.dumps(report,indent=2))
134    print(json.dumps(report,indent=2))
135if __name__=='__main__': main()