#!/usr/bin/env python3
"""
PROVENANCE: NUMERICAL

Computation 180 -- Born agreement: the atom obstruction survives on admissible graphs
======================================================================================
WHAT THIS DOES.  sec:open-structural carries Born agreement as a CONDITION: the
crossing rule eq:tau-i-explicit reproduces Born statistics iff the tension variable
is uniform in its own quantile, and comp 145 shows the condition is SATISFIABLE
while the register notes that satisfiable is not discharged.

comp 145 goes one step further than satisfiable, and that step is where this file
finds a problem.  It measures the real obstruction as ATOMS -- ties in |T(C)| that
no monotone map can separate, putting a floor KS >= m/2 on the distance from
uniform -- shows the floor vanishes for generic (Erdos-Renyi) graphs, and then sets
aside the graphs where it does NOT vanish with this sentence:

    "For regular graphs it is large -- but those are the same graphs the book
     already excludes, since T_ex fails complement-asymmetry on them."

THAT GROUND DOES NOT HOLD, AND THE FAILURES ARE NOT EXCLUDED.

WHY THE GROUND DOES NOT HOLD.  eq:ca is T(C-bar) != -T(C) generically, a condition on
the V_7-VALUED tension, and the body says in as many words what its content is:

    "the net V_7-vector T(D) is nonzero precisely when the elementary directions do
     not antipodally cancel, |sum_a v(a)| > 0.  This non-balance ... is the genuine
     content of complement-asymmetry eq:ca"

So eq:ca constrains v, the direction assignment.  It says nothing whatever about
delta, the distinction graph, and therefore excludes no graph at all.  Measured in
part 2: on the magnitude reading the complete graph satisfies complement-asymmetry
too, so the sentence fails on its own terms as well.

AND THE FAILURES ARE ADMISSIBLE.  The book's constraints on T are eq:pos, eq:mon and
eq:ca.  Part 3 checks all three against T_ex on each graph and measures the atom
floor's scaling:

    complete K_D      admissible, largest atom 0.210, ~|D|^-0.48   vanishes
    cycle C_D         admissible, largest atom 0.190, ~|D|^-0.50   vanishes
    star graph        admissible, largest atom 0.500, ~|D|^-0.01   FLAT
    K4 + isolated     admissible, largest atom 0.375, ~|D|^-0.00   FLAT
    K6 + isolated     admissible, largest atom 0.312, ~|D|^+0.00   FLAT
    Erdos-Renyi 0.5   admissible, largest atom 0.068, ~|D|^-1.38   vanishes

AND THE SENTENCE NAMES THE WRONG CLASS.  The REGULAR graphs comp 145 blamed are the
complete and the cycle, and both are fine: their floor vanishes as |D|^-0.5, slower
than generic but vanishing.  What actually fails is a different property, a BOUNDED
ACTIVE PART.  A star has one centre; K_k plus isolated vertices has a fixed clique;
in both, T_ex takes O(1) distinct values however large |D| grows.  That, and not
regularity, is the property the theory would have to exclude.

The star is the cleanest: T_ex(C) = |C and leaves| when the centre is in C and 0
otherwise, so HALF of all configurations tie at zero, at every |D|.  That atom does
not shrink, and KS >= 1/4 from uniform under every monotone reparametrisation, for
arbitrarily large substrates.

WHAT FOLLOWS.  Born agreement is not only a condition on the law of T.  It is jointly
a condition on the DISTINCTION GRAPH, and the theory currently constrains delta not at
all in the relevant respect.  Discharging the item therefore needs an argument that
the substrate's delta is not of the degenerate kind, which is a new constraint rather
than a calculation.  That is a smaller and much more specific residue than "the
condition is not discharged", and it is what this file contributes.

WHAT THIS IS NOT.  It is NOT a refutation of the crossing rule, and nothing standing
depends on it.  comp 145's generic result is confirmed here, not disputed: for
Erdos-Renyi graphs the floor vanishes as measured, and a physical substrate is
overwhelmingly more likely to be generic than to be a star.  The point is narrower and
is about what the register may claim: the item cannot be closed by the generic
argument alone, because the class where it fails is admissible.
"""

import math
import itertools
from collections import Counter

import numpy as np

RNG = np.random.default_rng(20260930)
FAMILIES = ("complete", "cycle", "erdos", "star", "K4+iso", "K6+iso")
SIZES = (10, 12, 14)


def rule():
    print("=" * 100)


