Barrier-Temperature Matching / barrier_temperature_mvp.py

Mechanism failed

Raw ⬇ ZIP
  1import json, math, random, time
  2from pathlib import Path
  3import numpy as np
  4
  5SEED = 3070
  6np.random.seed(SEED); random.seed(SEED)
  7
  8
  9def math_checks():
 10    # Piecewise-constant temperature field: the stated integral reduces to a
 11    # harmonic mean over the uphill half-period.
 12    T = np.array([0.5, 1.0, 2.0, 4.0])
 13    Tact = len(T) / np.sum(1.0 / T)
 14    numerical = 2.0 / np.mean(1.0 / T) / 2.0  # same harmonic mean, explicit average
 15    # T <- T r^k gives log(r_next)=(1-k)log(r) when U is locally fixed.
 16    rs = np.array([0.05, 0.2, 3.0, 20.0])
 17    k = 0.08
 18    next_rs = rs ** (1-k)
 19    contraction = np.max(np.abs(np.log(next_rs))/np.abs(np.log(rs)))
 20    # Arrhenius claim: log escape probability is linear in -U/T.
 21    ratios = np.linspace(0.3, 3.0, 12)
 22    p = np.exp(-ratios)
 23    slope = np.polyfit(ratios, np.log(p), 1)[0]
 24    return {"Tact_harmonic": float(Tact), "Tact_formula_error": float(abs(Tact-numerical)),
 25            "controller_log_contraction": float(contraction), "arrhenius_log_slope": float(slope)}
 26
 27
 28def double_well_check():
 29    # V(x)=(x^2-1)^2 has minima +/-1 and barrier U=1 at x=0.
 30    # Parallel trajectories reduce rare-transition sampling noise.
 31    rng = np.random.default_rng(SEED)
 32    dt, steps, burn, ntraj = 0.002, 100000, 5000, 256
 33    temps = np.array([0.18, 0.24, 0.32, 0.42, 0.55])
 34    rates = []
 35    for temp in temps:
 36        x = np.full(ntraj, -1.0)
 37        side = np.full(ntraj, -1)
 38        transitions = 0
 39        for i in range(steps):
 40            grad = 4*x*(x*x - 1)
 41            x += -grad*dt + math.sqrt(2*temp*dt)*rng.normal(size=ntraj)
 42            if i >= burn:
 43                newside = np.where(x > 0, 1, -1)
 44                transitions += int(np.count_nonzero(newside != side))
 45                side = newside
 46        rates.append(transitions/(ntraj*(steps-burn)))
 47    rates = np.maximum(np.asarray(rates), 1e-12)
 48    fit = np.polyfit(1/temps, np.log(rates), 1)
 49    return {"temperatures": temps.tolist(), "transition_rates": rates.tolist(),
 50            "log_rate_vs_inverse_temperature_slope": float(fit[0]),
 51            "expected_negative_barrier_sign": bool(fit[0] < 0)}
 52
 53def make_data(n=512):
 54    rng=np.random.default_rng(SEED)
 55    x=rng.normal(size=(n,2)).astype(np.float32)
 56    y=((x[:,0]**2 + x[:,1]**2 + .35*x[:,0]) > 1.0).astype(np.int64)
 57    # introduce a nontrivial but tiny classification task
 58    return x,y
 59
 60
 61def run_mlp(controller, steps=500):
 62    import torch
 63    torch.manual_seed(SEED); np.random.seed(SEED)
 64    device = "cuda" if torch.cuda.is_available() else "cpu"
 65    try:
 66        dev=torch.device(device)
 67        X,y=make_data(); X=torch.tensor(X,device=dev); y=torch.tensor(y,device=dev)
 68        model=torch.nn.Sequential(torch.nn.Linear(2,16),torch.nn.Tanh(),torch.nn.Linear(16,2)).to(dev)
 69        lossfn=torch.nn.CrossEntropyLoss()
 70        lr=0.035; T=0.0025; batch=64; d=sum(p.numel() for p in model.parameters())
 71        losses=[]; controls=[]; transitions=0; last_pred=None
 72        for step in range(steps):
 73            ix=torch.randint(0,len(X),(batch,),device=dev)
 74            logits=model(X[ix]); loss=lossfn(logits,y[ix])
 75            grads=torch.autograd.grad(loss,tuple(model.parameters()),create_graph=False)
 76            with torch.no_grad():
 77                for p,g in zip(model.parameters(),grads):
 78                    # Langevinized SGD. T is the injected update temperature.
 79                    p.add_(-lr*g + math.sqrt(2*max(T,1e-12))*lr*torch.randn_like(p))
 80            losses.append(float(loss.detach().cpu()))
 81            if step % 20 == 0:
 82                with torch.no_grad():
 83                    pred=model(X).argmax(1)
 84                    basin=int((pred==y).sum().item() > len(y)*.5)
 85                    if last_pred is not None and basin != last_pred: transitions += 1
 86                    last_pred=basin
 87            if controller and step % 50 == 0:
 88                # Gradient covariance from 8 independent minibatches; P=lr I.
 89                gs=[]
 90                for _ in range(8):
 91                    j=torch.randint(0,len(X),(batch,),device=dev)
 92                    ll=lossfn(model(X[j]),y[j])
 93                    gg=torch.autograd.grad(ll,tuple(model.parameters()))
 94                    gs.append(torch.cat([z.detach().reshape(-1) for z in gg]))
 95                G=torch.stack(gs); cov_trace=((G-G.mean(0))**2).sum(1).mean()
 96                That=float((lr*lr*cov_trace/(2*d)).detach().cpu())
 97                # Two perturbed endpoints and a linear interpolation barrier.
 98                base=[p.detach().clone() for p in model.parameters()]
 99                eps=[torch.randn_like(p)*math.sqrt(2*max(T,1e-12))*0.7 for p in model.parameters()]
