"""
n17 — The modular test: does the thermodynamic (Jacobson/Verlinde) form
of "gravity = entanglement bookkeeping" survive the intervention that
killed E_N and I as force carriers (n16)?

MEASURED (July 2026), three verdicts:
 P1. The first law holds exactly: dS(A) = d<K_A> to 0.1% (thermal and
     vacuum, ratio -> 1.000000 for infinitesimal perturbations). The
     Gaussian modular Hamiltonian is G = 2 arccoth(2 i Omega sigma) i Omega
     -- note the operator ORDER: our first implementation had i Omega on
     the left, wrong for noncommuting cases; caught against the exact
     thermal-chain case (G = blockdiag(K, I)/T, matched to 1e-15) and
     kept on the record.
 P2. No modular temperature exists even statically: T_eff = F/(dS_screen/dd)
     drifts monotonically x1.9 over d = 14-26. The entropic force law
     F = T dS/dx has no universal T in this arena.
 P3 + 5b. The last live thread -- the screen-entropy gradient flips sign
     WITH the true force under mid-channel noise (4/4) -- was executed by
     moving the noise outside the pair: sign concordance breaks (5/6
     disagreements across the two outside geometries). One more common
     cause: both quantities tracked the radiation gradient, not each other.

CONCLUSION: in this arena the force has no information-theoretic carrier
at all. Entanglement (n16), total correlations (n16), and entropy
gradients (here) are all non-causal shadows of the propagator that
carries the force. What survives of book one: the first law as an exact
identity, and whatever interacting/holographic regimes this Gaussian 1D
arena cannot reach. Runtime: ~5 minutes.
"""

import numpy as np
from scipy.linalg import solve_continuous_lyapunov, eigh

N       = 256
MU2     = 2.5e-3
LABS    = 28
ETA_MAX = 0.5
ETA_B   = 0.005
LAM     = 1.0
A0      = 100
CUT     = 111          # ecran = sites [0, CUT)
MIDS    = [109, 110, 111, 112]

# ------------------------------------------------------------- modele
def K_matrix(defects=()):
    K = np.diag(np.full(N, 2.0 + MU2)) \
        - np.diag(np.ones(N - 1), 1) - np.diag(np.ones(N - 1), -1)
    for s in defects:
        K[s, s] += LAM
    return K

def ground_cov(K):
    w2, U = eigh(K)
    w = np.sqrt(np.maximum(w2, 1e-14))
    sig = np.zeros((2 * N, 2 * N))
    sig[:N, :N] = U @ np.diag(0.5 / w) @ U.T
    sig[N:, N:] = U @ np.diag(0.5 * w) @ U.T
    return sig

def ground_E0(K):
    w2 = eigh(K, eigvals_only=True)
    return 0.5 * np.sum(np.sqrt(np.maximum(w2, 0)))

def ness(K, gamma=0.0):
    eta = np.full(N, ETA_B)
    for j in range(LABS):
        s = ((LABS - j) / LABS) ** 2
        eta[j] += ETA_MAX * s
        eta[N - 1 - j] += ETA_MAX * s
    E = np.diag(np.concatenate([eta, eta]))
    A_H = np.zeros((2 * N, 2 * N))
    A_H[:N, N:] = np.eye(N)
    A_H[N:, :N] = -K
    A = A_H - E / 2
    sgs = ground_cov(K)
    D = 0.5 * (E @ sgs + sgs @ E)
    for i in MIDS:
        D[N + i, N + i] += gamma
    return solve_continuous_lyapunov(A, -D)

# ------------------------------------------------------- info gaussienne
def block_cov(sig, sites):
    idx = list(sites) + [N + s for s in sites]
    return sig[np.ix_(idx, idx)]

def symp_eigs(sr):
    n = len(sr) // 2
    Om = np.zeros((2 * n, 2 * n))
    Om[:n, n:] = np.eye(n); Om[n:, :n] = -np.eye(n)
    ev = np.linalg.eigvals(1j * Om @ sr)
    nu = np.sort(np.abs(ev))
    return nu[::2]

def entropy(sr):
    S = 0.0
    for v in symp_eigs(sr):
        v = max(v, 0.5 + 1e-12)
        S += (v + .5) * np.log(v + .5) - (v - .5) * np.log(v - .5)
    return S

def modular_G(sr):
    """G tel que rho ~ exp(-1/2 X^T G X) : G = 2 i Omega arccoth(2 i Omega sigma)."""
    n = len(sr) // 2
    Om = np.zeros((2 * n, 2 * n))
    Om[:n, n:] = np.eye(n); Om[n:, :n] = -np.eye(n)
    M = 1j * Om @ sr
    lam, V = np.linalg.eig(M)
    lam = np.where(np.abs(lam) < 0.5 + 1e-10,
                   np.sign(lam.real) * (0.5 + 1e-10), lam)
    f = np.arctanh(1.0 / (2 * lam))          # arccoth(2x) = artanh(1/(2x))
    Gm = V @ np.diag(f) @ np.linalg.inv(V)
    return (2 * Gm @ (1j * Om)).real         # G = 2 f(i Omega sigma) i Omega