def edges_of(kind, D):
    if kind == "complete":
        return list(itertools.combinations(range(D), 2))
    if kind == "cycle":
        return [(i, (i + 1) % D) for i in range(D)]
    if kind == "erdos":
        r = np.random.default_rng(5)
        return [e for e in itertools.combinations(range(D), 2) if r.random() < 0.5]
    if kind == "star":
        return [(0, i) for i in range(1, D)]
    if kind == "K4+iso":
        return list(itertools.combinations(range(4), 2))
    if kind == "K6+iso":
        return list(itertools.combinations(range(6), 2))
    raise ValueError(kind)


def tex(D, edges):
    """eq:texample, the induced edge count, over all 2^D configurations."""
    E = np.array(edges, dtype=int) if edges else np.zeros((0, 2), dtype=int)
    out = np.empty(1 << D, dtype=np.int64)
    for m in range(1 << D):
        inC = np.array([(m >> a) & 1 for a in range(D)], dtype=bool)
        out[m] = int(np.sum(inC[E[:, 0]] & inC[E[:, 1]])) if len(E) else 0
    return out


def largest_atom(v):
    return max(Counter(v.tolist()).values()) / len(v)


def has_distinct_pair(D, edges):
    E = np.array(edges, dtype=int) if edges else np.zeros((0, 2), dtype=int)
    out = np.zeros(1 << D, dtype=bool)
    for m in range(1 << D):
        inC = np.array([(m >> a) & 1 for a in range(D)], dtype=bool)
        out[m] = bool(np.any(inC[E[:, 0]] & inC[E[:, 1]])) if len(E) else False
    return out


def ks_uniform(x):
    x = np.asarray(x, float)
    if np.ptp(x) == 0:
        return 1.0
    u = np.sort((x - x.min()) / np.ptp(x))
    n = len(u)
    grid = (np.arange(1, n + 1)) / n
    return float(np.max(np.abs(u - grid)))


# ---------------------------------------------------------------------------
def part1():
    rule()
    print("  1.  comp 145's GENERIC RESULT, CONFIRMED AND NOT DISPUTED")
    rule()
    print("      The obstruction is atoms: ties in T_ex survive every monotone map and")
    print("      put a floor KS >= m/2 on the distance from uniform.  For Erdos-Renyi")
    print("      graphs the floor vanishes with |D|, which is comp 145's result.")
    print()
    print(f"      {'|D|':>6}{'largest atom':>16}{'floor m/2':>12}")
    ats = []
    for D in SIZES:
        a = largest_atom(tex(D, edges_of("erdos", D)))
        ats.append(a)
        print(f"      {D:>6}{a:>16.4f}{a/2:>12.4f}")
    slope = np.polyfit(np.log(SIZES), np.log(ats), 1)[0]
    ok = slope < -1.0
    print()
    print(f"      fit: atom ~ |D|^{slope:+.2f}, so it vanishes           : {ok}")
    print()
    return ok


def part2():
    rule()
    print("  2.  THE GROUND comp 145 GIVES FOR SETTING THE FAILURES ASIDE DOES NOT HOLD")
    rule()
    print("      comp 145: 'For regular graphs it is large -- but those are the same")
    print("      graphs the book already excludes, since T_ex fails complement-asymmetry")
    print("      on them.'")
    print()
    print("      eq:ca is T(C-bar) != -T(C) generically, a condition on the V_7-VALUED")
    print("      tension, and the body states its content outright: the net vector T(D)")
    print("      is nonzero exactly when the elementary directions do not antipodally")
    print("      cancel, |sum_a v(a)| > 0, and 'this non-balance is the genuine content")
    print("      of complement-asymmetry eq:ca'.")
    print()
    print("      So eq:ca constrains v.  It says nothing about delta and excludes no")
    print("      graph.  And on the magnitude reading it does not exclude them either:")
    print()
    print(f"      {'family':>12}{'|D|':>5}{'T_ex(C) != T_ex(C-bar) for some C':>36}")
    allok = True
    for kind in ("complete", "cycle"):
        for D in (10, 12):
            v = tex(D, edges_of(kind, D))
            full = (1 << D) - 1
            asym = any(v[m] != v[full ^ m] for m in range(1 << D))
            allok &= asym
            print(f"      {kind:>12}{D:>5}{str(asym):>36}")
    print()
    print("      Both regular, both complement-asymmetric on that reading.  The")
    print("      exclusion the sentence relies on does not exist, on either reading.")
    print()
    print("      AND THE SENTENCE NAMES THE WRONG CLASS ANYWAY.  Part 3 measures the")
    print("      regular graphs' floor VANISHING, as |D|^-0.5: slower than generic, but")
    print("      vanishing.  The graphs that actually fail are not regular at all.")
    print()
    print(f"      -> the stated ground fails                            : {allok}")
    print()
    return allok


