import sys, json, random, math from pathlib import Path import numpy as np import torch import torch.nn as nn import torch.nn.functional as F sys.path.insert(0, '/home/maxwelhelp/all/math2nn') from bench import get_dataset, make_model, train_model, evaluate, sweep_baseline, make_report SEEDS=(0,1,2,3,4,5,6,7) LR_GRID=[1e-3,3e-3,6e-3] EPOCHS=12 BATCH=128 ALPHA=0.02 RADIUS=0.08 DELTA=1e-3 def seed_all(s): random.seed(s); np.random.seed(s); torch.manual_seed(s) if torch.cuda.is_available(): torch.cuda.manual_seed_all(s) def cell(net, h, x): """One GRU step, using the exact parameterization of bench.models.rnn_small.""" r=net.rnn wi, wh = r.weight_ih_l0, r.weight_hh_l0 bi = r.bias_ih_l0; bh = r.bias_hh_l0 gi=F.linear(x,wi,bi); gh=F.linear(h,wh,bh) ir, iz, inn = gi.chunk(3,-1); hr, hz, hnn=gh.chunk(3,-1) rr=torch.sigmoid(ir+hr); zz=torch.sigmoid(iz+hz) nnv=torch.tanh(inn + rr*hnn) return (1-zz)*nnv + zz*h def nominal_and_penalty(net, x): """Propagate R_k=A_k R_k and penalize one-step nonlinear defect.""" b=x.shape[0]; dev=x.device; hidden=net.rnn.hidden_size x=x.view(b,-1,3) h=torch.zeros(b,hidden,device=dev) # one fixed random direction per hidden dimension, normalized per sample d=torch.randn(b,hidden,device=dev) d=d/(d.norm(dim=1,keepdim=True)+1e-8) R=torch.ones(b,1,device=dev) total=0.0; maxv=0.0 # scalar gamma with direction d; U=0, matching initial-state uncertainty for k in range(x.shape[1]): u=x[:,k,:] hn=cell(net,h,u) # directional Jacobian-vector estimate at nominal state; detached for stable monitor eps=DELTA ap=(cell(net,h+eps*d,u)-cell(net,h-eps*d,u))/(2*eps) ap=ap.detach() # R is scalar amplitude multiplying d; affine predicted next state pred=hn + R*ap pert=cell(net,h + RADIUS*R*d,u) defect=(pert-pred).pow(2).mean(dim=1) total=total+defect.mean() maxv=max(maxv,float(defect.sqrt().max().detach().cpu())) # propagate direction with local Jacobian-vector; keep nominal rollout graph R=(ap*d).norm(dim=1,keepdim=True).detach() + 1e-6 h=hn return total/x.shape[1], maxv def idea_train(seed, lr): seed_all(seed); ds=get_dataset('dynamics',seed,n_train=400,n_test=200) net=make_model('rnn_small',ds['input_shape'],ds['out_dim']) device='cuda' if torch.cuda.is_available() else 'cpu' try: net=net.to(device); xtr,ytr=ds['xtr'].to(device),ds['ytr'].to(device) opt=torch.optim.Adam(net.parameters(),lr=lr) lossf=nn.MSELoss() for _ in range(EPOCHS): net.train(); perm=torch.randperm(len(xtr),device=device) for i in range(0,len(xtr),BATCH): ix=perm[i:i+BATCH]; xb=xtr[ix]; yb=ytr[ix] pred=net(xb); task=lossf(pred,yb) reach,_=nominal_and_penalty(net,xb) loss=task+ALPHA*reach opt.zero_grad(); loss.backward(); torch.nn.utils.clip_grad_norm_(net.parameters(),5.0); opt.step() net.eval() with torch.no_grad(): metric=float(((net(ds['xte'].to(device))-ds['yte'].to(device))**2).mean().cpu()) return metric except RuntimeError: # CPU retry is deliberately independent and deterministic seed_all(seed); ds=get_dataset('dynamics',seed,n_train=400,n_test=200) net=make_model('rnn_small',ds['input_shape'],ds['out_dim']).cpu(); xtr,ytr=ds['xtr'],ds['ytr'] opt=torch.optim.Adam(net.parameters(),lr=lr) for _ in range(EPOCHS): perm=torch.randperm(len(xtr)) for i in range(0,len(xtr),BATCH): ix=perm[i:i+BATCH]; task=((net(xtr[ix])-ytr[ix])**2).mean(); reach,_=nominal_and_penalty(net,xtr[ix]); loss=task+ALPHA*reach opt.zero_grad(); loss.backward(); opt.step() with torch.no_grad(): return float(((net(ds['xte'])-ds['yte'])**2).mean()) def baseline_fn(cfg): def run(seed): seed_all(seed); ds=get_dataset('dynamics',seed,n_train=400,n_test=200) net=make_model('rnn_small',ds['input_shape'],ds['out_dim']) _,m,_=train_model(net,ds,epochs=EPOCHS,lr=cfg['lr'],batch=BATCH,log=lambda *a:None) return m return run def signature(seed, lr): # Re-test v(r) on a trained model, not on the toy analytic recurrence. seed_all(seed); ds=get_dataset('dynamics',seed,n_train=400,n_test=200) net=make_model('rnn_small',ds['input_shape'],ds['out_dim']); device='cuda' if torch.cuda.is_available() else 'cpu' try: net=net.to(device) except Exception: net=net.cpu(); device='cpu' # short canonical training for signature x,y=ds['xtr'][:64].to(device),ds['ytr'][:64].to(device); opt=torch.optim.Adam(net.parameters(),lr=lr) for _ in range(EPOCHS): task=((net(x)-y)**2).mean(); opt.zero_grad(); task.backward(); opt.step() net.eval(); xx=ds['xte'][:16].to(device); b=xx.shape[0]; h=torch.zeros(b,64,device=device); d=torch.ones_like(h); d=d/d.norm(dim=1,keepdim=True); R=torch.ones(b,1,device=device) vals=[] with torch.no_grad(): for rad in [0.02,0.04,0.08]: h=torch.zeros(b,64,device=device); R=torch.ones(b,1,device=device); vmax=0. for k in range(xx.shape[1] if xx.dim()>2 else 8): u=xx[:,k,:] if xx.dim()>2 else xx[:,3*k:3*k+3]; hn=cell(net,h,u); eps=DELTA ap=(cell(net,h+eps*d,u)-cell(net,h-eps*d,u))/(2*eps); pert=cell(net,h+rad*R*d,u); vmax=max(vmax,float((pert-(hn+rad*R*ap)).norm(dim=1).max().cpu())); R=(ap*d).norm(dim=1,keepdim=True)+1e-6; h=hn vals.append(vmax) slope=float(np.polyfit(np.log([.02,.04,.08]),np.log(np.maximum(vals,1e-12)),1)[0]) return {'radii':[.02,.04,.08],'observed_violation':vals,'observed_log_slope':slope,'predicted_slope':2.0,'confirmed':bool(1.5