File size: 5,703 Bytes
e867f82 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 | """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)
|