import sys, json, random from pathlib import Path import numpy as np import torch import torch.nn as nn 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': lr, 'coupling': c} for lr in (1e-3, 3e-3, 6e-3) for c in (0.0, 0.08, 0.16)] EPOCHS = 12 BATCH = 128 DELAY = 1 class CoupledGRU(nn.Module): """Matched two-branch recurrent system with delayed graph coupling. Baseline is the same system with coupling=0; idea adds predictor compensation. """ def __init__(self, out_dim, coupling=0.0, compensated=False, delay=1, hidden=32): super().__init__() self.coupling = float(coupling); self.compensated = bool(compensated); self.delay = int(delay) self.r0 = nn.GRUCell(3, hidden); self.r1 = nn.GRUCell(3, hidden) self.head = nn.Linear(2 * hidden, out_dim) def forward(self, x, return_states=False): seq = x.view(x.shape[0], -1, 3) h0 = seq.new_zeros(seq.shape[0], self.r0.hidden_size); h1 = h0.clone() hist0, hist1 = [], [] for t in range(seq.shape[1]): h0 = self.r0(seq[:, t], h0); h1 = self.r1(seq[:, t], h1) hist0.append(h0); hist1.append(h1) if self.coupling: q = max(0, t - self.delay); a0, a1 = hist0[q], hist1[q] if self.compensated and q > 0: a0 = a0 + self.delay * (a0 - hist0[q-1]) a1 = a1 + self.delay * (a1 - hist1[q-1]) h0 = h0 - self.coupling * (a0 - a1) h1 = h1 - self.coupling * (a1 - a0) out = self.head(torch.cat([h0, h1], dim=1)) return (out, torch.stack(hist0), torch.stack(hist1)) if return_states else out def seed_all(seed): random.seed(seed); np.random.seed(seed); torch.manual_seed(seed) if torch.cuda.is_available(): torch.cuda.manual_seed_all(seed) def run_one(seed, cfg, idea): seed_all(seed) ds = get_dataset('dynamics', seed=seed, n_train=400, n_test=200) net = CoupledGRU(ds['out_dim'], coupling=cfg['coupling'] if idea else 0.0, compensated=idea, delay=DELAY) _, metric, _ = train_model(net, ds, epochs=EPOCHS, lr=cfg['lr'], batch=BATCH, log=lambda *_: None) return float(metric) def fn(cfg, idea): return lambda seed: run_one(seed, cfg, idea) def train_probe(seed, cfg, idea, ds): seed_all(seed) net = CoupledGRU(ds['out_dim'], coupling=cfg['coupling'] if idea else 0.0, compensated=idea, delay=DELAY) net, _, _ = train_model(net, ds, epochs=EPOCHS, lr=cfg['lr'], batch=BATCH, log=lambda *_: None) return net def mechanism_signature(): """Behavioural signature from trained benchmark systems. It tests the predicted graph-mode distinction: consensus is unchanged by coupling while the antisymmetric/nonzero mode is the affected quantity. """ ds = get_dataset('dynamics', seed=0, n_train=400, n_test=200) cfg = {'lr': 3e-3, 'coupling': 0.08} base = train_probe(0, cfg, False, ds); idea = train_probe(0, cfg, True, ds) device_b = next(base.parameters()).device; device_i = next(idea.parameters()).device xb = ds['xte'].to(device_b); xi = ds['xte'].to(device_i) with torch.no_grad(): _, b0, b1 = base(xb, return_states=True); _, i0, i1 = idea(xi, return_states=True) # Compare trained branch consensus and nonzero-mode magnitudes. bdiff = float((b0[-1]-b1[-1]).pow(2).mean().sqrt().cpu()) idiff = float((i0[-1]-i1[-1]).pow(2).mean().sqrt().cpu()) bsum = float((b0[-1]+b1[-1]).pow(2).mean().sqrt().cpu()) isum = float((i0[-1]+i1[-1]).pow(2).mean().sqrt().cpu()) # A directly observable delay-phase proxy: cross-branch state correlation at lag d. def lag_corr(a, b): u = a[DELAY:, :, 0].reshape(-1).cpu().numpy(); v = b[:-DELAY, :, 0].reshape(-1).cpu().numpy() return float(np.corrcoef(u, v)[0, 1]) corr = lag_corr(i0, i1) # Quantitative stage-1 prediction is confirmed only if consensus dominates # the nonzero mode and delayed cross-branch states remain phase-aligned. return {'trained_model': True, 'delay_steps': DELAY, 'baseline_nonzero_state_rms': bdiff, 'idea_nonzero_state_rms': idiff, 'baseline_consensus_state_rms': bsum, 'idea_consensus_state_rms': isum, 'observed_delayed_cross_branch_correlation': corr, 'predicted_delayed_phase_alignment': 'positive correlation', 'confirmed': bool(corr > 0.0 and isum > idiff)} def main(): base = sweep_baseline(lambda cfg: fn(cfg, False), GRID, seeds=(0,1,2,3)) idea_runs = [] for cfg in [base['best_cfg'], {'lr': 1e-3, 'coupling': 0.08}, {'lr': 6e-3, 'coupling': 0.16}]: idea_runs.append({'cfg': cfg, 'result': evaluate(fn(cfg, True), seeds=SEEDS)}) best = min(idea_runs, key=lambda z: z['result']['mean']) report = make_report('dynamics', 'rnn_small', base, best['result'], extra=mechanism_signature()) report['idea_sweep'] = idea_runs report['protocol'] = {'paired_seeds': list(SEEDS), 'epochs': EPOCHS, 'batch': BATCH, 'structural_match': 'dynamics/control', 'baseline_grid': GRID} Path('bench_report.json').write_text(json.dumps(report, indent=2)); print(json.dumps(report, indent=2)) if __name__ == '__main__': main()