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

Computation 106 -- colour does NOT preserve the generation sectors
                   (and the earlier block-diagonality claim is withdrawn)
=========================================================================
WHAT THIS FILE USED TO CLAIM, AND WHY IT IS WITHDRAWN.

An earlier version of this computation was graded [PROOF] and concluded that
every g in SU(3)_c x SU(2)_L x U(1)_Y preserves each generation sector G_k of
the Dixon-synthesis decomposition, and hence that the absence of tree-level
flavour-changing neutral currents is a structural theorem of PST.  Two things
were wrong with it.

  (1) Its part (1) asserted that SU(3)_c acts on a colour triplet WITHIN one
      Fano-plane sub-algebra through tau-hat.  But SU(3) here IS
      Stab_{G_2}(tau-hat), which FIXES tau-hat; and each G_k carries one
      complex octonionic line, two real dimensions, which cannot carry a
      colour triplet.  The premise is dimensionally impossible, not merely
      unproven.

  (2) Its numerical "verification" built V_toy = V_gen (x) V_per-gen and
      applied kron(I_gen, X).  That is block-diagonal in the generation index
      for EVERY X, by the Kronecker identity.  The check could not fail, and
      the "wrong physics" control was non-block-diagonal for the same trivial
      reason.  The script never constructed the octonions, G_2, tau-hat or a
      Fano line.

The book's own text had it right two paragraphs earlier -- "the three
sub-algebras mix under colour SU(3)" -- and then asserted the opposite.

WHAT THIS FILE DOES NOW.  It measures the thing, from Der(O) upward:

  Part 1  build the octonions, verify the Clifford relations.
  Part 2  build Der(O) as a linear system; check dim = 14 (= g_2).
  Part 3  take the subalgebra annihilating tau-hat; check dim = 8 (= su(3)).
  Part 4  exponentiate to genuine automorphisms (verified: orthogonal, fixes
          tau-hat, respects the product), and MEASURE the leakage
          ||(1 - P_k) g P_k|| out of each generation sector.
  Part 5  compute the dimension of the subalgebra preserving ALL three Fano
          lines through tau-hat.

RESULT: the leakage is O(1), and only a two-dimensional subalgebra -- the
maximal torus U(1)^2 -- preserves the three lines.  Six of the eight gluons
are generation-changing.  SU(2)_L and U(1)_Y remain generation-blind, by the
tensor-product structure of C (x) H (x) O, and that part of the old argument
stands; it is the colour part that fails.

