"""Exhaust local binary lifts of a closure cycle, without prescribing T^3=M.

Tests three distinct meanings of connection: preservation of the particle/
antiparticle pairing; connectivity when that pairing is an edge; and a single
orbit of the clock update itself. These must not be silently identified.
The closure length 3 comes from the existing model's minimal-loop construction.
No temporal splitting rate or spatial expansion law is invented here.
"""
from itertools import product
from pathlib import Path
import json


def lift(n, eps):
    # State (i,b), encoded by 2*i+b. eps specifies the locally reversible
    # transport of the binary pole on the next edge of the common closure.
    return [2*((i+1)%n)+(b ^ eps[i]) for i in range(n) for b in range(2)]


def cycles(p):
    unseen=set(range(len(p)))
    out=[]
    while unseen:
        start=min(unseen)
        c=[]
        k=start
        while k in unseen:
            unseen.remove(k)
            c.append(k)
            k=p[k]
        assert k==start
        out.append(c)
    return out


def connected_with_pairing(p):
    reached={0}
    todo=[0]
    inv=[0]*len(p)
    for i,j in enumerate(p): inv[j]=i
    while todo:
        i=todo.pop()
        for j in (p[i],inv[i],i^1):
            if j not in reached:
                reached.add(j)
                todo.append(j)
    return len(reached)==len(p)


def switching_normal_form(eps):
    # Relabel the two fiber poles locally, as a mathematical coordinate change.
    # This is not automatically a permitted physical relabeling of space/time.
    n=len(eps)
    gauge=[0]*n
    for i in range(n-1): gauge[i+1]=gauge[i]^eps[i]
    return tuple(eps[i]^gauge[i]^gauge[(i+1)%n] for i in range(n))


def main():
    census=[]
    triangle=[]
    for n in range(1,13):
        counts={'length':n,'lifts':0,'pairing_preserved':0,'pairing_graph_connected':0,
                'single_clock_orbit':0,'two_clock_orbits':0}
        for eps in product((0,1),repeat=n):
            p=lift(n,eps)
            assert sorted(p)==list(range(2*n))
            assert all(p[i^1]==(p[i]^1) for i in range(2*n))
            hol=sum(eps)%2
            for i in range(2*n):
                k=i
                for _ in range(n): k=p[k]
                assert k==(i^hol)
            cs=cycles(p)
            assert sorted(map(len,cs))==([2*n] if hol else [n,n])
            assert connected_with_pairing(p)
            normal=switching_normal_form(eps)
            assert normal==(0,)*(n-1)+(hol,)
            counts['lifts']+=1
            counts['pairing_preserved']+=1
            counts['pairing_graph_connected']+=1
            counts['single_clock_orbit']+=int(len(cs)==1)
            counts['two_clock_orbits']+=int(len(cs)==2)
            if n==3:
                triangle.append({'edge_flip_bits':eps,'loop_flip':hol,
                    'clock_orbit_lengths':list(map(len,cs)),
                    'pairing_graph_connected':True,'switching_normal_form':normal})
        assert counts['single_clock_orbit']==counts['two_clock_orbits']==2**(n-1)
        census.append(counts)

    # All invert-every-tick lifts: this uses the original primitive update.
    uniform=[]
    for n in range(1,13):
        cs=cycles(lift(n,(1,)*n))
        uniform.append({'closure_length':n,'orbit_lengths':list(map(len,cs)),
                        'loop_inverts_seed':bool(n%2)})
    assert cycles(lift(3,(1,1,1)))==[[0,3,4,1,2,5]]
    result={
        'scope':'Local reversible binary lifts of a prescribed closure cycle; not all possible seed evolutions',
        'current_wording':'Space expands and time splits as the particle/antiparticle poles of one seed, sharing the clock dimension.',
        'no_growth_law_assumed':True,
        'triangle_lifts':triangle,
        'general_census':census,
        'invert_every_tick':uniform,
        'exact_findings':[
            'Pairing preservation alone permits both loop signs.',
            'Including the particle/antiparticle pairing as a connection edge makes both classes connected.',
            'A single clock orbit through the full lift is equivalent to odd loop inversion.',
            'For closure length three that orbit is a six-cycle, with T^3=C and T^6=I.',
            'The original invert-every-tick primitive on the minimal three-step loop selects that class without a sign fit.',
            'Local fiber relabeling leaves two mathematical classes, classified by loop inversion.'
        ],
        'remaining_questions':[
            'Why the developing structure has this binary-cover local transport description.',
            'How persistent connection in the physical theory constrains clock orbits, rather than only pairing edges.',
            'The physical evolution laws distinguishing temporal splitting from spatial expansion.',
            'How contracting/binding observables arise, rather than merely closure of an internal clock orbit.'
        ]
    }
    out=Path(__file__).with_name('persistent-connection-results.json')
    out.write_text(json.dumps(result,indent=2)+'\n',encoding='utf8')
    print('Exact lifts tested:',sum(c['lifts'] for c in census))
    print('Triangle lift census:',json.dumps(triangle,indent=2))
    print('All pairing/clock/cycle/switching assertions passed.')
    print('Saved',out)


if __name__=='__main__': main()
