| """Tests for the read-back kernel ``Phi`` (``palimseste.phi``). |
| |
| Key behaviors: |
| - Phi returns the stored value when queried with the exact stored address |
| - Phi blends multiple matching values via weighted bundle (majority) |
| - Phi returns None on empty memory / no matches (no fabrication) |
| - radius controls neighborhood width |
| - min_weight + topk cutoffs work |
| - reconstruct_param implements Axiome 4 (W_t = Phi(bind(x, s_t))) |
| """ |
|
|
| from __future__ import annotations |
|
|
| import numpy as np |
| import pytest |
|
|
| from palimseste import hv |
| from palimseste.memory import Memory |
| from palimseste.phi import Phi, KernelConfig, Retrieval |
|
|
|
|
| def _mem_with(D=2000, n=0, seed=0) -> tuple[Memory, np.random.Generator]: |
| rng = np.random.default_rng(seed) |
| return Memory(D=D, rng=np.random.default_rng(seed)), rng |
|
|
|
|
| def test_phi_exact_recall(): |
| |
| mem, rng = _mem_with(D=2000, seed=1) |
| addr = hv.random_hv(D=2000, rng=rng) |
| val = hv.random_hv(D=2000, rng=rng) |
| mem.write(addr, val) |
| phi = Phi(config=KernelConfig(radius=0)) |
| out = phi(mem, addr) |
| assert out is not None |
| |
| assert out == val |
|
|
|
|
| def test_phi_empty_memory_returns_none(): |
| mem, _ = _mem_with(D=500, seed=2) |
| phi = Phi(config=KernelConfig(radius=50)) |
| assert phi(mem, hv.random_hv(D=500)) is None |
|
|
|
|
| def test_phi_no_match_returns_none(): |
| mem, rng = _mem_with(D=2000, seed=3) |
| mem.write(hv.random_hv(D=2000, rng=rng), hv.random_hv(D=2000, rng=rng)) |
| phi = Phi(config=KernelConfig(radius=0)) |
| |
| q = hv.random_hv(D=2000, rng=rng) |
| |
| out = phi(mem, q) |
| |
| assert out is None or out == mem.traces[0].value |
|
|
|
|
| def test_phi_blends_multiple_values(): |
| |
| |
| mem, rng = _mem_with(D=3000, seed=4) |
| base_addr = hv.random_hv(D=3000, rng=rng) |
| |
| def near(h, n): |
| s = hv.bits_to_signs(h) |
| pos = rng.choice(h.D, size=n, replace=False) |
| s[pos] = -s[pos] |
| return hv.signs_to_bits(s) |
|
|
| v_majority = hv.random_hv(D=3000, rng=rng) |
| v_minority = hv.random_hv(D=3000, rng=rng) |
| |
| mem.write(near(base_addr, 3), v_majority) |
| mem.write(near(base_addr, 4), v_majority) |
| mem.write(near(base_addr, 5), v_minority) |
| phi = Phi(config=KernelConfig(radius=20)) |
| out = phi(mem, base_addr) |
| assert out is not None |
| |
| assert hv.similarity(out, v_majority) > hv.similarity(out, v_minority) |
|
|
|
|
| def test_phi_radius_filters(): |
| mem, rng = _mem_with(D=2000, seed=5) |
| addr = hv.random_hv(D=2000, rng=rng) |
| val = hv.random_hv(D=2000, rng=rng) |
| mem.write(addr, val) |
| |
| s = hv.bits_to_signs(addr) |
| pos = rng.choice(addr.D, size=200, replace=False) |
| s[pos] = -s[pos] |
| q = hv.signs_to_bits(s) |
| phi0 = Phi(config=KernelConfig(radius=0)) |
| phi_big = Phi(config=KernelConfig(radius=500)) |
| assert phi0(mem, q) is None |
| out = phi_big(mem, q) |
| assert out is not None |
|
|
|
|
| def test_phi_topk_limits_matches(): |
| mem, rng = _mem_with(D=2000, seed=6) |
| base = hv.random_hv(D=2000, rng=rng) |
|
|
| def near(h, n): |
| s = hv.bits_to_signs(h) |
| pos = rng.choice(h.D, size=n, replace=False) |
| s[pos] = -s[pos] |
| return hv.signs_to_bits(s) |
|
|
| for _ in range(20): |
| mem.write(near(base, 5), hv.random_hv(D=2000, rng=rng)) |
| phi = Phi(config=KernelConfig(radius=50, topk=3)) |
| ret = phi.retrieve(mem, base) |
| assert ret.n_matches <= 3 |
|
|
|
|
| def test_phi_min_weight_filters(): |
| mem, rng = _mem_with(D=2000, seed=7) |
| addr = hv.random_hv(D=2000, rng=rng) |
| val = hv.random_hv(D=2000, rng=rng) |
| mem.write(addr, val, weight=1e-5) |
| phi = Phi(config=KernelConfig(radius=0, min_weight=1e-3)) |
| assert phi(mem, addr) is None |
| |
| phi_loose = Phi(config=KernelConfig(radius=0, min_weight=1e-7)) |
| assert phi_loose(mem, addr) == val |
|
|
|
|
| def test_phi_reconstruct_param_axiom4(): |
| |
| mem, rng = _mem_with(D=2000, seed=8) |
| x = hv.random_hv(D=2000, rng=rng) |
| s = hv.random_hv(D=2000, rng=rng) |
| W = hv.random_hv(D=2000, rng=rng) |
| |
| mem.write(hv.bind(x, s), W) |
| phi = Phi(config=KernelConfig(radius=0)) |
| rec = phi.reconstruct_param(mem, x, s) |
| assert rec is not None |
| assert rec == W |
|
|
|
|
| def test_kernel_config_encode_decode_roundtrip(): |
| c = KernelConfig(radius=42, min_weight=0.01, sharpness=2.0, topk=5) |
| d = c.encode() |
| c2 = KernelConfig.decode(d) |
| assert c2 == c |
|
|
|
|
| def test_kernel_config_invalid(): |
| with pytest.raises(ValueError): |
| KernelConfig(radius=-1) |
| with pytest.raises(ValueError): |
| KernelConfig(min_weight=0.0) |
| with pytest.raises(ValueError): |
| KernelConfig(topk=0) |
|
|
|
|
| def test_retrieval_introspection(): |
| mem, rng = _mem_with(D=2000, seed=9) |
| addr = hv.random_hv(D=2000, rng=rng) |
| val = hv.random_hv(D=2000, rng=rng) |
| mem.write(addr, val) |
| phi = Phi(config=KernelConfig(radius=10)) |
| ret = phi.retrieve(mem, addr) |
| assert isinstance(ret, Retrieval) |
| assert ret.n_matches == 1 |
| assert ret.sims[0] == pytest.approx(1.0) |
| assert ret.weights[0] == pytest.approx(1.0) |
|
|