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

Computation 150 -- Scope of the L_comm uniform bound, and the m(k)/N mechanism
================================================================================
Computation 65 measured G(k, N) := |gap(k, N)| * N over k in [1, 16],
N in [12, 32] and found max G = 5.249 at (k, N) = (15, 20).  The book carried
that forward as "G <= 5.3 uniformly in (k, N)".

This computation establishes three things about that statement.

(1) IT IS NOT UNIFORM IN (k, N).  Extending the sweep to k <= 40 finds
    G = 22.0 at (k, N) = (39, 22).  Comp 65 stopped at k = 16 and did not see
    it.  The unrestricted claim is false.

(2) THE VIOLATIONS ARE ALL OUTSIDE THE OPERATIVE REGIME.  PST uses the Walsh
    cutoff k_D = floor(sqrt D) with the matched Bergman truncation
    N(D) = 2 sqrt D, so k <= k_D = N/2 always.  Restricted to k <= N/2 the
    bound holds with room: max G = 3.674, attained at the SMALLEST substrate
    size in range.  Comp 65's own maximum, at (15, 20), has k = 15 > k_D = 10,
    so the published sharp constant was measured at a point the theory never
    visits.

(3) THE CONTROLLING PARAMETER IS m(k)/N, AND THE MATCHED SCALING SENDS IT TO
    ZERO.  G degrades as the bridge degree becomes a finite fraction of the
    truncation -- the standard failure mode of a Toeplitz operator on a
    truncated Bergman space, and the same instability Comp 64 diagnosed for the
    c_1 fits.  Measured: m(k) <= 2 sqrt(k) for all k <= 2000.  Hence at the
    matched scaling

        m(k_D) / N(D)  <=  2 sqrt(k_D) / (2 sqrt D)  <=  D^(-1/4),

    so the blow-up region m/N -> 1/2 is never approached; it is only neared at
    the smallest D.  This is the structural reason a uniform bound exists on
    the operative regime.

CONSEQUENCE FOR THE BOOK'S ANALYTICAL ARGUMENT.  The existing argument bounds
m(k) <= sqrt D -- but that is the bound on k, not on m.  With m(k) ~ 2 sqrt k
and k <= k_D = sqrt D, the deficit is O(D^(-1/4)), which is o(1), not the O(1)
the argument concludes.  The Englis-Zhu deficit therefore VANISHES at the
matched scaling rather than merely staying bounded.

WHAT REMAINS OPEN.  An explicit analytical constant.  This computation supplies
the right variable, the mechanism and the rate; it does not derive C.