SCOPE.  Even a repaired version could not deliver FCNC absence.  That is a
statement about the MASS eigenbasis, and sec:yukawa-scope places the Yukawa
and CKM structure in the T(C)-contingent layer, outside what P1-P3 fix.
"""

import numpy as np


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


# ---------------------------------------------------------------------------
# Part 1.  The octonions.
# ---------------------------------------------------------------------------
# Fano triples in the Baez convention; {a,b,c} means e_a e_b = e_c cyclically.
TRIPLES = [(1, 2, 3), (1, 4, 5), (1, 7, 6), (2, 4, 6), (2, 5, 7), (3, 4, 7), (3, 6, 5)]


def octonion_structure():
    C = np.zeros((8, 8, 8))
    for i in range(8):
        C[0, i, i] = 1.0
        C[i, 0, i] = 1.0
    for i in range(1, 8):
        C[i, i, 0] = -1.0
    for (a, b, c) in TRIPLES:
        for (x, y, z) in [(a, b, c), (b, c, a), (c, a, b)]:
            C[x, y, z] += 1.0
            C[y, x, z] -= 1.0
    return C


C = octonion_structure()
E = np.eye(8)


def mul(u, v):
    return np.einsum('i,j,ijk->k', u, v, C)


def part1():
    rule()
    print("  1.  THE OCTONIONS")
    rule()
    L = [np.einsum('i,ijk->kj', E[a], C) for a in range(8)]
    err = max(np.linalg.norm(L[i] @ L[j] + L[j] @ L[i] + 2 * (i == j) * np.eye(8))
              for i in range(1, 8) for j in range(1, 8))
    norm_ok = max(abs(np.linalg.norm(mul(E[i], E[j])) - 1.0)
                  for i in range(8) for j in range(8))
    print(f"      Clifford {{L_i, L_j}} = -2 delta_ij, max residual : {err:.2e}")
    print(f"      norm-multiplicative on the basis, max error      : {norm_ok:.2e}")
    ok = err < 1e-12 and norm_ok < 1e-12
    print(f"      -> octonion table is sound                        : {ok}")
    print()
    return ok


# ---------------------------------------------------------------------------
# Part 2.  Der(O).
# ---------------------------------------------------------------------------
def derivations():
    """Linear D with D(xy) = D(x)y + x D(y); returns a basis as 8x8 matrices."""
    rows = []
    for i in range(8):
        for j in range(8):
            blk = np.zeros((8, 64))
            eij = mul(E[i], E[j])
            for a in range(8):
                for b in range(8):
                    v = np.zeros(8)
                    v[a] = eij[b]                                   # D(e_i e_j)
                    w = mul(E[a], E[j]) if b == i else np.zeros(8)  # D(e_i) e_j
                    u = mul(E[i], E[a]) if b == j else np.zeros(8)  # e_i D(e_j)
                    blk[:, a * 8 + b] = v - w - u
            rows.append(blk)
    A = np.vstack(rows)
    _, s, Vt = np.linalg.svd(A)
    return [Vt[k].reshape(8, 8) for k in range(64) if k >= len(s) or s[k] < 1e-9]


def part2(der):
    rule()
    print("  2.  Der(O) = g_2")
    rule()
    ok = len(der) == 14
    print(f"      dim Der(O) = {len(der)}   (g_2 has dimension 14)      : {ok}")
    print()
    return ok


# ---------------------------------------------------------------------------
# Part 3.  The stabiliser of tau-hat.
# ---------------------------------------------------------------------------
TAU = 7   # tau-hat = e_7 in this convention


def stabiliser(der):
    M = np.array([(d @ E[TAU]) for d in der]).T
    _, s, Vt = np.linalg.svd(M)
    coef = [Vt[k] for k in range(len(der)) if k >= len(s) or s[k] < 1e-9]
    return [sum(c[i] * der[i] for i in range(len(der))) for c in coef]


def part3(stab):
    rule()
    print("  3.  su(3) = stab_{g_2}(tau-hat)")
    rule()
    ok = len(stab) == 8
    print(f"      dim stab_{{g_2}}(e_{TAU}) = {len(stab)}   (su(3) has dimension 8) : {ok}")
    print()
    return ok


# ---------------------------------------------------------------------------
# Part 4.  The measurement: how much does colour move the generation sectors?
# ---------------------------------------------------------------------------
def fano_lines_through(t):
    return [tuple(sorted(set(tr) - {t})) for tr in TRIPLES if t in tr]


def part4(stab):
    from scipy.linalg import expm
    rule()
    print("  4.  LEAKAGE: does colour preserve the generation sectors?")
    rule()
    lines = fano_lines_through(TAU)
    print(f"      the three Fano lines through e_{TAU}: {lines}")
    print(f"      these are the octonionic slots of G_1, G_2, G_3")
    print()
    rng = np.random.default_rng(20260929)
    worst = 0.0
    print(f"      {'trial':>6}{'automorphism err':>20}{'fixes tau':>12}"
          f"{'leak G_1':>11}{'leak G_2':>11}{'leak G_3':>11}")
    for trial in range(6):
        X = sum(rng.normal() * S for S in stab)
        g = expm(X)
        auto = max(np.linalg.norm(mul(g @ E[i], g @ E[j]) - g @ mul(E[i], E[j]))
                   for i in range(8) for j in range(8))
        fix = np.linalg.norm(g @ E[TAU] - E[TAU])
        leaks = []
        for Ln in lines:
            P = sum(np.outer(E[k], E[k]) for k in Ln)
            leaks.append(float(np.linalg.norm((np.eye(8) - P) @ g @ P)))
        worst = max(worst, max(leaks))
        print(f"      {trial:>6}{auto:>20.2e}{fix:>12.2e}"
              f"{leaks[0]:>11.3f}{leaks[1]:>11.3f}{leaks[2]:>11.3f}")
    print()
    print(f"      MAX leakage over the trials = {worst:.3f}")
    print("      (0 would mean the generation sectors are preserved; they are not)")
    ok = worst > 0.1
    print(f"      -> colour MOVES the generation sectors, by an O(1) amount : {ok}")
    print()
    return ok


# ---------------------------------------------------------------------------
# Part 5.  What DOES preserve all three lines?
# ---------------------------------------------------------------------------
def part5(stab):
    rule()
    print("  5.  THE SUBALGEBRA THAT DOES PRESERVE ALL THREE LINES")
    rule()
    lines = fano_lines_through(TAU)
    blocks = []
    for Ln in lines:
        P = sum(np.outer(E[k], E[k]) for k in Ln)
        Q = np.eye(8) - P
        blocks.append(np.array([(Q @ S @ P).flatten() for S in stab]).T)
    B = np.vstack(blocks)
    s = np.linalg.svd(B, compute_uv=False)
    dim_pres = len(stab) - int((s > 1e-9).sum())
    print(f"      dim of the su(3) subalgebra preserving ALL three lines = {dim_pres}")
    print(f"      su(3) has rank 2, so this is the maximal torus U(1)^2.")
    print(f"      Of the eight gluons, {8 - dim_pres} are generation-changing.")
    ok = dim_pres == 2
    print(f"      -> only the Cartan torus is generation-blind           : {ok}")
    print()
    return ok


def main():
    print()
    rule()
    print("  COMPUTATION 106.  Colour does not preserve the generation sectors")
    rule()
    print("""
  An earlier version of this file claimed the opposite and was graded [PROOF].
  Its numerical check was a Kronecker identity that could not fail, and its
  stated mechanism was dimensionally impossible.  Both are documented in the
  docstring.  This version measures the quantity instead.
