Holonomy-designed recurrent memory / holonomy_experiment.py

Failed on benchmark

Raw ⬇ ZIP
  1import json, random
  2import numpy as np
  3
  4SEED=7
  5np.random.seed(SEED); random.seed(SEED)
  6N=16; q0=0; q1=5; r0=1; r1=2; attractor=15
  7
  8def make_maps():
  9    A=np.full(N,attractor,dtype=int); B=np.full(N,attractor,dtype=int)
 10    A[q0]=r0; A[q1]=r1; A[r0]=attractor; A[r1]=attractor
 11    B[r0]=q1; B[r1]=q0; B[q0]=attractor; B[q1]=attractor
 12    return A,B
 13
 14def cycles_of(f):
 15    seen=set(); cyc=[]
 16    for start in range(N):
 17        if start in seen: continue
 18        path=[]; pos={}; x=start
 19        while x not in pos and x not in seen:
 20            pos[x]=len(path); path.append(x); x=int(f[x])
 21        if x in pos: cyc.append(path[pos[x]:])
 22        seen.update(path)
 23    return cyc
 24
 25def closure(gens):
 26    ident=tuple(range(N)); seen={ident}; todo=[ident]
 27    while todo:
 28        x=todo.pop()
 29        for g in gens:
 30            y=tuple(g[list(x)])
 31            if y not in seen: seen.add(y); todo.append(y)
 32    return seen
 33
 34def audit():
 35    A,B=make_maps(); G=B[A]
 36    return {'q0':(0,0),'q1':(1,1),'A_q0_q1':bool(A[q0]==q1),
 37      'composite_q0':int(G[q0]),'composite_q1':int(G[q1]),'swap':bool(G[q0]==q1 and G[q1]==q0),
 38      'A_cycle_lengths':sorted(map(len,cycles_of(A))),'B_cycle_lengths':sorted(map(len,cycles_of(B))),
 39      'composite_cycle_lengths':sorted(map(len,cycles_of(G))),'monoid_size':len(closure([A,B]))}
 40
 41def stochastic_transition(f, eps):
 42    P=np.full((N,N),eps/(N-1),float)
 43    for i,j in enumerate(f): P[i,j]=1-eps
 44    return P
 45
 46def sweeps():
 47    A,B=make_maps(); G=B[A]; rows=[]
 48    # Prediction 1: each frozen generator is aperiodic.
 49    # Prediction 2: preserving both directions of the two-leg word costs four
 50    # independent transitions, hence reliability (1-eps)^4.
 51    for eps in [0,.01,.03,.05,.1,.2,.3,.4]:
 52        PA=stochastic_transition(A,eps); PB=stochastic_transition(B,eps)
 53        p=(PA[q0,r0]*PB[r0,q1])*(PA[q1,r1]*PB[r1,q0])
 54        rows.append({'eps':eps,'observed':float(p),'predicted_(1-eps)^4':(1-eps)**4})
 55    # Prediction 3: softening continuously reduces exchange fidelity, while
 56    # the exact deterministic limit is a perfect Z2 orbit.
 57    temp=[]
 58    for tau in [0,.01,.03,.05,.1,.2,.3,.4]:
 59        PA=stochastic_transition(A,tau); PB=stochastic_transition(B,tau)
 60        p0=(PA[q0]@PB)[q1]; p1=(PA[q1]@PB)[q0]
 61        temp.append({'tau':tau,'observed_fidelity':float((p0+p1)/2)})
 62    x=q0; orbit=[]
 63    for _ in range(8): x=int(G[x]); orbit.append(x)
 64    return rows,temp,orbit
 65
 66def finite_delayed_copy(noise, n=20000, delay=6):
 67    # The target word is G repeated delay times. An even delay returns the bit.
 68    A,B=make_maps(); G=B[A]; correct=0
 69    for _ in range(n):
 70        bit=np.random.randint(2); state=q0 if bit==0 else q1
 71        for _ in range(delay):
 72            state=(np.random.randint(N) if np.random.random()<noise else int(G[state]))
 73        pred=0 if state==q0 else (1 if state==q1 else -1)
 74        correct += (pred==bit)
 75    return correct/n
 76
 77def gru_delayed_copy(force_cpu=False):
 78    try:
 79        import torch
 80        torch.manual_seed(SEED); torch.set_num_threads(2)
 81        dev='cpu' if force_cpu else ('cuda' if torch.cuda.is_available() else 'cpu')
 82        T=12; n=600; bs=64
 83        X=torch.zeros(n,T,3,device=dev); y=torch.zeros(n,dtype=torch.long,device=dev)
 84        for i in range(n):
 85            bit=np.random.randint(2); X[i,0,bit]=1; X[i,1:,2]=1; y[i]=bit
 86        model=torch.nn.GRU(3,8,batch_first=True).to(dev); head=torch.nn.Linear(8,2).to(dev)
 87        opt=torch.optim.Adam(list(model.parameters())+list(head.parameters()),lr=.02)
 88        for _ in range(80):
 89            ix=torch.randperm(n,device=dev)
 90            for j in range(0,n,bs):
 91                h,_=model(X[ix[j:j+bs]]); loss=torch.nn.functional.cross_entropy(head(h[:,-1]),y[ix[j:j+bs]])
 92                opt.zero_grad(); loss.backward(); opt.step()
 93        with torch.no_grad(): acc=(head(model(X)[0][:,-1]).argmax(1)==y).float().mean().item()
 94        return {'accuracy':acc,'device':dev,'parameters':sum(p.numel() for p in model.parameters())+sum(p.numel() for p in head.parameters())}
 95    except Exception:
 96        if not force_cpu: return gru_delayed_copy(True)
 97        return {'accuracy':None,'device':'cpu'}
 98
 99def main():
100    noise,temp,orbit=sweeps()
101    idea={str(e):finite_delayed_copy(e) for e in [0,.05,.1,.2,.3]}
102    out={'seed':SEED,'audit':audit(),'noise_sweep':noise,'temperature_sweep':temp,
103      'composite_orbit_8_steps':orbit,'delayed_copy_delay':6,
104      'idea_accuracy_under_transition_noise':idea,'gru_baseline':gru_delayed_copy()}
105    with open('results.json','w') as f: json.dump(out,f,indent=2)
106    print(json.dumps(out,indent=2))
107if __name__=='__main__': main()