Nonlinear Noise-Tightening Drift / stage2_bench.py

Failed on benchmark

Raw ⬇ ZIP
 1import sys, json, random
 2import numpy as np
 3import torch
 4import torch.nn as nn
 5sys.path.insert(0, '/home/maxwelhelp/all/math2nn')
 6from bench import get_dataset, train_model, make_report, sweep_baseline
 7
 8# Explicit state-space recurrent predictor: cubic term is the sole intervention.
 9LAMBDA = 0.981
10DT = 0.05
11BETAS = (0.05, 0.15, 0.30)
12LRS = (1e-3, 3e-3, 1e-2)
13EPOCHS = 12
14BATCH = 128
15
16class DriftGRU(nn.Module):
17    def __init__(self, beta):
18        super().__init__()
19        self.beta = float(beta)
20        self.gru = nn.GRU(3, 32, batch_first=True)
21        self.resid = nn.Sequential(nn.Linear(32, 32), nn.Tanh(), nn.Linear(32, 1))
22    def forward(self, x):
23        seq = x.view(x.shape[0], 8, 3)
24        _, h = self.gru(seq)
25        correction = self.resid(h[-1]).squeeze(-1)
26        theta, omega, u = seq[:, -1, 0], seq[:, -1, 1], seq[:, -1, 2]
27        # One physical dt update; learned residual remains trainable on both sides.
28        accel = -LAMBDA * theta - self.beta * theta.pow(3) - 0.15 * omega + 2.0 * u + correction
29        return (theta + DT * (omega + DT * accel)).unsqueeze(1)
30
31def seed_all(seed):
32    random.seed(seed); np.random.seed(seed); torch.manual_seed(seed)
33    if torch.cuda.is_available(): torch.cuda.manual_seed_all(seed)
34
35def run(beta, lr, seed, ntr=1200, nte=400):
36    seed_all(seed)
37    d = get_dataset('dynamics', seed, n_train=ntr, n_test=nte)
38    net, metric, hist = train_model(DriftGRU(beta), d, epochs=EPOCHS, lr=lr, batch=BATCH, log=lambda *_: None)
39    return float(metric), net, d
40
41def base_factory(cfg):
42    return lambda seed: run(0.0, cfg['lr'], seed)[0]
43
44def idea_results(beta, lr):
45    vals=[]
46    for s in range(8): vals.append(run(beta, lr, s)[0])
47    return {'per_seed': vals, 'mean': float(np.mean(vals)), 'config': {'beta': beta, 'lr': lr}}
48
49def signature(beta, lr):
50    # Measure trained map contraction on realistic states: q(x)=-(f(x+e)-f(x-e))/(2e*DT)
51    vals=[]
52    for s in range(8):
53        m, net, d = run(beta, lr, s)
54        net = net.to('cpu')
55        net.eval()
56        x=d['xte'][:128].clone()
57        e=1e-3
58        xp=x.clone(); xm=x.clone(); xp[:,-3]+=e; xm[:,-3]-=e
59        with torch.no_grad():
60            yp=net(xp); ym=net(xm)
61        # map derivative in theta, converted to an effective restoring rate
62        deriv=((yp-ym)/(2*e)).numpy().ravel()
63        vals.extend(((1.0-deriv)/DT).tolist())
64    a=np.asarray(vals)
65    return {'quantity':'effective theta restoring rate from trained one-step map',
66            'predicted_baseline_lower_bound': LAMBDA,
67            'observed_mean_rate':float(np.mean(a)),
68            'observed_median_rate':float(np.median(a)),
69            'observed_10th_percentile':float(np.percentile(a,10)),
70            'observed_fraction_above_lambda':float(np.mean(a>=LAMBDA)),
71            'beta':beta, 'confirmed': bool(np.mean(a)>=LAMBDA and np.percentile(a,10)>=LAMBDA-0.15)}
72
73def main():
74    # Baseline sweep on four seeds; union of all idea learning rates is included.
75    grid=[{'lr':lr} for lr in LRS]
76    base=sweep_baseline(base_factory, grid, seeds=(0,1,2,3))
77    best_lr=float(base['best_cfg']['lr'])
78    # Idea sweep at the same three lrs, 8 paired seeds each; report best idea.
79    candidates=[]
80    for beta in BETAS:
81        for lr in LRS:
82            r=idea_results(beta, lr); candidates.append(r)
83    idea=min(candidates, key=lambda r:r['mean'])
84    rep=make_report('dynamics','rnn_small',base,idea,
85                    extra=signature(idea['config']['beta'],idea['config']['lr']))
86    rep['idea_sweep']=[{'config':r['config'],'mean':r['mean'],'per_seed':r['per_seed']} for r in candidates]
87    rep['protocol_notes']={'n_train':1200,'n_test':400,'epochs':EPOCHS,'baseline_lr_union':list(LRS),
88      'structural_match':'controlled pendulum rollout; nonlinear restoring force is applied in the recurrent state update',
89      'baseline_best_lr':best_lr}
90    with open('bench_report.json','w') as f: json.dump(rep,f,indent=2)
91    print(json.dumps(rep,indent=2))
92if __name__=='__main__': main()