From 4f7ee05cc3b072478062e53645af016861c4b529 Mon Sep 17 00:00:00 2001 From: YurenHao0426 Date: Sat, 1 Aug 2026 17:59:32 -0500 Subject: The failure is the optimiser, not the information: a 257x faster descent and a benchmark The gate settles which failure mode each field is in, and refutes the symmetry hypothesis I proposed. On the caption-omitted field the truth is a STRICT local minimum -- descent started at the truth does not move at all -- the anchor bound says the information is 99.7% intact, and our solver stops 0.44 above it at 4.9% accuracy. That is a pure optimiser failure. Natural data is the opposite: descent from the truth falls a further 0.167, so the truth is not even locally optimal, which is the information-deficit signature the 0.291 bound predicted. Steepest descent was brute-forcing all 32,640 candidate permutations through the full energy every step, including a batched cube trace with the triangle term active -- 203 seconds per descent, which is why the gates were hopeless. The pairwise term needs one matrix product for the whole table: swapping p,q changes the alignment sum by 2(C_pq + C_qp - C_pp - C_qq + 2 A_pq B_pq) with C = A @ B. Verified against brute force to 1e-9 before use, and the fast descent reaches the same optimum. 203s -> 0.79s. Adds a matching benchmark with known-reachable answers and the solver families never tried on these fields: Gromov-Wasserstein, entropic GW with an annealed regulariser, BAPG. Co-Authored-By: Claude --- tests/test_core.py | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) (limited to 'tests') diff --git a/tests/test_core.py b/tests/test_core.py index 09e58f1..429eed0 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -323,3 +323,60 @@ def test_structured_phrase_encoding_separates_head_from_modifier(): np.mean([vectors[t] for t in ["red", "bus"]], axis=0), np.mean([vectors[t] for t in ["bus", "red"]], axis=0), ) + + +def test_closed_form_pair_swap_deltas_match_brute_force(): + """Every transposition's energy change, against the definition.""" + import numpy as np + from worldalign.synth_fast_gate import ( + ClosedFormEnergy, all_pair_swap_deltas, all_swaps, apply_swaps, + ) + + size = 24 + generator = torch.Generator().manual_seed(0) + def field(): + raw = torch.randn(size, size, generator=generator, dtype=torch.float64) + out = (raw + raw.T) / 2 + out.fill_diagonal_(0.0) + return out + + text, visual = field(), field() + energy = ClosedFormEnergy(text, visual, 1.0, 0.0, 64) + permutation = torch.randperm(size, generator=generator) + + base = float(energy.energy(permutation[None])[0]) + swaps = all_swaps(size, torch.device("cpu")) + brute = energy.energy(apply_swaps(permutation, swaps)) - base + + permuted = text[permutation[:, None], permutation[None, :]] + table = all_pair_swap_deltas(permuted, visual) + closed = table[swaps[:, 0], swaps[:, 1]] + + assert torch.allclose(closed, brute, atol=1e-9), ( + closed[:4].tolist(), brute[:4].tolist() + ) + + +def test_fast_pair_descent_matches_brute_force_descent(): + """The fast descent must reach the same energy as the slow one.""" + from worldalign.synth_fast_gate import ( + ClosedFormEnergy, all_swaps, fast_pair_descent, steepest_descent, + ) + + size = 24 + generator = torch.Generator().manual_seed(1) + def field(): + raw = torch.randn(size, size, generator=generator, dtype=torch.float64) + out = (raw + raw.T) / 2 + out.fill_diagonal_(0.0) + return out + + text, visual = field(), field() + energy = ClosedFormEnergy(text, visual, 1.0, 0.0, 64) + start = torch.randperm(size, generator=generator) + slow, slow_value = steepest_descent( + energy, start.clone(), all_swaps(size, torch.device("cpu")), 500 + ) + fast = fast_pair_descent(text, visual, start.clone(), 500) + fast_value = float(energy.energy(fast[None])[0]) + assert abs(fast_value - slow_value) < 1e-9, (fast_value, slow_value) -- cgit v1.2.3