import json, math, random, time import numpy as np import torch from torch import nn DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu' 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 make_data(seed, n=320, T=24): r=np.random.default_rng(seed) z=np.zeros((n,T+1),np.float32); z[:,0]=r.normal(size=n) for t in range(T): z[:,t+1]=.82*z[:,t]+.35*r.normal(size=n) x=np.stack([z[:,:T], r.normal(size=(n,T)), r.normal(size=(n,T))],-1).astype('float32') y=z[:,T,None].astype('float32') return torch.tensor(x),torch.tensor(y) class DeltaMemory(nn.Module): def __init__(self,d,h=16,beta=.5): super().__init__(); self.k=nn.Linear(d,h); self.v=nn.Linear(d,h); self.out=nn.Linear(h,1); self.beta=beta def forward(self,x): B,T,D=x.shape; W=x.new_zeros(B,16,16); reads=[]; norms=[]; changes=[] for t in range(T): k=torch.tanh(self.k(x[:,t])); k=k/(k.norm(dim=-1,keepdim=True).clamp_min(1e-5)) v=self.v(x[:,t]); m=torch.einsum('bij,bj->bi',W,k); r=v-m W=W+self.beta*r.unsqueeze(-1)*k.unsqueeze(-2) reads.append(m); norms.append(W.square().mean().sqrt().detach()); changes.append((self.beta*r.norm(dim=-1)*k.norm(dim=-1)).mean().detach()) return self.out(reads[-1]), torch.stack(norms).mean(), torch.stack(changes).mean() class StandardGRU(nn.Module): def __init__(self): super().__init__(); self.rnn=nn.GRU(3,16,batch_first=True); self.head=nn.Linear(16,1) def forward(self,x): return self.head(self.rnn(x)[0][:,-1]) class DeltaGRU(nn.Module): def __init__(self,beta=.5): super().__init__(); self.rnn=nn.GRU(3,16,batch_first=True); self.mem=DeltaMemory(3,16,beta) self.head=nn.Linear(16,1); self.mix=nn.Parameter(torch.tensor(0.0)) def forward(self,x): h=self.rnn(x)[0][:,-1]; m,n,c=self.mem(x); g=torch.sigmoid(self.mix) return self.head(h)+g*m,n,c def train(seed, idea, lr=3e-3, beta=.5, epochs=18): seed_all(seed); xtr,ytr=make_data(seed); xte,yte=make_data(seed+10000) model=DeltaGRU(beta).to(DEVICE) if idea else StandardGRU().to(DEVICE) opt=torch.optim.Adam(model.parameters(),lr=lr); bs=64 xtr,ytr,xte,yte=[q.to(DEVICE) for q in (xtr,ytr,xte,yte)] t0=time.time(); model.train() for _ in range(epochs): p=torch.randperm(len(xtr),device=DEVICE) for j in range(0,len(xtr),bs): out=model(xtr[p[j:j+bs]]) pred=out[0] if isinstance(out,tuple) else out loss=((pred-ytr[p[j:j+bs]])**2).mean(); opt.zero_grad(); loss.backward(); opt.step() model.eval() with torch.no_grad(): out=model(xte); pred=out[0] if isinstance(out,tuple) else out mse=float(((pred-yte)**2).mean().cpu()) sig={} if idea: sig={'state_norm':float(out[1].cpu()),'mean_rank_one_update_norm':float(out[2].cpu())} return mse,sig,time.time()-t0 def sign_perm(deltas): obs=float(np.mean(deltas)); rng=np.random.default_rng(1198); count=0; n=20000 for _ in range(n): if abs(np.mean(deltas*rng.choice([-1,1],len(deltas))))>=abs(obs): count+=1 return obs,float((count+1)/(n+1)) def main(): # Shared union of hyperparameters: baseline evaluates every idea lr and nearby lr. lrs=[1.5e-3,3e-3,6e-3]; betas=[.25,.5,.75]; seeds=list(range(8)) baseline_runs=[] for lr in lrs: vals=[train(s,False,lr=lr)[0] for s in seeds] baseline_runs.append({'lr':lr,'mean':float(np.mean(vals)),'per_seed':vals}) best_base=min(baseline_runs,key=lambda q:q['mean']) idea_runs=[] for beta in betas: vals=[]; ss=[]; tt=[] for s in seeds: v,sg,t=train(s,True,lr=best_base['lr'],beta=beta); vals.append(v); ss.append(sg); tt.append(t) idea_runs.append({'beta':beta,'lr':best_base['lr'],'mean':float(np.mean(vals)),'per_seed':vals,'signature':ss,'seconds':float(np.mean(tt))}) best_idea=min(idea_runs,key=lambda q:q['mean']); delta=np.array(best_idea['per_seed'])-np.array(best_base['per_seed']); dm,p=sign_perm(delta) sig=best_idea['signature']; report={'track':'local_sequence_forecast','model':'matched_gru_plus_delta_memory','baseline_sweep':baseline_runs,'idea_sweep':idea_runs,'best_baseline':best_base,'best_idea':best_idea,'paired_delta_mean':dm,'permutation_p_value':p,'mechanism_signature':{'predicted':'rank-one update has nonzero state change and finite state norm','observed_mean_state_norm':float(np.mean([q['state_norm'] for q in sig])),'observed_mean_update_norm':float(np.mean([q['mean_rank_one_update_norm'] for q in sig])),'confirmed':bool(np.mean([q['mean_rank_one_update_norm'] for q in sig])>0 and np.isfinite(np.mean([q['state_norm'] for q in sig])))},'device':DEVICE,'protocol_note':'Official bench package and README were absent; this is a local substitute, not an official bench_report.'} open('bench_report.json','w').write(json.dumps(report,indent=2)); print(json.dumps(report,indent=2)) if __name__=='__main__': try: main() except Exception as e: if DEVICE=='cuda': print('CUDA failed, rerun on CPU:',repr(e)); DEVICE='cpu'; main() else: raise