def part3():
    rule()
    print("  3.  AND THE FAILING GRAPHS ARE ADMISSIBLE.  ALL THREE CONSTRAINTS CHECKED")
    rule()
    print("      eq:pos  |T(C)| = 0 iff C has no distinct pairs")
    print("      eq:mon  |T(C u {a})| >= |T(C)|")
    print("      eq:ca   a condition on v, satisfiable independently of delta")
    print()
    print(f"      {'family':>12}{'eq:pos':>9}{'eq:mon':>9}{'atom |D|=10':>13}"
          f"{'|D|=12':>9}{'|D|=14':>9}{'trend':>13}")
    flat, verdicts = [], True
    for kind in FAMILIES:
        ats, pos_ok, mon_ok = [], True, True
        for D in SIZES:
            E = edges_of(kind, D)
            v = tex(D, E)
            ats.append(largest_atom(v))
            pos_ok &= bool(np.all((v == 0) == (~has_distinct_pair(D, E))))
            for m in range(1 << D):
                for a in range(D):
                    if not (m >> a) & 1 and v[m | (1 << a)] < v[m]:
                        mon_ok = False
                        break
                if not mon_ok:
                    break
        sl = np.polyfit(np.log(SIZES), np.log(ats), 1)[0]
        tag = "FLAT" if sl > -0.3 else "vanishes"
        if tag == "FLAT":
            flat.append(kind)
        verdicts &= pos_ok and mon_ok
        print(f"      {kind:>12}{str(pos_ok):>9}{str(mon_ok):>9}{ats[0]:>13.4f}"
              f"{ats[1]:>9.4f}{ats[2]:>9.4f}  {tag+f' ({sl:+.2f})':<18}")
    print()
    print("      Every family satisfies eq:pos and eq:mon, so every one is admissible,")
    print("      and eq:ca is about v and cannot exclude any of them.  Yet these have")
    print(f"      an atom floor that does not vanish: {', '.join(flat)}.")
    print()
    print("      NOTE WHICH ONES.  The regular graphs, which comp 145 blamed, are fine:")
    print("      their floor vanishes as |D|^-0.5.  The failures are the graphs whose")
    print("      ACTIVE PART IS BOUNDED while |D| grows: a star has one centre, and")
    print("      K_k plus isolated vertices has a fixed clique, so T_ex takes O(1)")
    print("      distinct values however large the substrate becomes.  That is the")
    print("      property the theory would have to exclude, and it is not regularity.")
    print()
    print("      The star is the cleanest.  T_ex(C) = |C and leaves| when the centre is")
    print("      in C and 0 otherwise, so HALF of all configurations tie at zero at")
    print("      every |D|: the atom is 1/2 forever and KS >= 1/4 under every monotone")
    print("      reparametrisation, for arbitrarily large substrates.")
    print()
    ok = verdicts and ("star" in flat)
    print(f"      -> admissible graphs exist with a non-vanishing floor  : {ok}")
    print()
    return ok


def part4():
    rule()
    print("  4.  WHAT FOLLOWS FOR THE REGISTER")
    rule()
    print("      Born agreement is not only a condition on the LAW OF T.  It is jointly")
    print("      a condition on the DISTINCTION GRAPH, and in the relevant respect the")
    print("      theory constrains delta not at all: eq:pos and eq:mon are satisfied by")
    print("      the degenerate graphs, and eq:ca is about v.")
    print()
    print("      So discharging the item needs an argument that the substrate's delta is")
    print("      not of the degenerate kind.  That is a NEW CONSTRAINT rather than a")
    print("      calculation, and naming it is what this file contributes.  The residue")
    print("      goes from 'the condition is not discharged' to 'the condition needs a")
    print("      genericity constraint on delta', which is smaller and much more specific.")
    print()
    return True


def part5():
    rule()
    print("  5.  SCOPE: WHAT THIS IS NOT")
    rule()
    print("      IT IS NOT A REFUTATION of the crossing rule, and nothing standing")
    print("      depends on it.  comp 145's generic result is CONFIRMED in part 1, not")
    print("      disputed: for Erdos-Renyi graphs the floor vanishes as measured.")
    print()
    print("      Nor is it a claim that the physical substrate is degenerate.  A star is")
    print("      a pathology and a real distinction structure is overwhelmingly more")
    print("      likely to be generic.  The point is narrower and is about what the")
    print("      REGISTER MAY CLAIM: the item cannot be closed by the generic argument")
    print("      alone, because the class where it fails is admissible rather than")
    print("      excluded, and comp 145's sentence saying otherwise is withdrawn.")
    print()
    return True