RUNTIME.  Until v32.162 part (B)'s sweep ran for hours (a dense SVD of every
commutator, up to 1722 x 1722), so this script was not runnable in the browser and the
book's numbers came from an offline run.  comp 59's norm now takes the top singular
value by Lanczos iteration on the sparse commutator, the same value to 1e-15, and the
whole sweep runs in about three minutes here and perhaps ten in the browser; comps 63,
64 and 65 are the same shape and gained the same way.  The printed output is the
offline run's, line for line.
"""
import math
import sys

import numpy as np
import numpy.linalg as la

sys.path.insert(0, "computations-se")
from computation_59_se import (  # noqa: E402
    bergman_basis, J_matrices, D_alpha, T_bridge, lcomm_ratio, m_of_k,
)

I2 = np.eye(2, dtype=complex)


def gap_times_N(k, N, cache):
    """G(k, N) = |ratio(k, N) - 2 sqrt k| * N, or None if the bridge vanishes.

    `cache` holds ONE N at a time.  The Dirac operator is a 2 len(basis) square
    complex array, 47 MB at N = 40 and more above it, and keeping every N the sweeps
    visit held a few hundred megabytes at once; the browser runtime this book
    publishes to cannot be relied on for that.  Both sweeps below walk N upwards, so
    one entry still builds each operator once per sweep and no number moves.
    """
    if N not in cache:
        cache.clear()
        basis = bergman_basis(N)
        Jp, Jm, Jz = J_matrices(basis, 0.0)
        cache[N] = (basis, D_alpha(Jp, Jm, Jz))
    basis, D_op = cache[N]
    T_b = T_bridge(basis, 0.0, k)
    if la.norm(T_b) < 1e-12:
        return None
    gap = lcomm_ratio(T_b, D_op, len(basis), I2) - 2.0 * math.sqrt(k)
    return abs(gap) * N


def main():
    print("=" * 100)
    print("  Computation 150 -- scope of the L_comm uniform bound, and the m(k)/N mechanism")
    print("=" * 100)

    # ---- (A) m(k) <= 2 sqrt k -------------------------------------------
    print()
    print("  (A) The bridge degree obeys m(k) <= 2 sqrt(k).")
    worst, wk = 0.0, None
    viol = []
    for k in range(1, 2001):
        r = m_of_k(k) / (2.0 * math.sqrt(k))
        if r > 1.0:
            viol.append(k)
        if r > worst:
            worst, wk = r, k
    print("      k = 1..2000:  violations = %s" % (viol if viol else "NONE"))
    print("      max m(k)/(2 sqrt k) = %.5f at k = %d  (ratio -> 1 from below)" % (worst, wk))

    # ---- (B) the sweep ---------------------------------------------------
    print()
    print("  (B) Sweep of G(k, N), split by the operative regime k <= k_D = N/2.")
    cache = {}
    inside, outside = [], []
    for N in range(12, 41, 2):
        for k in range(1, 41):
            G = gap_times_N(k, N, cache)
            if G is None:
                continue
            (inside if k <= N // 2 else outside).append((G, k, N))
    inside.sort(reverse=True)
    outside.sort(reverse=True)
    med = sorted(x[0] for x in inside)[len(inside) // 2]
    print("      operative  k <= N/2 : %3d points, max G = %7.4f at (k,N)=(%d,%d), median %.4f"
          % (len(inside), inside[0][0], inside[0][1], inside[0][2], med))
    print("      outside    k >  N/2 : %3d points, max G = %7.4f at (k,N)=(%d,%d)"
          % (len(outside), outside[0][0], outside[0][1], outside[0][2]))
    print()
    print("      => 'G <= 5.3 uniformly in (k, N)' is FALSE: G reaches %.2f outside the regime."
          % outside[0][0])
    print("      => on the regime PST actually uses, the sharp constant is %.3f, not 5.3."
          % inside[0][0])

    # ---- (C) G is governed by m(k)/N -------------------------------------
    print()
    print("  (C) G bucketed by m(k)/N -- the controlling parameter.")
    buckets = {}
    for G, k, N in inside + outside:
        buckets.setdefault(round(m_of_k(k) / N * 20) / 20, []).append(G)
    print("      %8s %6s %10s" % ("m(k)/N", "n", "max G"))
    for b in sorted(buckets):
        print("      %8.2f %6d %10.3f" % (b, len(buckets[b]), max(buckets[b])))

    # ---- (D) the matched scaling ----------------------------------------
    print()
    print("  (D) At the matched scaling k = k_D = floor(sqrt D), N(D) = 2 sqrt D.")
    print("      %6s %5s %5s %6s %10s %10s %10s"
          % ("D", "N", "k_D", "m(k)", "G", "m/N", "D^(-1/4)"))
    worstG = 0.0
    for N in range(12, 45, 2):
        kD = N // 2
        D = kD * kD
        G = gap_times_N(kD, N, cache)
        if G is None:
            continue
        m = m_of_k(kD)
        worstG = max(worstG, G)
        flag = "" if m / N <= D ** -0.25 + 1e-12 else "  <-- BOUND VIOLATED"
        print("      %6d %5d %5d %6d %10.4f %10.3f %10.3f%s"
              % (D, N, kD, m, G, m / N, D ** -0.25, flag))
    print()
    print("      sup over the matched-scaling sequence: G <= %.3f, attained at the smallest D."
          % worstG)
    print("      m(k_D)/N(D) <= D^(-1/4) at every point: the deficit VANISHES, o(1) not O(1).")

    print()
    print("  CONCLUSION")
    print("    - 'uniformly in (k, N)' is refuted; the bound holds on k <= k_D.")
    print("    - on that regime the sharp constant is 3.7, not 5.3.")
    print("    - m(k)/N is the controlling parameter and is O(D^(-1/4)) at matched scaling.")
    print("    - an explicit analytical constant remains open.")


if __name__ == "__main__":
    main()