""")
    der = derivations()
    stab = stabiliser(der)
    r1, r2, r3 = part1(), part2(der), part3(stab)
    r4 = part4(stab)
    r5 = part5(stab)

    rule()
    print("  ASSESSMENT")
    rule()
    print(f"  1. octonion table sound                                  : {r1}")
    print(f"  2. dim Der(O) = 14                                       : {r2}")
    print(f"  3. dim stab(tau-hat) = 8                                 : {r3}")
    print(f"  4. colour leaks out of the generation sectors, O(1)      : {r4}")
    print(f"  5. only the maximal torus preserves all three lines      : {r5}")
    print()
    print("""  RESULT: THE BLOCK-DIAGONALITY CLAIM IS WITHDRAWN.  SU(2)_L and U(1)_Y are
  generation-blind by the tensor-product structure of C (x) H (x) O, and that half
  of the old argument stands.  SU(3)_c is not: it is Stab_{G_2}(tau-hat), it acts on
  tau-hat-perp as the fundamental, and it rotates the three Fano lines into one
  another.  Colour and generation are read off the SAME three directions, not two
  independent threes -- there is not room for two, since independent factors would
  need 3 (x) 3 in the octonionic slot, real dimension >= 18, against tau-hat-perp's 6.

  CONSEQUENCE: the absence of tree-level flavour-changing neutral currents is NOT a
  structural theorem of PST.  It would not be even with colour repaired, because FCNC
  absence is a statement about the MASS eigenbasis and sec:yukawa-scope places the
  Yukawa and CKM structure in the T(C)-contingent layer.  This is the octonionic
  programme's own open question, and PST inherits it rather than closing it.""")
    print()
    return all([r1, r2, r3, r4, r5])


if __name__ == "__main__":
    main()
