Holonomy-designed recurrent memory / holonomy_experiment.py
Failed on benchmark
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()