Reciprocal-Lattice Gauge-Covariant Bloch Network / stage2_bench.py

✓✓ Beats tuned baseline

Raw ⬇ ZIP
  1import sys, json, math, random
  2from pathlib import Path
  3import numpy as np
  4import torch
  5from torch import nn
  6sys.path.insert(0, '/home/maxwelhelp/all/math2nn')
  7from bench import make_model, train_model, evaluate, sweep_baseline, make_report
  8from bloch_track import get_dataset
  9
 10K = 2.0 * math.pi
 11SEEDS = tuple(range(8))
 12SWEEP_SEEDS = tuple(range(4))
 13
 14class GaugeModel(nn.Module):
 15    """Same MLP as baseline, with canonical q and exact output gauge."""
 16    def __init__(self, base):
 17        super().__init__(); self.base = base
 18    def forward(self, z):
 19        c, x, q = z[:, 0], z[:, 1], z[:, 2]
 20        m = torch.round(q / K)
 21        q0 = q - m * K
 22        raw = self.base(torch.stack([c, x, q0], dim=1))
 23        phase = torch.exp(-1j * m * K * x)
 24        out = torch.complex(raw[:, 0], raw[:, 1]) * phase
 25        return torch.stack([out.real, out.imag], dim=1)
 26
 27def seed_all(seed):
 28    random.seed(seed); np.random.seed(seed); torch.manual_seed(seed)
 29    if torch.cuda.is_available(): torch.cuda.manual_seed_all(seed)
 30
 31def make_system(kind, ds):
 32    base = make_model('mlp_tiny', ds['input_shape'], 2)
 33    return GaugeModel(base) if kind == 'idea' else base
 34
 35def run_one(kind, cfg, seed):
 36    seed_all(seed)
 37    ds = get_dataset(seed, 400, 400)
 38    ds['xtr'] = torch.as_tensor(ds['xtr'], dtype=torch.float32); ds['ytr'] = torch.as_tensor(ds['ytr'], dtype=torch.float32)
 39    ds['xte'] = torch.as_tensor(ds['xte'], dtype=torch.float32); ds['yte'] = torch.as_tensor(ds['yte'], dtype=torch.float32)
 40    ds['input_shape'] = tuple(ds['xtr'].shape[1:]); ds['out_dim'] = 2
 41    net = make_system(kind, ds)
 42    trained, metric, hist = train_model(net, ds, epochs=cfg['epochs'], lr=cfg['lr'], batch=128, weight_decay=cfg['weight_decay'], log=lambda *_: None)
 43    return float(metric) if metric is not None else float('nan')
 44
 45def model_for_signature(kind, cfg, seed):
 46    seed_all(seed); ds = get_dataset(seed, 400, 400)
 47    ds['xtr'] = torch.as_tensor(ds['xtr'], dtype=torch.float32); ds['ytr'] = torch.as_tensor(ds['ytr'], dtype=torch.float32)
 48    ds['xte'] = torch.as_tensor(ds['xte'], dtype=torch.float32); ds['yte'] = torch.as_tensor(ds['yte'], dtype=torch.float32)
 49    ds['input_shape'] = tuple(ds['xtr'].shape[1:]); ds['out_dim'] = 2
 50    net = make_system(kind, ds)
 51    net, _, _ = train_model(net, ds, epochs=cfg['epochs'], lr=cfg['lr'], batch=128, weight_decay=cfg['weight_decay'], log=lambda *_: None)
 52    return net, ds
 53
 54def signature(cfg):
 55    rows = {'baseline': [], 'idea': []}
 56    # Query the trained systems at q0 and q0+K, then compare observed shift with exact phase prediction.
 57    rng = np.random.default_rng(991)
 58    c = torch.tensor(rng.uniform(-.9, .9, 96), dtype=torch.float32)
 59    x = torch.tensor(rng.uniform(0, 1, 96), dtype=torch.float32)
 60    q0 = torch.tensor(rng.uniform(-.8*math.pi, .8*math.pi, 96), dtype=torch.float32)
 61    for kind in ('baseline', 'idea'):
 62        net, _ = model_for_signature(kind, cfg, 0)
 63        net.eval()
 64        dev = next(net.parameters()).device
 65        with torch.no_grad():
 66            probe0 = torch.stack([c, x, q0], 1).to(dev)
 67            probe1 = torch.stack([c, x, q0 + K], 1).to(dev)
 68            p0 = net(probe0)
 69            p1 = net(probe1)
 70            cdev, xdev, qdev = c.to(dev), x.to(dev), q0.to(dev)
 71        z0 = torch.complex(p0[:,0], p0[:,1]); z1 = torch.complex(p1[:,0], p1[:,1])
 72        expected = z0 * torch.exp(-1j*K*xdev)
 73        err = torch.sqrt(torch.mean(torch.abs(z1-expected)**2)).item()
 74        scale = torch.sqrt(torch.mean(torch.abs(z0)**2)).item()
 75        rows[kind] = {'shift_m': 1, 'observed_gauge_rmse': err, 'relative_rmse': err/(scale+1e-8)}
 76    # Quantitative stage-1 prediction: exact covariance should be near zero for the wrapped model,
 77    # while the raw model should have a nonzero alias discrepancy.
 78    confirmed = rows['idea']['relative_rmse'] < 0.08 and rows['idea']['relative_rmse'] < rows['baseline']['relative_rmse']
 79    return {'prediction': 'canonicalized model has much smaller trained-model reciprocal-shift gauge discrepancy', 'rows': rows, 'confirmed': bool(confirmed)}
 80
 81def main():
 82    grid = [
 83        {'lr': 0.001, 'epochs': 25, 'weight_decay': 0.0},
 84        {'lr': 0.003, 'epochs': 25, 'weight_decay': 0.0},
 85        {'lr': 0.006, 'epochs': 25, 'weight_decay': 0.0},
 86    ]
 87    base = sweep_baseline(lambda cfg: lambda s: run_one('baseline', cfg, s), grid, seeds=SWEEP_SEEDS)
 88    idea_sweep = []
 89    for cfg in grid:
 90        r = evaluate(lambda s, cfg=cfg: run_one('idea', cfg, s), seeds=SWEEP_SEEDS)
 91        idea_sweep.append({'cfg': cfg, 'mean': r['mean']})
 92    best_idea_cfg = min(idea_sweep, key=lambda x: x['mean'])['cfg']
 93    idea_full = evaluate(lambda s: run_one('idea', best_idea_cfg, s), seeds=SEEDS)
 94    rep = make_report('bloch_gauge_pde', 'mlp_tiny',
 95                      {'best_cfg': base['best_cfg'], 'sweep': base['sweep'], 'full': base['full']},
 96                      dict(idea_full, best_cfg=best_idea_cfg, sweep=idea_sweep),
 97                      extra=signature(best_idea_cfg))
 98    rep['custom_track'] = {'name': 'bloch_gauge_pde', 'file': 'bloch_track.py', 'domain': 'pde'}
 99    Path('bench_report.json').write_text(json.dumps(rep, indent=2))
100    print(json.dumps(rep, indent=2))
101if __name__ == '__main__': main()