import json, random import numpy as np import torch import torch.nn as nn import sys sys.path.insert(0, '/home/maxwelhelp/all/math2nn') from bench import get_dataset, train_model, evaluate, sweep_baseline, make_report SEEDS = tuple(range(8)) GRID = [{'lr': 1e-3, 'epochs': 8}, {'lr': 3e-3, 'epochs': 8}, {'lr': 1e-2, 'epochs': 8}] class SpectralLinearRNN(nn.Module): def __init__(self, input_dim=3, out_dim=1, hidden=24): super().__init__() self.d = hidden self.raw = nn.Parameter(torch.randn(hidden, hidden) * .08) self.inp = nn.Linear(input_dim, hidden) self.head = nn.Linear(hidden, out_dim) def transition(self): # differentiable contraction normalization, standard baseline mechanism return self.raw / torch.clamp(torch.linalg.matrix_norm(self.raw, 2), min=1.0) def forward(self, x): x = x.reshape(x.shape[0], -1, 3) h = torch.zeros(x.shape[0], self.d, device=x.device, dtype=x.dtype) X = self.transition() for t in range(x.shape[1]): h = h @ X.T + self.inp(x[:, t]) return self.head(h) class SchafferLiftRNN(nn.Module): def __init__(self, input_dim=3, out_dim=1, hidden=24, K=8): super().__init__() self.d, self.K = hidden, K self.raw = nn.Parameter(torch.randn(hidden, hidden) * .08) self.inp = nn.Linear(input_dim, hidden) self.head = nn.Linear(hidden, out_dim) def transition(self): return self.raw / torch.clamp(torch.linalg.matrix_norm(self.raw, 2), min=1.0) def defect(self, X): I = torch.eye(self.d, device=X.device, dtype=X.dtype) A = (I - X.T @ X + (I - X.T @ X).T) / 2 w, U = torch.linalg.eigh(A) return (U * torch.sqrt(torch.clamp(w, min=1e-7))) @ U.T def forward(self, x): x = x.reshape(x.shape[0], -1, 3) B = x.shape[0]; dev=x.device; dtype=x.dtype h = torch.zeros(B, self.d, device=dev, dtype=dtype) q = torch.zeros(B, self.K, self.d, device=dev, dtype=dtype) X = self.transition(); D = self.defect(X) for t in range(x.shape[1]): # exact finite Schaeffer update, with external input entering active state h_new = h @ X.T + self.inp(x[:, t]) q_new = torch.cat([(h @ D.T).unsqueeze(1), q[:, :-1]], dim=1) h, q = h_new, q_new return self.head(h) def seed_all(seed): torch.manual_seed(seed); np.random.seed(seed); random.seed(seed) def run_one(kind, cfg, seed): seed_all(seed) ds = get_dataset('dynamics', seed, n_train=400, n_test=200) model = SpectralLinearRNN() if kind == 'baseline' else SchafferLiftRNN() net, metric, hist = train_model(model, ds, epochs=cfg['epochs'], lr=cfg['lr'], batch=128, log=lambda *_: None) if net is None: raise RuntimeError('training failed') return float(metric), net def train_value(kind, cfg, seed): return run_one(kind, cfg, seed)[0] def signature(): metric, net = run_one('idea', GRID[1], 0) net.eval(); X = net.transition().detach(); D = net.defect(X).detach() d=net.d; K=net.K; n=(K+1)*d Y=torch.zeros(n,n,device=X.device); Y[:d,:d]=X; Y[d:2*d,:d]=D for j in range(1,K): Y[(j+1)*d:(j+2)*d,j*d:(j+1)*d]=torch.eye(d,device=X.device) z=torch.randn(n,device=X.device); z[d:]=0; z0=z.clone(); z=Y@z one_err=float(abs(z.norm().item()**2-z0.norm().item()**2)/(z0.norm().item()**2+1e-12)) # Compare the trained transition with an uncorrected Euler-like repeated update. e=torch.randn(d,device=X.device); e0=e.clone(); z=torch.randn(n,device=X.device); z[d:]=0; z0=z.clone() for _ in range(min(4, K-1)): e=X@e; z=Y@z observed_lift=float(z.norm()/z0.norm()); observed_base=float(e.norm()/e0.norm()) return {'prediction':'Schaeffer active-subspace update preserves total energy before memory truncation; baseline contraction decays', 'observed_one_step_relative_energy_error':one_err, 'observed_lift_preboundary_norm_ratio':observed_lift, 'observed_baseline_same_horizon_norm_ratio':observed_base, 'trained_test_mse':metric, 'confirmed': bool(one_err < 1e-4 and abs(observed_lift-1) < 1e-3 and observed_base < .999)} def main(): # Canonical harness sweep on four seeds, with the same union of settings on both sides. def mk(cfg): return lambda seed: train_value('baseline', cfg, int(seed)) tuned = sweep_baseline(mk, GRID, seeds=(0,1,2,3)) base_sweep=[] for cfg in GRID: r=evaluate(mk(cfg), seeds=SEEDS); base_sweep.append({'cfg':cfg, **r}) best=min(base_sweep, key=lambda r:r['mean']) base={'best_cfg':best['cfg'], 'sweep':base_sweep, 'harness_tuning':tuned, 'full':evaluate(mk(best['cfg']), seeds=SEEDS)} idea_sweep=[] for cfg in GRID: r=evaluate(lambda seed, c=cfg: train_value('idea', c, int(seed)), seeds=SEEDS) idea_sweep.append({'cfg':cfg, **r}) ibest=min(idea_sweep, key=lambda r:r['mean']) idea={k:ibest[k] for k in ('cfg','per_seed','mean','std','n') if k in ibest} rep=make_report('dynamics','schaffer_lift_vs_spectral_linear_rnn',base,idea, {'idea_sweep':idea_sweep, 'mechanism_signature':signature()}) rep['custom_track']=None with open('bench_report.json','w') as f: json.dump(rep,f,indent=2) print(json.dumps(rep,indent=2)) if __name__ == '__main__': main()