diff options
| author | YurenHao0426 <Blackhao0426@gmail.com> | 2026-08-01 17:59:32 -0500 |
|---|---|---|
| committer | YurenHao0426 <Blackhao0426@gmail.com> | 2026-08-01 17:59:32 -0500 |
| commit | 4f7ee05cc3b072478062e53645af016861c4b529 (patch) | |
| tree | e7dc4e428f3dbe199b40a23be49019846430128e /tests | |
| parent | f29c41e78da10d3c40afdd2deeb43c4d73f5eb43 (diff) | |
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 <noreply@anthropic.com>
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test_core.py | 57 |
1 files changed, 57 insertions, 0 deletions
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) |
