import json, math, random, time from pathlib import Path import numpy as np import torch import torch.nn as nn import torch.nn.functional as F SEED = 2062 random.seed(SEED); np.random.seed(SEED); torch.manual_seed(SEED) try: device = 'cuda' if torch.cuda.is_available() else 'cpu' except Exception: device = 'cpu' def cycle(n): g=[set() for _ in range(n)] for i in range(n): for j in ((i-1)%n,(i+1)%n): g[i].add(j) return g def grid(side): n=side*side; g=[set() for _ in range(n)] for r in range(side): for c in range(side): i=r*side+c for rr,cc in ((r-1,c),(r+1,c),(r,c-1),(r,c+1)): if 0<=rr1+delta: crossing=t predicted = (math.log((1+delta-1)/(r-1))/math.log(beta) if r>1+delta else None) persistence.append({'r':r,'cross_step_observed':crossing,'cross_step_continuous_prediction':None if predicted is None else predicted}) return {'cycle_boundary_prediction':cycle_rows,'tree_expansion_prediction':tree_rows,'gate_boundary_prediction':boundary_rows,'ema_persistence_prediction':persistence} def train_case(name,g,epochs=60): n=len(g); A=graph_matrix(g).to(device) # fixed random features; label is a local structural signal (degree parity plus index hash) deg=np.array([len(x) for x in g]); x=np.random.default_rng(SEED+n).normal(size=(n,8)).astype('float32') x[:,0]=deg/ max(1,deg.max()) y=torch.tensor(((deg + np.arange(n))%2),dtype=torch.long,device=device) perm=np.random.default_rng(SEED).permutation(n); cut=max(2,int(.7*n)); tridx=torch.tensor(perm[:cut],device=device); vaidx=torch.tensor(perm[cut:],device=device) # sampled seed frontier and a stable EMA estimate, as the controller would see it online seeds=set(perm[:min(16,n)]); Fset=seeds; rb=1.; beta=.7; rs=[]; frontiers=[] for _ in range(6): Fset=expand(g,Fset); r=len(Fset)/max(1,len(seeds)); rb=beta*rb+(1-beta)*r; rs.append(rb); frontiers.append(len(Fset)) rbar=torch.tensor(float(rb),device=device) result={} for gated in [False,True]: torch.manual_seed(SEED+int(gated)+n) model=GNN(8,24,6,gated=gated).to(device); opt=torch.optim.Adam(model.parameters(),lr=.025) t0=time.time() for _ in range(epochs): opt.zero_grad(); logits,_=model(torch.tensor(x,device=device),A,rbar) loss=F.cross_entropy(logits[tridx],y[tridx]); loss.backward(); opt.step() with torch.no_grad(): logits,gs=model(torch.tensor(x,device=device),A,rbar); acc=(logits[vaidx].argmax(1)==y[vaidx]).float().mean().item() h=logits[vaidx]; cos=F.cosine_similarity(h[:,None,:],h[None,:,:],dim=-1).mean().item() if len(vaidx)>1 else 0 result['gated' if gated else 'baseline']={'val_accuracy':acc,'mean_pairwise_cosine':cos,'seconds':time.time()-t0,'gate_mean':float(np.mean(gs))} result.update({'rbar':float(rb),'frontier_peak':max(frontiers),'raw_ratios':[float(x) for x in rs]}) return result def main(): checks=mechanism_checks(); cases={} graphs={'cycle':cycle(100),'grid':grid(10),'random3':random_regular(100,3)} tr,_=tree(3,5); graphs['tree3']=tr for name,g in graphs.items(): try: cases[name]=train_case(name,g) except Exception as e: cases[name]={'error':repr(e)} out={'seed':SEED,'device':device,'checks':checks,'experiments':cases} Path('results.json').write_text(json.dumps(out,indent=2)) print(json.dumps(out,indent=2)) if __name__=='__main__': main()