Nonlinear Hydrodynamic Optimizer / bench_hydro.py

Mechanism confirmed, baseline not beaten

Raw ⬇ ZIP
  1import json, math, random, sys
  2from pathlib import Path
  3import numpy as np
  4import torch
  5from torch import nn
  6
  7sys.path.insert(0, "/home/maxwelhelp/all/math2nn")
  8from bench import get_dataset, make_model, sweep_baseline, evaluate, make_report
  9
 10SEEDS = list(range(8))
 11EPOCHS, BATCH = 12, 128
 12NTR, NTE = 1000, 400
 13LRS = [1e-3, 3e-3, 1e-2]
 14
 15
 16def seed_all(seed):
 17    random.seed(seed); np.random.seed(seed); torch.manual_seed(seed)
 18    if torch.cuda.is_available(): torch.cuda.manual_seed_all(seed)
 19
 20
 21class HydroAllocator:
 22    def __init__(self, m, alpha=0.12, d0=0.7, beta=1.0, noise=0.0, seed=0):
 23        self.m, self.alpha, self.d0, self.beta, self.noise = m, alpha, d0, beta, noise
 24        self.n = np.ones(m, dtype=np.float64)
 25        self.mean = np.zeros(m); self.var = np.zeros(m)
 26        self.rng = np.random.default_rng(seed + 991)
 27        self.mass_err = []; self.activity_var = []; self.n_hist = []
 28
 29    def step(self, activity):
 30        a = np.asarray(activity, dtype=np.float64)
 31        self.mean = .95*self.mean + .05*a
 32        self.var = .95*self.var + .05*(a-self.mean)**2
 33        nf = .5*(self.n[:-1] + self.n[1:])
 34        D = self.d0 * (1.0 + self.beta*nf/self.m)
 35        mobility = .02 + self.var[:-1] + self.var[1:]
 36        z = self.rng.normal(size=self.m-1)
 37        J = -D*(self.n[1:]-self.n[:-1]) + self.noise*np.sqrt(mobility)*z
 38        div = np.zeros(self.m); div[:-1] += J; div[1:] -= J
 39        self.n = np.maximum(self.n - self.alpha*div, 1e-10)
 40        self.n *= self.m/self.n.sum()
 41        target = self.m*(a+1e-8)/(a.sum()+self.m*1e-8)
 42        self.n = .85*self.n + .15*target
 43        self.n *= self.m/self.n.sum()
 44        self.mass_err.append(abs(self.n.sum()-self.m))
 45        self.activity_var.append(float(np.var(a)))
 46        self.n_hist.append(self.n.copy())
 47        return self.n.copy()
 48
 49
 50def train(seed, lr, idea=False, alpha=0.12, noise=0.0, collect=False):
 51    seed_all(seed)
 52    ds = get_dataset("tabular", seed, n_train=NTR, n_test=NTE)
 53    model = make_model("mlp_tiny", ds["input_shape"], ds["out_dim"])
 54    device = "cuda" if torch.cuda.is_available() else "cpu"
 55    try:
 56        model = model.to(device)
 57        xtr, ytr = ds["xtr"].to(device), ds["ytr"].to(device)
 58        xte, yte = ds["xte"].to(device), ds["yte"].to(device)
 59        lossf = nn.MSELoss()
 60        opt = torch.optim.Adam(model.parameters(), lr=lr)
 61        blocks = list(model.net) if hasattr(model, "net") else [m for m in model.modules() if isinstance(m, nn.Linear)]
 62        blocks = [list(b.parameters()) for b in blocks]
 63        alloc = HydroAllocator(len(blocks), alpha=alpha, noise=noise, seed=seed) if idea else None
 64        for _ in range(EPOCHS):
 65            model.train(); perm = torch.randperm(len(xtr), device=device)
 66            for j in range(0, len(xtr), BATCH):
 67                idx = perm[j:j+BATCH]
 68                opt.zero_grad(set_to_none=True)
 69                loss = lossf(model(xtr[idx]), ytr[idx]); loss.backward()
 70                if idea:
 71                    acts=[]
 72                    with torch.no_grad():
 73                        for ps in blocks:
 74                            sq = sum(float((p.grad*p.grad).sum()) for p in ps if p.grad is not None)
 75                            pn = sum(float((p*p).sum()) for p in ps)
 76                            acts.append(math.sqrt(sq)/(math.sqrt(pn)+1e-8))
 77                        scales = alloc.step(acts)/(len(blocks)/len(blocks))
 78                        for bi, ps in enumerate(blocks):
 79                            for p in ps:
 80                                if p.grad is not None: p.grad.mul_(float(scales[bi]))
 81                opt.step()
 82        model.eval()
 83        with torch.no_grad(): metric = float(lossf(model(xte), yte))
 84        out = {"metric": metric}
 85        if alloc is not None and collect:
 86            out.update({"mass_error": float(max(alloc.mass_err)), "activity_var": float(np.mean(alloc.activity_var)), "n_hist": np.asarray(alloc.n_hist)})
 87        return out
 88    except RuntimeError:
 89        if device == "cuda":
 90            torch.cuda.empty_cache(); return train(seed, lr, idea, alpha, noise, collect)
 91        raise
 92
 93
 94def main():
 95    baseline = sweep_baseline(lambda cfg: (lambda s: train(s, cfg["lr"], False)["metric"]), [{"lr": x} for x in LRS], seeds=SEEDS[:4])
 96    base_full = evaluate(lambda s: train(s, baseline["best_cfg"]["lr"], False)["metric"], seeds=SEEDS)
 97    idea_cfgs = [{"lr": x} for x in LRS]
 98    idea_runs = {c["lr"]: evaluate(lambda s, lr=c["lr"]: train(s, lr, True)["metric"], seeds=SEEDS) for c in idea_cfgs}
 99    best_lr = min(idea_runs, key=lambda x: idea_runs[x]["mean"])
100    idea = evaluate(lambda s: train(s, best_lr, True)["metric"], seeds=SEEDS)
101    sig = [train(s, best_lr, True, collect=True) for s in SEEDS]
102    hist = np.concatenate([x["n_hist"] for x in sig])
103    mass = max(x["mass_error"] for x in sig)
104    var = float(np.mean([x["activity_var"] for x in sig]))
105    report = make_report("tabular", "mlp_tiny", {"best_cfg": baseline["best_cfg"], "sweep": baseline["sweep"], "full": base_full}, idea, {"mechanism_signature": {"predicted_mass_error": 0.0, "observed_mass_error": mass, "predicted_conservative": True, "observed_mean_activity_variance": var, "density_mean": float(hist.mean()), "density_std": float(hist.std()), "confirmed": mass < 1e-6}})
106    report["idea_sweep"] = {str(k): v for k,v in idea_runs.items()}
107    report["protocol"] = {"paired_seeds": SEEDS, "shared_lr_union": LRS, "track_rationale": "tabular is the built-in structural match for optimizer interventions"}
108    Path("bench_report.json").write_text(json.dumps(report, indent=2, default=lambda x: x.tolist() if isinstance(x,np.ndarray) else float(x)))
109    print(json.dumps(report, indent=2))
110
111if __name__ == "__main__": main()