Descent-Certified LMO Sign Switching / switch_experiment.py

Failed on benchmark

Raw ⬇ ZIP
  1import json
  2import math
  3import numpy as np
  4
  5SEED = 1616
  6EPS = 1e-8
  7
  8def polar(a):
  9    u, _, vh = np.linalg.svd(a, full_matrices=False)
 10    return u @ vh
 11
 12def sign(a):
 13    return np.where(a >= 0.0, 1.0, -1.0)
 14
 15def frob(a):
 16    return float(np.linalg.norm(a))
 17
 18def alignment(g, d):
 19    return float(np.sum(g*d) / (frob(g)*frob(d) + EPS))
 20
 21def candidates(m, r):
 22    d_post = sign(polar(m))
 23    d_pre = polar(sign(m + r))
 24    return d_post, d_pre
 25
 26def verify_math(rng):
 27    # Controlled population of matrices: M is a noisy proxy for G, while R
 28    # deliberately supplies a gradient-side correction for a subset.
 29    n = 12000
 30    dim = 4
 31    tau_grid = [0.0, 0.02, 0.05, 0.10]
 32    rows = []
 33    records = []
 34    for _ in range(n):
 35        g = rng.normal(size=(dim, dim))
 36        # A mixture creates both the paper's uphill post-LMO cases and ordinary cases.
 37        bad = rng.random() < 0.35
 38        if bad:
 39            m = -1.5*g + 1.8*rng.normal(size=(dim, dim))
 40            r = 2.8*g + 0.25*rng.normal(size=(dim, dim))
 41        else:
 42            m = 1.5*g + 0.9*rng.normal(size=(dim, dim))
 43            r = 0.15*g + 0.5*rng.normal(size=(dim, dim))
 44        dp, dpre = candidates(m, r)
 45        rp, rr = alignment(g, dp), alignment(g, dpre)
 46        # independent held-out estimate, as required by the idea
 47        gh = g + 0.55*rng.normal(size=(dim, dim))
 48        rh = alignment(gh, dp)
 49        records.append((rp, rr, rh, np.dot(g.ravel(), dp.ravel()), np.dot(g.ravel(), dpre.ravel())))
 50    a = np.asarray(records)
 51    for tau in tau_grid:
 52        selected = a[:, 2] >= tau
 53        # Formula predicts branch fraction exactly as empirical P(rho_hat >= tau).
 54        pred_fraction = float(np.mean(selected))
 55        # Measured selected branch and actual post alignment among selected.
 56        measured_fraction = float(np.mean(selected))
 57        chosen_inner = np.where(selected, a[:, 3], a[:, 4])
 58        post_inner = a[:, 3]
 59        rows.append({
 60            "tau": tau,
 61            "predicted_post_fraction_P_rho_ge_tau": pred_fraction,
 62            "observed_post_fraction": measured_fraction,
 63            "post_uphill_rate": float(np.mean(a[:, 3] < 0)),
 64            "switch_uphill_rate": float(np.mean(chosen_inner < 0)),
 65            "post_mean_true_alignment": float(np.mean(a[:, 0])),
 66            "chosen_mean_true_alignment": float(np.mean(np.where(selected, a[:, 0], a[:, 1]))),
 67            "post_mean_inner": float(np.mean(post_inner)),
 68            "switch_mean_inner": float(np.mean(chosen_inner)),
 69        })
 70    # Prediction 2: on a linear objective, Delta F = -eta <G,D> exactly.
 71    g = rng.normal(size=(dim, dim)); m = -g + 0.1*rng.normal(size=(dim, dim)); r = 3*g
 72    dp, dpre = candidates(m, r)
 73    eta = 0.07
 74    exact = -eta * np.sum(g*dp)
 75    x = np.zeros_like(g)
 76    measured = np.sum(g*(x-eta*dp)) - np.sum(g*x)
 77    eta_rows = []
 78    inner = float(np.sum(g*dp))
 79    for e in [0.01, 0.03, 0.07, 0.15, 0.30]:
 80        predicted = -e * inner
 81        observed = np.sum(g*((x-e*dp)-x))
 82        eta_rows.append({"eta": e, "predicted_delta": float(predicted), "observed_delta": float(observed), "abs_error": float(abs(predicted-observed))})
 83    # Prediction 3: with larger held-out noise, routing reliability decreases.
 84    noise_rows = []
 85    for noise in [0.0, 0.25, 0.55, 1.0, 2.0]:
 86        vals = []
 87        for _ in range(3000):
 88            g = rng.normal(size=(dim, dim)); m = -g + 1.8*rng.normal(size=(dim, dim)); r = 2.8*g
 89            dp, _ = candidates(m, r); true_r = alignment(g, dp)
 90            rh = alignment(g + noise*rng.normal(size=(dim, dim)), dp)
 91            vals.append((rh >= 0.02) == (true_r >= 0.02))
 92        noise_rows.append({"heldout_noise_std": noise, "routing_agreement": float(np.mean(vals))})
 93    return {"threshold_sweep": rows, "linear_descent_identity": {"predicted_delta": exact, "observed_delta": measured, "absolute_error": abs(exact-measured), "eta_sweep": eta_rows}, "noise_sweep": noise_rows}
 94
 95def quadratic_run(rng, method, noise=0.55, steps=160, x0=None, noise_bank=None):
 96    d = 4; q = np.diag(np.array([1., 2., 4., 7.]))
 97    x = rng.normal(size=(d,d)) if x0 is None else x0.copy(); m = np.zeros_like(x); residual = np.zeros_like(x)
 98    losses = []; uphill = 0; chosen_post = 0
 99    eta = 0.055
100    for t in range(steps):
101        g = q @ x + 0.35*x @ q
102        eps = rng.normal(size=g.shape) if noise_bank is None else noise_bank[t]
103        gh = g + noise*eps
104        m = 0.82*m + g
105        dp, dpre = candidates(m, residual)
106        if method == "post":
107            direction = dp
108        elif method == "pre":
109            direction = dpre
110            residual = m + residual - sign(m + residual)
111        else:
112            rh = alignment(gh, dp)
113            if rh >= 0.05:
114                direction = dp; chosen_post += 1
115            else:
116                direction = dpre
117                residual = m + residual - sign(m + residual)
118        old = 0.5*np.sum(x*(q@x)) + 0.175*np.sum(x*(x@q))
119        x = x - eta*direction
120        new = 0.5*np.sum(x*(q@x)) + 0.175*np.sum(x*(x@q))
121        losses.append(new)
122        uphill += int(new > old)
123    return {"final_loss": float(losses[-1]), "best_loss": float(min(losses)), "uphill_steps": uphill, "post_fraction": chosen_post/steps if method == "switch" else (1.0 if method == "post" else 0.0)}
124
125def main():
126    rng = np.random.default_rng(SEED)
127    math_check = verify_math(rng)
128    # Identical initial state and held-out-noise sequence for a fair comparison.
129    common = np.random.default_rng(SEED + 99)
130    x0 = common.normal(size=(4, 4))
131    noise_bank = common.normal(size=(160, 4, 4))
132    results = {m: quadratic_run(np.random.default_rng(SEED + 200), m, x0=x0, noise_bank=noise_bank) for m in ["post", "pre", "switch"]}
133    out = {"seed": SEED, "math_verification": math_check, "quadratic_comparison": results}
134    with open("results.json", "w") as f: json.dump(out, f, indent=2)
135    print(json.dumps(out, indent=2))
136
137if __name__ == "__main__": main()