summaryrefslogtreecommitdiff
path: root/artifacts/spectral_frontier_probe/eval_impure_anchors.py
diff options
context:
space:
mode:
authorYurenHao0426 <Blackhao0426@gmail.com>2026-08-01 21:37:55 -0500
committerYurenHao0426 <Blackhao0426@gmail.com>2026-08-01 21:37:55 -0500
commit22acd2899958def0d103f11da49c2c4a499be773 (patch)
tree892b422c1e04cd9854420eda0c965e21df81c8db /artifacts/spectral_frontier_probe/eval_impure_anchors.py
parentef104fe4f07713bf11f266d2954b7446f176f8ae (diff)
The missing term was unary: omit-size solved at its ceiling in 1.4s
Adversarial review of the artifacts found three of my numbers to be artifacts of my own code. All three reproduced here before acceptance: - anchor_bound presented probe rows in the same index order on both sides, so exact twins had their tie broken onto the diagonal. 0.997 -> 0.920 on omit-size. Fixed by scrambling the T-side presentation. - The truth is not a strict local minimum: 51 transpositions have exactly zero energy delta. fast_pair_descent only looked stationary because its break test treats zero as no-improvement. - scipy's FAQ takes no n_init, so it was swallowed into unknown_options and 'FAQ x30 restarts' computed bit-identically to plain FAQ. Replaced with a real restart loop over P0='randomized'. The blind ceiling for omit-size is 0.836, not 1.0: the text field has 51 exact transposition automorphisms, so T[s,s] is bitwise identical to T and no objective f(V, P T P^T) can separate an orbit at any order. Every synthetic accuracy was being divided by the wrong denominator. The fifteen failed solvers share one property -- all purely quadratic or purely spectral, none with a node-level term. Eight moments of each node's own field row, blended with the quadratic term through Frank-Wolfe, reach 0.837 with an energy gap of exactly zero. The term must stay in the loop: as a seed for pure-quadratic descent it scores 0.21, pinned through the iterations it scores 0.84 -- which is also why amplification plateaued, being itself pure-quadratic. Co-Authored-By: Claude <noreply@anthropic.com>
Diffstat (limited to 'artifacts/spectral_frontier_probe/eval_impure_anchors.py')
-rw-r--r--artifacts/spectral_frontier_probe/eval_impure_anchors.py65
1 files changed, 65 insertions, 0 deletions
diff --git a/artifacts/spectral_frontier_probe/eval_impure_anchors.py b/artifacts/spectral_frontier_probe/eval_impure_anchors.py
new file mode 100644
index 0000000..f6b4397
--- /dev/null
+++ b/artifacts/spectral_frontier_probe/eval_impure_anchors.py
@@ -0,0 +1,65 @@
+import numpy as np, torch
+from scipy.optimize import linear_sum_assignment
+
+def standardise(M):
+ M = np.asarray(M, dtype=np.float64); mask = ~np.eye(len(M), dtype=bool); v = M[mask]
+ out = (M - v.mean()) / v.std(); np.fill_diagonal(out, 0.0); return out
+
+d = torch.load('/home/yurenh2/emm/artifacts/synth_v1/omit_size.pt', map_location='cpu', weights_only=False)
+V = standardise(d['visual_field']); T = standardise(d['text_field']); N = len(V)
+truth = np.arange(N)
+
+def expand(anchorL, anchorR):
+ """anchorL: vision indices; anchorR: claimed text partner. Returns full accuracy."""
+ probe = np.setdiff1d(np.arange(N), anchorL)
+ # probe scenes on the text side are whatever is not claimed
+ probeR = np.setdiff1d(np.arange(N), anchorR)
+ L = V[np.ix_(probe, anchorL)]; R = T[np.ix_(probeR, anchorR)]
+ 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]
+ _, cols = linear_sum_assignment(-S)
+ resolved = probeR[cols]
+ n_ok = int((resolved == probe).sum()) + int((anchorR == anchorL).sum())
+ return n_ok / N
+
+rng = np.random.default_rng(0)
+REP = 20
+print("=== (a) PURE random anchors: reproduce the published curve ===")
+for K in (12, 25, 40, 50, 64, 128):
+ a = [expand(*(lambda s: (s, s))(np.sort(rng.choice(N, K, replace=False)))) for _ in range(REP)]
+ print(f" K={K:4d} pure accuracy {np.mean(a):.3f} +- {np.std(a):.3f}")
+
+print("\n=== (b) IMPURE random anchors: c correct, K-c wrong (wrong = derangement among selected) ===")
+for K in (12, 25, 40, 50, 100):
+ for prec in (1.0, 0.92, 0.88, 0.84, 0.80, 0.72, 0.60):
+ nb = int(round(K * (1 - prec)))
+ if nb == 1: nb = 2 # a single wrong pair is impossible inside a bijection
+ accs = []
+ for _ in range(REP):
+ sel = np.sort(rng.choice(N, K, replace=False))
+ right = sel.copy()
+ if nb >= 2:
+ bad = rng.choice(K, nb, replace=False)
+ sh = right[bad].copy()
+ while True:
+ perm = rng.permutation(nb)
+ if not (perm == np.arange(nb)).any(): break
+ right[bad] = sh[perm]
+ accs.append(expand(sel, right))
+ print(f" K={K:4d} prec={prec:.2f} ({K-nb}/{K} right) accuracy {np.mean(accs):.3f} +- {np.std(accs):.3f}")
+ print()
+
+print("=== (c) IMPURE with wrong partners drawn from OUTSIDE the anchor set ===")
+for K in (25, 50):
+ for prec in (1.0, 0.88, 0.84, 0.80, 0.72):
+ nb = int(round(K * (1 - prec))); accs = []
+ for _ in range(REP):
+ sel = np.sort(rng.choice(N, K, replace=False))
+ right = sel.copy()
+ if nb:
+ bad = rng.choice(K, nb, replace=False)
+ outside = np.setdiff1d(np.arange(N), sel)
+ right[bad] = rng.choice(outside, nb, replace=False)
+ accs.append(expand(sel, right))
+ print(f" K={K:4d} prec={prec:.2f} accuracy {np.mean(accs):.3f} +- {np.std(accs):.3f}")