def part6():
    rule()
    print("  6.  TAMPER TEST: IS THE ATOM FLOOR REAL?")
    rule()
    print("      The whole argument rests on KS >= m/2 under every monotone map.  If a")
    print("      monotone reparametrisation could beat that bound the finding evaporates.")
    print("      Try to beat it: 400 random increasing maps applied to the star's law.")
    print()
    D = 12
    v = tex(D, edges_of("star", D)).astype(float)
    m = largest_atom(tex(D, edges_of("star", D)))
    lo = 1.0
    uniq = np.unique(v)
    for _ in range(400):
        knots = np.sort(RNG.random(len(uniq)))
        mapped = np.interp(v, uniq, knots)
        lo = min(lo, ks_uniform(mapped))
    print(f"      largest atom m                         : {m:.4f}")
    print(f"      floor m/2                              : {m/2:.4f}")
    print(f"      best KS found over 400 monotone maps   : {lo:.4f}")
    ok = lo >= m / 2 - 1e-9
    print()
    print("      No map beats the floor, which is what makes the obstruction structural")
    print("      rather than an artefact of the parametrisation chosen.  And the control:")
    er = tex(D, edges_of("erdos", D)).astype(float)
    m2 = largest_atom(tex(D, edges_of("erdos", D)))
    uniq2 = np.unique(er)
    lo2 = 1.0
    for _ in range(400):
        knots = np.sort(RNG.random(len(uniq2)))
        lo2 = min(lo2, ks_uniform(np.interp(er, uniq2, knots)))
    print(f"      same search on Erdos-Renyi: floor {m2/2:.4f}, best {lo2:.4f}")
    print("      which is far smaller, so the test discriminates between the families.")
    print()
    disc = lo2 < lo
    print(f"      -> the floor holds, and separates the two families    : {ok and disc}")
    print()
    return ok and disc


def main():
    print()
    rule()
    print("  COMPUTATION 180.  Born agreement: the atom obstruction survives")
    rule()
    print("""
  comp 145 measured the Born condition's obstruction as atoms, showed the floor
  vanishes for generic distinction graphs, and set the failures aside on the
  ground that the book already excludes them.  It does not: eq:ca constrains the
  direction assignment v, not the distinction graph delta.  The failing graphs
  are admissible, and one of them keeps an atom of 1/2 at every |D|.
""")
    r1, r2, r3, r4, r5, r6 = part1(), part2(), part3(), part4(), part5(), part6()

    rule()
    print("  ASSESSMENT")
    rule()
    print(f"  1. comp 145's generic result confirmed, atoms vanish there   : {r1}")
    print(f"  2. the stated ground for excluding the failures fails        : {r2}")
    print(f"  3. admissible graphs exist with a non-vanishing atom floor   : {r3}")
    print(f"  4. the residue restated: a genericity constraint on delta    : {r4}")
    print(f"  5. scope: not a refutation, and the generic case stands      : {r5}")
    print(f"  6. tamper test: no monotone map beats the floor              : {r6}")
    print()
    print("""  RESULT.  Born agreement stays a condition, and the reason it stays one is
  not the reason the register currently implies.

  comp 145 is right that the obstruction is atoms and right that they vanish for
  generic graphs; part 1 reproduces both.  What does not hold is the sentence
  setting the remaining graphs aside, that the book already excludes them because
  T_ex fails complement-asymmetry there.  eq:ca is a condition on v, and the body
  says so in as many words: its genuine content is the non-balance |sum_a v(a)| > 0.
  It constrains no distinction graph.  On the magnitude reading it does not exclude
  them either, the complete graph being regular and complement-asymmetric at once.

  And the failures are admissible on the book's own three constraints, eq:pos and
  eq:mon checked here exhaustively and eq:ca being about v.  The star graph keeps an
  atom of exactly 1/2 at every |D|, because half of all configurations omit the
  centre and tie at zero, so KS >= 1/4 under every monotone reparametrisation no
  matter how large the substrate.  Four hundred random monotone maps fail to beat
  that floor, and the same search on an Erdos-Renyi graph goes far below it.

  So Born agreement is jointly a condition on the law of T AND on delta, and the
  theory constrains delta not at all in the relevant respect.  Discharging it needs
  a genericity constraint on the distinction graph, which is a new ingredient.

  None of this touches the crossing rule or anything downstream, and it is not a
  claim that the physical substrate is degenerate.  It is a statement about what the
  register may claim: the item cannot be closed by the generic argument alone.""")
    print()
    return all([r1, r2, r3, r4, r5, r6])


if __name__ == "__main__":
    import sys
    # Exit only on FAILURE.  The website runs these in the browser under Pyodide,
    # where SystemExit is fatal even at status 0: it tears the runtime down, and the
    # page prints ERROR after output that was in fact correct.  Falling off the end
    # hands the build gates the same status 0, and a failure still exits 1.
    if not main():
        sys.exit(1)
