#!/usr/bin/env python3
"""
N11 (v2 — extended calibration; v1 saturated its instrument and was discarded) — THE TWO METRICS: which distance does entanglement see?
==============================================================
A weighted chain carries TWO natural metrics:
    d_R(i,j) = sum 1/c_k      (resistance — circuits; 049's MASS dictionary)
    d_T(i,j) = sum 1/sqrt(c_k) (time-of-flight — waves; the causal metric)
They coincide when c = 1 and diverge inside any "lens" of weakened
springs. The Ledger reads distance from correlation decay. Which metric
does the vacuum's mutual information follow?

PRE-REGISTERED PREDICTION (before any inhomogeneous run) — REFUTED BY THE RUN:
    WKB: vacuum correlations of the massless field propagate at the
    local wave speed v = sqrt(c); MI collapses on d_T (time-of-flight),
    NOT on d_R (resistance).
KILL-GATE: if NEITHER metric collapses the lens data onto the
    homogeneous calibration curve, the dictionary thesis dies.

DESIGN: chain N=400, OBC, m=1e-3. Smooth Gaussian lens (depth c_min,
width w, tapered — adiabatic, so reflections are negligible and pure
path-length physics survives). Pairs straddle the lens; endpoints in
c=1 region (kills WKB amplitude factors). Baseline F: MI(d) homogeneous.
Inferred distance d_inf = F^{-1}(MI_lens); compare to d_R and d_T.
"""
import numpy as np, json, warnings
warnings.filterwarnings("ignore", category=RuntimeWarning)

N, m = 1200, 3e-4

def covs(c):
    K = np.zeros((N, N))
    for i in range(N - 1):
        K[i, i] += c[i]; K[i+1, i+1] += c[i]
        K[i, i+1] -= c[i]; K[i+1, i] -= c[i]
    K += np.eye(N) * m * m
    w2, U = np.linalg.eigh(K)
    w = np.sqrt(np.clip(w2, 1e-30, None))
    return (U*(0.5/w)) @ U.T, (U*(0.5*w)) @ U.T

def S(X, P, R):
    ix = np.ix_(R, R)
    nu = np.sqrt(np.clip(np.linalg.eigvals(X[ix] @ P[ix]).real, 0.25, None))
    up, dn = nu+.5, nu-.5
    return float(np.sum(up*np.log(up)) -
                 np.sum(np.where(dn > 1e-12, dn*np.log(np.clip(dn, 1e-300, None)), 0)))

def MI(X, P, i, j):
    return S(X, P, [i]) + S(X, P, [j]) - S(X, P, [i, j])

# ---------- baseline: homogeneous calibration curve F(d)
c0 = np.ones(N - 1)
X0, P0 = covs(c0)
base_d = np.array([8, 12, 16, 24, 32, 48, 64, 96, 128, 160], float)
base_I = np.array([MI(X0, P0, int(200 - d//2), int(200 + (d+1)//2)) for d in base_d])
# monotone interpolation in log-log
lg_d, lg_I = np.log(base_d), np.log(base_I)
def d_inferred(I):
    return float(np.exp(np.interp(np.log(I), lg_I[::-1], lg_d[::-1])))

print("calibration F(d):", "  ".join(f"d={int(d)}:I={i:.4f}" for d, i in zip(base_d, base_I)))

# ---------- the lens: smooth profile, two depths
def lens(cmin, center=200, width=30):
    x = np.arange(N - 1) + 0.5
    return 1.0 - (1.0 - cmin) * np.exp(-((x - center)/width)**2)

print("\nPRE-REGISTERED: MI follows d_T = sum 1/sqrt(c) (time-of-flight), not d_R = sum 1/c")
print(f"{'arm':>14} {'pair d_eucl':>11} {'d_R':>8} {'d_T':>8} {'d_inf(MI)':>10} {'verdict-leaning':>16}")
rows = []
for cmin in (0.25, 0.0625):
    c = lens(cmin)
    X, P = covs(c)
    for d in (64, 96, 128, 160):
        i, j = int(200 - d//2), int(200 + (d+1)//2)
        dR = float(np.sum(1.0/c[i:j]))
        dT = float(np.sum(1.0/np.sqrt(c[i:j])))
        I = MI(X, P, i, j)
        dinf = d_inferred(I)
        lean = "TIME-OF-FLIGHT" if abs(dinf - dT) < abs(dinf - dR) else "RESISTANCE"
        rows.append(dict(cmin=cmin, d=d, dR=dR, dT=dT, I=I, dinf=dinf, lean=lean))
        print(f"  cmin={cmin:<6} {d:11d} {dR:8.1f} {dT:8.1f} {dinf:10.1f} {lean:>16}")

# disorder arm: random log-uniform weights
rng = np.random.default_rng(7)
cd = np.exp(rng.uniform(np.log(0.4), np.log(2.5), N - 1))
Xd, Pd = covs(cd)
print("\ndisorder arm (c log-uniform [0.4, 2.5]):")
errR, errT = [], []
for (i, j) in ((100, 180), (120, 230), (80, 240), (150, 300), (60, 200), (170, 320)):
    dR = float(np.sum(1.0/cd[i:j])); dT = float(np.sum(1.0/np.sqrt(cd[i:j])))
    I = MI(Xd, Pd, i, j); dinf = d_inferred(I)
    errR.append((dinf - dR)/dR); errT.append((dinf - dT)/dT)
    print(f"  pair ({i},{j}): d_R={dR:6.1f}  d_T={dT:6.1f}  d_inf={dinf:6.1f}")
print(f"\nrms relative error: vs RESISTANCE {np.sqrt(np.mean(np.array(errR)**2)):.3f}"
      f"   vs TIME-OF-FLIGHT {np.sqrt(np.mean(np.array(errT)**2)):.3f}")
json.dump(dict(base_d=list(base_d), base_I=list(base_I), rows=rows,
               disorder=dict(errR=errR, errT=errT)),
          open('/Users/antoine/agi/ledger/n11_results.json', 'w'), indent=1)
print("-> n11_results.json")
