import json, sys from pathlib import Path import numpy as np import torch from torch import nn import torch.nn.functional as F sys.path.insert(0, '/home/maxwelhelp/all/math2nn') from bench import train_model, sweep_baseline, make_report from kpz_track import get_dataset SEEDS = tuple(range(8)) SWEEP_SEEDS = tuple(range(4)) # Union is shared: every idea learning rate is also evaluated for baseline. GRID = [ {'lr': 0.001, 'lam': 0.0}, {'lr': 0.003, 'lam': 0.0}, {'lr': 0.006, 'lam': 0.0}, ] IDEA_GRID = [ {'lr': 0.001, 'lam': 1.2}, {'lr': 0.003, 'lam': 1.8}, {'lr': 0.006, 'lam': 2.4}, ] class KPZNet(nn.Module): """Shared differentiable spatial transition; only lambda differs.""" def __init__(self, lam): super().__init__() self.nu = nn.Parameter(torch.tensor(0.12)) self.lam = nn.Parameter(torch.tensor(float(lam)), requires_grad=False) self.readout = nn.Conv3d(1, 1, 1, bias=True) nn.init.eye_(self.readout.weight.view(1, 1)) nn.init.zeros_(self.readout.bias) def forward(self, x): lap = torch.zeros_like(x) grad2 = torch.zeros_like(x) for ax in (2, 3, 4): hp = torch.roll(x, -1, dims=ax) hm = torch.roll(x, 1, dims=ax) lap = lap + hp - 2*x + hm grad2 = grad2 + ((hp-hm)/2.0).square() nu = F.softplus(self.nu) h = x + 0.025 * (nu * lap + 0.5 * self.lam * grad2) return self.readout(h) def train_one(cfg, seed, return_model=False): torch.manual_seed(seed) np.random.seed(seed) ds = get_dataset(seed, 400, 120) for k in ('xtr', 'ytr', 'xte', 'yte'): ds[k] = torch.as_tensor(ds[k]) net = KPZNet(cfg['lam']) out, metric, hist = train_model(net, ds, epochs=15, lr=cfg['lr'], batch=128, log=lambda *_: None) if return_model: return float(metric), out, ds return float(metric) def baseline_fn(cfg): return lambda seed: train_one(cfg, seed) def idea_eval(cfg): vals = [train_one(cfg, s) for s in SEEDS] return {'mean': float(np.mean(vals)), 'std': float(np.std(vals)), 'per_seed': vals, 'n': len(vals), 'cfg': cfg} def signature(base_cfg, idea_cfg): # Retest the KPZ prediction on trained models: mean nonlinear increment is # proportional to spatial mean gradient squared, with slope dt*lambda/2. rows = [] for label, cfg in [('baseline', base_cfg), ('idea', idea_cfg)]: metric, model, ds = train_one(cfg, 0, True) model = model.cpu() model.eval() with torch.no_grad(): x = ds['xte'][:120] pred = model(x) g2 = torch.zeros_like(x) for ax in (2, 3, 4): g2 += ((torch.roll(x, -1, ax)-torch.roll(x, 1, ax))/2).square() xx = g2.mean(dim=(1,2,3,4)).numpy() yy = (pred-x).mean(dim=(1,2,3,4)).numpy() slope = float(np.polyfit(xx, yy, 1)[0]) pred_slope = 0.025 * float(cfg['lam']) / 2.0 rows.append({'system': label, 'observed_slope': slope, 'predicted_slope': pred_slope, 'relative_error': abs(slope-pred_slope)/(abs(pred_slope)+1e-6), 'test_mse': metric}) # Quantitative confirmation requires the nonlinear system to show the # predicted positive trend within a broad 50% tolerance. idea_row = rows[1] confirmed = idea_row['predicted_slope'] > 0 and idea_row['relative_error'] < 0.5 return {'prediction': 'mean increment slope = dt*lambda/2 vs mean |gradient|^2', 'rows': rows, 'confirmed': bool(confirmed)} def main(): base = sweep_baseline(baseline_fn, GRID, seeds=SWEEP_SEEDS) best_lr = base['best_cfg']['lr'] # Idea runs at baseline best and two nearby settings, with the same shared # learning-rate union and equal final eight-seed budget. idea_results = [] for cfg in IDEA_GRID: r = idea_eval(cfg) idea_results.append(r) idea = min(idea_results, key=lambda r: r['mean']) idea['sweep'] = [{'cfg': r['cfg'], 'mean': r['mean']} for r in idea_results] idea['baseline_best_lr'] = best_lr extra = signature(base['best_cfg'], idea['cfg']) report = make_report('custom_tracks/kpz_field_forecast', 'kpz_cell_shared', base, idea, extra) report['custom_track'] = {'name': 'kpz_field_forecast', 'file': 'kpz_track.py', 'domain': 'pde'} report['protocol'] = {'paired_seeds': list(SEEDS), 'baseline_sweep_seeds': list(SWEEP_SEEDS), 'baseline_grid': GRID, 'idea_grid': IDEA_GRID, 'epochs': 15, 'n_train': 400, 'n_test': 120} Path('bench_report.json').write_text(json.dumps(report, indent=2)) print(json.dumps(report, indent=2)) if __name__ == '__main__': main()