summaryrefslogtreecommitdiff
path: root/artifacts/spectral_frontier_probe/eval_voteanchor_vs_bestE.py
blob: d97d2396b514c738e57630d3f8689643d74904cf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
"""Replication with BLIND energy selection over the (m,K) grid, two fresh hidden perms."""
import sys, time, numpy as np, torch
sys.path.insert(0, '/home/yurenh2/emm')
from scipy.optimize import linear_sum_assignment
from scipy.stats import ortho_group
from worldalign.synth_fast_gate import fast_pair_descent

dev = 'cuda:0'
def standardise(M):
    M = np.asarray(M, dtype=np.float64); m = ~np.eye(len(M), dtype=bool); v = M[m]
    o = (M - v.mean())/v.std(); np.fill_diagonal(o, 0.0); return o
d = torch.load('/home/yurenh2/emm/artifacts/synth_v1/omit_size.pt', map_location='cpu', weights_only=False)
V = standardise(d['visual_field']); T0 = standardise(d['text_field']); N = len(V)
Vt = torch.tensor(V, dtype=torch.float32, device=dev)
wV, UV = np.linalg.eigh(V); wV = wV[::-1]; UV = UV[:, ::-1]

def hung(A, B):
    C = ((A**2).sum(1)[:, None] + (B**2).sum(1)[None, :] - 2*A@B.T)
    r, c = linear_sum_assignment(C); return c
def icp(XV, XT, O, iters=25):
    for _ in range(iters):
        p = hung(XV, XT@O); u, s, vt = np.linalg.svd(XT[p].T@XV); On = u@vt
        if np.allclose(On, O, atol=1e-10): O = On; break
        O = On
    return hung(XV, XT@O)

for SEED in (11, 23):
    rng = np.random.default_rng(SEED); sigma = rng.permutation(N)
    T = T0[np.ix_(sigma, sigma)]; truth = np.argsort(sigma)
    acc = lambda p: float((np.asarray(p) == truth).mean())
    Tt = torch.tensor(T, dtype=torch.float32, device=dev)
    CONST = float((Tt*Tt).sum() + (Vt*Vt).sum())
    def energy(p):
        P = torch.as_tensor(np.asarray(p), dtype=torch.long, device=dev)
        return (CONST - 2.0*float((Tt[P[:, None], P[None, :]]*Vt).sum()))/(N*(N-1))
    def descend(p, steps=4000):
        P = torch.as_tensor(np.ascontiguousarray(p), dtype=torch.long, device=dev)
        return fast_pair_descent(Tt, Vt, P, steps).cpu().numpy()
    wT, UT = np.linalg.eigh(T); wT = wT[::-1]; UT = UT[:, ::-1]
    t0 = time.time(); sols = []
    for r in (8, 10, 12, 14, 16):
        XV = UV[:, :r]*np.sqrt(np.abs(wV[:r])); XT = UT[:, :r]*np.sqrt(np.abs(wT[:r]))
        for _ in range(30):
            p = icp(XV, XT, ortho_group.rvs(r, random_state=int(rng.integers(1 << 30))))
            sols.append((energy(p), p, r))
    sols.sort(key=lambda z: z[0])
    print(f"\n##### hidden seed {SEED}: {len(sols)} ICP sols in {time.time()-t0:.0f}s, "
          f"E(truth)={energy(truth):.4f}, best member E={sols[0][0]:.4f} acc={acc(sols[0][1]):.3f}", flush=True)
    A = [descend(p) for _, p, _ in sols[:5]]
    bA = min(A, key=energy)
    print(f"  ROUTE A (descend 5 lowest-E, blind pick): E={energy(bA):.4f} acc={acc(bA):.3f}", flush=True)

    def expand(aL, aR):
        pr = np.setdiff1d(np.arange(N), aL); prR = np.setdiff1d(np.arange(N), aR)
        L = V[np.ix_(pr, aL)]; R = T[np.ix_(prR, aR)]
        L = (L-L.mean(1, keepdims=True))/L.std(1, keepdims=True).clip(1e-9)
        R = (R-R.mean(1, keepdims=True))/R.std(1, keepdims=True).clip(1e-9)
        S = L@R.T/L.shape[1]; _, c = linear_sum_assignment(-S)
        full = np.zeros(N, dtype=int); full[aL] = aR; full[pr] = prR[c]; return full
    grid = []
    for m in (10, 30, 60):
        votes = np.zeros((N, N))
        for _, p, _ in sols[:m]: votes[np.arange(N), p] += 1
        conf = votes.max(1) - np.partition(votes, -2, axis=1)[:, -2]
        order = np.argsort(-conf); pm = votes.argmax(1)
        for K in (12, 25, 50, 100):
            sel = order[:K]; part = pm[sel]; keep = np.zeros(len(sel), bool); seen = set()
            for i, c in enumerate(part):
                if c not in seen: seen.add(c); keep[i] = True
            sel, part = sel[keep], part[keep]
            prec = float((part == truth[sel]).mean())
            e = expand(sel, part); pd = descend(e)
            grid.append((energy(pd), acc(pd), m, K, prec, acc(e)))
            print(f"   m={m:2d} K={K:3d} prec={prec:.3f} expand={acc(e):.3f} -> descent acc={acc(pd):.3f} E={energy(pd):.4f}", flush=True)
    grid.sort()
    print(f"  ROUTE B (blind pick by energy over the 12 cells): m={grid[0][2]} K={grid[0][3]} "
          f"E={grid[0][0]:.4f} acc={grid[0][1]:.3f}   [best cell by accuracy was {max(g[1] for g in grid):.3f}]", flush=True)
    print(f"  wall {time.time()-t0:.0f}s", flush=True)