Rankine–Hugoniot Front Tokens / bench_front_tokens.py

Failed on benchmark

Raw ⬇ ZIP
  1import sys, json, random
  2from pathlib import Path
  3import numpy as np
  4import torch
  5from torch import nn
  6sys.path.insert(0, '/home/maxwelhelp/all/math2nn')
  7from bench import train_model, evaluate, sweep_baseline, make_report
  8from bench.custom_tracks import burgers_periodic_split as track
  9
 10META = {'name':'burgers_front_tokens_bench','domain':'pde','description':'Burgers one-step regression with analytical RH front tokens versus matched CNN.'}
 11
 12# Shared base architecture: both systems have the same hidden convolutional encoder.
 13class Encoder(nn.Module):
 14    def __init__(self, n=32, width=32):
 15        super().__init__(); self.net=nn.Sequential(nn.Conv1d(1,width,5,padding=2),nn.ReLU(),nn.Conv1d(width,width,5,padding=2),nn.ReLU(),nn.Conv1d(width,width,3,padding=1),nn.ReLU())
 16    def forward(self,x): return self.net(x)
 17
 18class Baseline(nn.Module):
 19    def __init__(self,n=32):
 20        super().__init__(); self.enc=Encoder(n); self.head=nn.Conv1d(32,1,1)
 21    def forward(self,x): return self.head(self.enc(x))
 22
 23def front_info(x, threshold=.18):
 24    # Differentiable-free detector is intentionally input preprocessing; traces are noisy local estimates.
 25    u=x[:,0,:]; d=(u[:,:,None] if False else None)
 26    jumps=(u[:,1:]-u[:,:-1]).abs(); B,N=u.shape
 27    mask=jumps > threshold
 28    # one token: strongest detected edge (periodic edge included)
 29    vals, idx=jumps.max(dim=1); idx=idx.clamp(1,N-2)
 30    grid=torch.arange(N,device=x.device).float()[None,:]
 31    pos=(idx.float()+.5)/(N-1)
 32    il=idx-1; ir=idx+1
 33    ul=u.gather(1,il[:,None]).squeeze(1); ur=u.gather(1,ir[:,None]).squeeze(1)
 34    active=(vals>threshold).float()
 35    return pos,ul,ur,active
 36
 37class FrontTokens(nn.Module):
 38    def __init__(self,n=32):
 39        super().__init__(); self.enc=Encoder(n)
 40        # residual head uses same parameter count scale, while traces are estimated from local observed grid values
 41        self.res=nn.Conv1d(32,1,1)
 42        self.trace=nn.Sequential(nn.Linear(32,16),nn.Tanh(),nn.Linear(16,2))
 43        self.n=n
 44    def forward(self,x):
 45        h=self.enc(x); residual=self.res(h)
 46        pos,ul,ur,active=front_info(x)
 47        # RH speed for Burgers: (F(ur)-F(ul))/(ur-ul) = (ul+ur)/2.
 48        speed=.5*(ul+ur)
 49        newpos=(pos + .08*speed).remainder(1.0)
 50        # Render a periodic step from estimated traces; trace MLP receives local encoded context.
 51        b=torch.arange(x.shape[0],device=x.device)
 52        ctx=h[b,:,torch.clamp((pos*(self.n-1)).long(),0,self.n-1)]
 53        delta=self.trace(ctx); aul=ul+0.15*delta[:,0]; aur=ur+0.15*delta[:,1]
 54        xx=torch.arange(self.n,device=x.device).float()[None,:]/(self.n-1)
 55        # smooth sigmoid rendering keeps gradients through positions and traces
 56        dist=((xx-newpos[:,None]+.5)%1.0)-.5
 57        front=aur[:,None]+(aul-aur)[:,None]*torch.sigmoid(-dist/0.035)
 58        return residual + active[:,None,None]*front[:,None,:], (pos,newpos,ul,ur,speed,active)
 59
 60def idea_forward(net,x): return net(x)[0]
 61def ds(seed):
 62    d=track.get_dataset(seed, n_train=400, n_test=160)
 63    for k in ('xtr','ytr','xte','yte'):
 64        d[k]=torch.as_tensor(d[k], dtype=torch.float32)
 65    return d
 66def seed_all(s): random.seed(s); np.random.seed(s); torch.manual_seed(s)
 67def train_one(kind,cfg,seed,return_net=False):
 68    seed_all(seed); d=ds(seed); net=Baseline() if kind=='base' else FrontTokens()
 69    if kind=='base': out=train_model(net,d,epochs=cfg['epochs'],lr=cfg['lr'],batch=128,log=lambda *_:None)
 70    else:
 71        # Official trainer expects tensor output; wrap the idea system without changing its learned system.
 72        class Wrap(nn.Module):
 73            def __init__(self,m): super().__init__(); self.m=m
 74            def forward(self,z): return self.m(z)[0]
 75        out=train_model(Wrap(net),d,epochs=cfg['epochs'],lr=cfg['lr'],batch=128,log=lambda *_:None)
 76    if return_net: return out,out[0],d
 77    return out[1] if out[1] is not None else float('nan')
 78
 79def signature():
 80    pred=[]; obs=[]
 81    try:
 82        for s in range(8):
 83            out,net,d=train_one('idea',{'lr':.003,'epochs':15},s,True)
 84            if out[0] is None: continue
 85            net.cpu(); net.eval()
 86            x=torch.as_tensor(d['xte'], dtype=torch.float32); y=torch.as_tensor(d['yte'], dtype=torch.float32)
 87            with torch.no_grad(): z,info=net.m(x); pos,np_,ul,ur,sp,act=info
 88            def cross(a):
 89                a=a[:,0,:] if a.ndim==3 else a
 90                q=(a[:,:-1]>=0.5)&(a[:,1:]<0.5); ix=q.float().argmax(1); return (ix.float()+.5)/31
 91            target=cross(y); observed=target-cross(x); m=act>0
 92            if m.any(): pred += ((np_-pos)[m]).tolist(); obs += observed[m].tolist()
 93    except Exception as e:
 94        return {'predicted_mean_speed_displacement':float('nan'),'observed_mean_displacement':float('nan'),'relative_error':float('nan'),'confirmed':False,'error':str(e)[:180]}
 95    if not pred: return {'predicted_mean_speed_displacement':float('nan'),'observed_mean_displacement':float('nan'),'relative_error':float('nan'),'confirmed':False}
 96    a,b=float(np.mean(pred)),float(np.mean(obs)); rel=abs(a-b)/(abs(b)+1e-6)
 97    return {'predicted_mean_speed_displacement':a,'observed_mean_displacement':b,'relative_error':rel,'confirmed':bool(rel<.15)}
 98
 99def main():
100    # Union-parity grid: all idea learning rates are also baseline-evaluated.
101    grid=[{'lr':lr,'epochs':ep} for lr in (.0015,.003,.006) for ep in (15,)]
102    base=sweep_baseline(lambda c: lambda s:train_one('base',c,s),grid)
103    idea_grid=grid
104    idea_tuning=[]
105    for cfg in idea_grid:
106        r=evaluate(lambda s,c=cfg:train_one('idea',c,s), seeds=(0,1,2,3))
107        idea_tuning.append({'cfg':cfg,'mean':r['mean']})
108    idea_cfg=min(idea_tuning,key=lambda z:z['mean'])['cfg']
109    idea=evaluate(lambda s:train_one('idea',idea_cfg,s))
110    idea['selected_cfg']=idea_cfg
111    extra={'idea_sweep':idea_tuning,'custom_track':{'name':'burgers_front_tokens_bench','file':'bench_front_tokens.py','domain':'pde'},'mechanism_signature':signature()}
112    rep=make_report('burgers_periodic_split','cnn_shared',base,idea,extra)
113    Path('bench_report.json').write_text(json.dumps(rep,indent=2)); print(json.dumps(rep,indent=2))
114if __name__=='__main__': main()