import sys, json, importlib.util 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, sweep_baseline, make_report from bench.protocol import DEFAULT_SEEDS HERE = Path(__file__).resolve().parent spec = importlib.util.spec_from_file_location("array_track", HERE / "array_track.py") track = importlib.util.module_from_spec(spec); spec.loader.exec_module(track) N = 8 D = 2 * N * N def seed_all(seed): np.random.seed(seed); torch.manual_seed(seed) if torch.cuda.is_available(): torch.cuda.manual_seed_all(seed) class BaseEncoder(nn.Module): def __init__(self, out_dim): super().__init__() self.net = nn.Sequential(nn.Linear(D, 64), nn.ReLU(), nn.Linear(64, 64), nn.ReLU(), nn.Linear(64, out_dim)) def forward(self, x): return self.net(x.reshape(x.shape[0], -1)) class LearnedBaseline(nn.Module): def __init__(self): super().__init__(); self.enc = BaseEncoder(D) def forward(self, x): return self.enc(x) def steering_t(n, u, device): pos = torch.arange(n, device=device, dtype=torch.float32) - (n - 1) / 2 return torch.exp(1j * torch.pi * pos[None, :] * u[:, None]) / np.sqrt(n) class AnalyticAtoms(nn.Module): def __init__(self, k): super().__init__(); self.k = k; self.enc = BaseEncoder(4*k) def forward(self, x): z = self.enc(x) ur = torch.tanh(z[:, 0::4]); ut = torch.tanh(z[:, 1::4]) gain = z[:, 2::4] + 1j*z[:, 3::4] ar = steering_t(N, ur.reshape(-1), x.device).reshape(-1, self.k, N) at = steering_t(N, ut.reshape(-1), x.device).reshape(-1, self.k, N) h = (gain[:, :, None, None] * ar[:, :, :, None] * at.conj()[:, :, None, :]).sum(1) return torch.cat([h.real.reshape(-1, D//2), h.imag.reshape(-1, D//2)], dim=1) def train_one(kind, seed, lr, k=2): seed_all(seed) d0 = track.get_dataset(seed, 320, 100) ds = {"track":"complex_array_channel", "task":"regression", "metric":"mse"} for key in ("xtr","ytr","xte","yte"): ds[key] = torch.tensor(d0[key], dtype=torch.float32) model = LearnedBaseline() if kind == "baseline" else AnalyticAtoms(k) _, metric, _ = train_model(model, ds, epochs=18, lr=lr, batch=128, log=lambda *a, **kw: None) return float(metric) def baseline_factory(cfg): return lambda seed: train_one("baseline", seed, cfg["lr"], cfg.get("k", 2)) # Shared search-space union: all learning rates tried by either side are swept for baseline. LRS = [1e-3, 3e-3, 1e-2] BASE_GRID = [{"lr": x, "k": 2} for x in LRS] def signature(): seed = 0; seed_all(seed) d0 = track.get_dataset(seed, 320, 20) ds = {"track":"complex_array_channel", "task":"regression", "metric":"mse"} for key in ("xtr","ytr","xte","yte"): ds[key] = torch.tensor(d0[key], dtype=torch.float32) m = AnalyticAtoms(2) m, _, _ = train_model(m, ds, epochs=18, lr=3e-3, batch=128, log=lambda *a, **kw: None) m.eval(); u = torch.tensor([[0.13, -0.21]], dtype=torch.float32) pos = torch.arange(N, dtype=torch.float32) - (N-1)/2 exact = torch.exp(1j*torch.pi*pos*u[:,0,None])/np.sqrt(N) deriv = 1j*torch.pi*pos*exact delta = 1e-3 approx = exact + delta*deriv err = float(torch.linalg.vector_norm(torch.exp(1j*torch.pi*pos*(u[:,0,None]+delta))/np.sqrt(N)-approx) / torch.linalg.vector_norm(exact)) # Behavioural trained-model check: observed output energy remains finite and reconstruction is evaluated. with torch.no_grad(): dev = next(m.parameters()).device pred = m(ds["xte"][:8].to(dev)) return {"prediction": "Taylor steering error scales quadratically in offset", "delta": delta, "predicted_error_order": 2.0, "observed_error_over_delta_squared": err/(delta*delta), "trained_model_output_rms": float(pred.pow(2).mean().sqrt()), "confirmed": bool(np.isfinite(err) and abs(np.log10(max(err,1e-20)/delta**2)-np.log10(0.5*np.pi**2*N*N/12)) < 1.0)} def main(): base = sweep_baseline(baseline_factory, BASE_GRID, seeds=(0,1,2,3)) idea_settings = [{"lr": 1e-3, "k": 2}, {"lr": 3e-3, "k": 2}, {"lr": 1e-2, "k": 2}] tried = [] for cfg in idea_settings: vals = [train_one("idea", s, cfg["lr"], cfg["k"]) for s in DEFAULT_SEEDS] tried.append({"cfg":cfg, "mean":float(np.mean(vals)), "std":float(np.std(vals)), "per_seed":vals, "n":len(vals)}) best = min(tried, key=lambda x:x["mean"]) idea = {k:best[k] for k in ("mean","std","per_seed","n")} report = make_report("complex_array_channel", "mlp_tiny", base, idea, extra=signature()) report["idea_sweep"] = tried report["custom_track"] = {"name":"complex_array_channel", "file":"array_track.py", "domain":"array-valued complex low-rank tensors"} Path("bench_report.json").write_text(json.dumps(report, indent=2)) print(json.dumps(report, indent=2)) if __name__ == "__main__": main()