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)