#!/usr/bin/env python3
"""Exact two/four-slot butterfly-palindrome law and regular-operator witness.

Follows selected Harmonic.Model definitions with MSB-first integer labels:
recursive child action occurs before the root pair switch; palindrome is
first butterfly composed with the inverse second butterfly. No universal
source constant or independently verified proof/program is produced.
"""
import argparse
import hashlib
import itertools
import json
from collections import Counter
from fractions import Fraction
from math import factorial
from pathlib import Path


def compose(p, q):
    return tuple(p[q[i]] for i in range(len(p)))


def inverse(p):
    result = [0] * len(p)
    for i, j in enumerate(p):
        result[j] = i
    return tuple(result)


def butterfly(d, bits, x):
    if d == 0:
        return 0
    half = 1 << (d - 1)
    prefix, tail = divmod(x, half)
    child_bits = (d - 1) * half // 2
    start = half + prefix * child_bits
    tail = butterfly(d - 1, bits[start:start + child_bits], tail)
    return ((prefix ^ bits[tail]) * half) + tail


def calibrated_sweeps(n, norm_squared):
    target_squared = Fraction(1, 4 * n ** 10)
    initial_squared = Fraction(factorial(n) - 1, 4)
    for v in range(1, 129):
        bound = initial_squared * norm_squared ** v
        if bound <= target_squared:
            return dict(sweeps=v, total_variation_upper_bound_squared=str(bound), source_target_squared=str(target_squared),
                        previous_bound_squared=str(initial_squared * norm_squared ** (v - 1)) if v > 1 else None)
    return None


def calculate(d):
    if type(d) is not int or d not in (1, 2):
        raise ValueError('Exact calibration supports dimension 1 or 2 only')
    n = 1 << d
    bit_count = d * n // 2
    q = Counter(tuple(butterfly(d, bits, x) for x in range(n)) for bits in itertools.product((0, 1), repeat=bit_count))
    pal = Counter()
    for p, a in q.items():
        for r, b in q.items():
            pal[compose(p, inverse(r))] += a * b
    denominator = (1 << bit_count) ** 2
    group = list(itertools.permutations(range(n)))
    size = len(group)
    mass = size * Fraction(min(pal.get(g, 0) for g in group), denominator)
    if mass <= 0:
        raise ArithmeticError('No full-support exponent-zero minorant')
    gap = 1 - mass / 2
    a = [[Fraction(pal.get(compose(r, inverse(s)), 0), denominator) for s in group] for r in group]
    b = [[x - Fraction(1, size) for x in row] for row in a]
    b2 = [[sum(b[i][k] * b[k][j] for k in range(size)) for j in range(size)] for i in range(size)]
    symmetric = all(b[i][j] == b[j][i] for i in range(size) for j in range(size))
    entry = next(((i, j) for i in range(size) for j in range(size) if b[i][j]), None)
    coefficient = b2[entry[0]][entry[1]] / b[entry[0]][entry[1]] if entry else Fraction(0)
    identity = all(b2[i][j] == coefficient * b[i][j] for i in range(size) for j in range(size))
    if not symmetric or not identity or coefficient < 0:
        raise ArithmeticError('Expected scaled-projection certificate unavailable')
    witness = [b[i][entry[1]] for i in range(size)] if entry else [Fraction(0)] * size
    witness_verified = bool(entry) and sum(witness) == 0 and any(witness) and all(
        sum(b[i][j] * witness[j] for j in range(size)) == coefficient * witness[i] for i in range(size))
    rank = sum(b[i][i] for i in range(size)) / coefficient if coefficient else 0
    return dict(status='complete', dimension=d, slots=n, butterfly_coin_bits=bit_count,
                independent_palindrome_coin_strings=denominator, full_group_size=size, palindrome_support=len(pal),
                law=[dict(permutation=list(g), mass=str(Fraction(pal.get(g, 0), denominator)), coin_strings=pal.get(g, 0)) for g in group],
                palindrome_total_variation=str(sum(abs(Fraction(pal.get(g, 0), denominator) - Fraction(1, size)) for g in group) / 2),
                explicit_minorant=dict(exponent=0, mass=str(mass), uniform_floor=str(mass / size),
                                       gap_formula='1 - mass/(2*2^exponent)', off_invariants_bound=str(gap)),
                regular_operator=dict(convention='A[r,s] = palindrome_mass(r composed with inverse(s)); B=A-J',
                                      group_order=[list(g) for g in group], centered_matrix=[[str(x) for x in row] for row in b],
                                      symmetric=symmetric, scaled_projection_identity_verified=identity,
                                      identity_coefficient=str(coefficient), exact_nonconstant_norm=str(coefficient),
                                      scaled_projection_rank=str(rank), nonzero_eigenvector=[str(x) for x in witness],
                                      eigenvector_verified=witness_verified,
                                      justification='A symmetric nonzero B with B^2=cB has eigenvalues 0,c, so its norm is c. The displayed nonzero eigenvector attains c. Zero B has norm zero.'),
                physical_sweep_nonconstant_norm_squared=str(coefficient),
                finite_source_target_calibration=dict(exact_operator_bound=calibrated_sweeps(n, coefficient),
                                                      explicit_minorant_bound=calibrated_sweeps(n, gap),
                                                      squared_TV_bound_formula='((n! - 1)/4) * (sweep_nonconstant_norm_squared)^v',
                                                      scope='Conservative regular L2-to-TV bound at these two/four slots only; not optimal mixing, the source-selected Classical.choose witness or any universal all-size schedule'),
                source_commit='fd4aeeb2ee4fc729c18d98444fed42fd0529eeeb',
                source_proof_verification='not_run', formal_program_verification='not_run',
                arithmetic='exact Python integer/Fraction; no floating eigensolver',
                interpretation='Finite independent enumeration and explicit rational witness. Assumes independent fair switches in the mathematical law; certifies no OS or seeded randomness backend. Selected source identities connect physical sweep adjoint to butterfly/palindrome, but their imports/proofs were not executed or independently accepted.')


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('dimension', type=int)
    parser.add_argument('--output', type=Path)
    args = parser.parse_args()
    body = json.dumps(calculate(args.dimension), indent=2) + '\n'
    if args.output:
        with args.output.open('x') as stream:
            stream.write(body)
    else:
        print(body, end='')


if __name__ == '__main__':
    main()
