palimpseste-max / tests /test_phi.py
thefinalboss's picture
Upload tests/test_phi.py with huggingface_hub
e867f82 verified
Raw
History Blame Contribute Delete
5.7 kB
"""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():
# query the exact stored address -> recover its value
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
# exact match at radius 0 -> identical value
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))
# query a fresh random HV -> almost surely > 0 Hamming away
q = hv.random_hv(D=2000, rng=rng)
# try a few times; with radius 0 and random q, no match is expected
out = phi(mem, q)
# could rarely collide; assert None or equality with the single value
assert out is None or out == mem.traces[0].value
def test_phi_blends_multiple_values():
# Three traces near the same address with different values:
# majority value should win the bundle.
mem, rng = _mem_with(D=3000, seed=4)
base_addr = hv.random_hv(D=3000, rng=rng)
# create near addresses by flipping a few bits
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)
# two traces point to v_majority, one to v_minority
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
# majority should dominate
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)
# query with something ~10% different; radius 0 excludes it, radius large includes
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) # below default min_weight 1e-3
phi = Phi(config=KernelConfig(radius=0, min_weight=1e-3))
assert phi(mem, addr) is None
# lowering the floor recovers it
phi_loose = Phi(config=KernelConfig(radius=0, min_weight=1e-7))
assert phi_loose(mem, addr) == val
def test_phi_reconstruct_param_axiom4():
# W_t = Phi(bind(x, s_t))
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)
# store W under address bind(x, s)
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)