# ------------------------------------------------------- force (stress)
def stress(sig, K):
    sxx = sig[:N, :N]; spp = sig[N:, N:]
    pp = 0.5 * (np.diag(spp)[:-1] + np.diag(spp)[1:])
    dx2 = np.diag(sxx)[:-1] + np.diag(sxx)[1:] - 2 * np.diag(sxx, 1)
    xx = 0.5 * (np.diag(sxx)[:-1] + np.diag(sxx)[1:])
    return 0.5 * pp + 0.5 * dx2 - 0.5 * MU2 * xx

def force_on_B(d, gamma, off=5):
    """Force d'interaction sur B (>0 = vers A = attraction), ABBA."""
    b = A0 + d
    T = {}
    for tag, dl in (("00", ()), ("A0", (A0,)), ("0B", (b,)), ("AB", (A0, b))):
        K = K_matrix(dl)
        T[tag] = stress(ness(K, gamma), K)
    dT = T["AB"] - T["A0"] - T["0B"] + T["00"]
    return dT[b + off - 1] - dT[b - off]

# =====================================================================
if __name__ == "__main__":
    # ---- P1 : premiere loi, etat fondamental exact ----
    print("--- P1. premiere loi dS(A) = dTr(G_A sigma)/2, bloc de 12 autour de A0 ---")
    Ablk = list(range(A0 - 6, A0 + 6))
    d0 = 18
    sig0 = ground_cov(K_matrix((A0, A0 + d0)))
    G_A = modular_G(block_cov(sig0, Ablk))
    for dd in (1, 2):
        sp = block_cov(ground_cov(K_matrix((A0, A0 + d0 + dd))), Ablk)
        sm = block_cov(ground_cov(K_matrix((A0, A0 + d0 - dd))), Ablk)
        dS = (entropy(sp) - entropy(sm)) / 2
        dK = 0.5 * np.trace(G_A @ (sp - sm)) / 2
        print(f"  delta={dd}: dS = {dS:+.6e}, d<K> = {dK:+.6e}, ratio = {dS/dK:.4f}")
    print()

    # ---- P2 : statique — S_ecran(d) vs F(d) ----
    print("--- P2. statique : F = T_eff dS_ecran/dd ? (A fixe, B mobile, coupure fixe) ---")
    print(f"{'d':>4} {'F (dE0/dd)':>12} {'dS/dd':>12} {'T_eff=F/(dS/dd)':>16}")
    screen = list(range(CUT))
    stat = {}
    for d in (14, 16, 18, 20, 22, 24, 26):
        E = {dd: ground_E0(K_matrix((A0, A0 + dd))) for dd in (d - 1, d + 1)}
        F = (E[d + 1] - E[d - 1]) / 2
        S = {dd: entropy(block_cov(ground_cov(K_matrix((A0, A0 + dd))), screen))
             for dd in (d - 1, d + 1)}
        dS = (S[d + 1] - S[d - 1]) / 2
        stat[d] = (F, dS)
        print(f"{d:>4} {F:>12.4e} {dS:>12.4e} {F/dS if abs(dS)>0 else float('nan'):>16.4e}")
    Teff = np.median([F / dS for F, dS in stat.values()])
    print(f"  T_eff (mediane) = {Teff:.4e}")
    print()

    # ---- P3 : intervention ----
    print("--- P3. intervention : F_vraie vs F_mod = T_eff dS/dd (d=18) ---")
    print(f"{'gamma':>8} {'F_vraie':>12} {'dS/dd':>12} {'F_mod':>12} {'meme signe?':>12}")
    d = 18
    for g in (0.0, 0.01, 0.03, 0.1):
        F = force_on_B(d, g)
        Ss = {dd: entropy(block_cov(ness(K_matrix((A0, A0 + dd)), g), screen))
              for dd in (d - 1, d + 1)}
        dS = (Ss[d + 1] - Ss[d - 1]) / 2
        Fm = Teff * dS
        print(f"{g:>8} {F:>12.4e} {dS:>12.4e} {Fm:>12.4e} {str(np.sign(F)==np.sign(Fm)):>12}")

    # ---- 5b : le dephaseur HORS de la paire (test de cause commune) ----
    print("--- 5b. dephaseur exterieur : la concordance de signe casse ---")
    Teff = 1.7312e-2
    for name, mids in (("gauche de A", [90, 91, 92, 93]),
                       ("droite de B", [124, 125, 126, 127]),
                       ("centre (rappel P3)", [109, 110, 111, 112])):
        globals()['MIDS'][:] = mids
        print(f"  {name}:")
        for g in (0.01, 0.03, 0.1):
            F = force_on_B(18, g)
            Ss = {dd: entropy(block_cov(ness(K_matrix((A0, A0 + dd)), g), screen))
                  for dd in (17, 19)}
            dS = (Ss[19] - Ss[17]) / 2
            print(f"    gamma={g:<5} F={F:+.3e}  T_eff*dS/dd={Teff*dS:+.3e}  "
                  f"meme signe: {np.sign(F) == np.sign(dS)}")