100                vals=[]
101                for alpha in torch.linspace(0,1,7,device=dev):
102                    with torch.no_grad():
103                        for p,b,e in zip(model.parameters(),base,eps): p.copy_(b+(2*alpha-1)*e)
104                        vals.append(float(lossfn(model(X),y).detach().cpu()))
105                with torch.no_grad():
106                    for p,b in zip(model.parameters(),base): p.copy_(b)
107                U=max(0.0,max(vals)-vals[3])
108                effective=max(That, T)
109                r=U/max(effective,1e-10)
110                old=T
111                T=float(np.clip(T*math.exp(0.08*np.clip(math.log(max(r,1e-8)),-4,4)),1e-5,0.08))
112                controls.append((step,U,That,r,old,T))
113        with torch.no_grad():
114            final_loss=float(lossfn(model(X),y).cpu()); acc=float((model(X).argmax(1)==y).float().mean().cpu())
115        return {"final_loss":final_loss,"accuracy":acc,"mean_last100_loss":float(np.mean(losses[-100:])),
116                "transitions":transitions,"final_temperature":T,"controls":controls,"device":str(dev)}
117    except Exception as e:
118        # Explicit CPU fallback as required for shared-GPU robustness.
119        if device == "cuda":
120            torch.cuda.empty_cache()
121            torch.cuda.is_available=lambda: False
122            return run_mlp(controller,steps)
123        raise
124
125
126def main():
127    t=time.time()
128    out={"seed":SEED,"math":math_checks(),"double_well":double_well_check(),
129         "mlp_fixed":run_mlp(False),"mlp_barrier_matching":run_mlp(True),"seconds":time.time()-t}
130    # Keep the report compact while preserving all controller observations.
131    Path("results.json").write_text(json.dumps(out,indent=2))
132    print(json.dumps({k:v for k,v in out.items() if k not in ("mlp_fixed","mlp_barrier_matching")},indent=2))
133    for k in ("mlp_fixed","mlp_barrier_matching"):
134        v=out[k]; print(k, {z:v[z] for z in ("final_loss","accuracy","mean_last100_loss","transitions","final_temperature","device")})
135
136if __name__ == "__main__": main()