File size: 7,554 Bytes
dbb04e4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
"""

Tests for HDV module (deprecated) and BinaryHDV compatibility shims.



This test file verifies:

1. Legacy HDV class still works (with deprecation warnings)

2. BinaryHDV compatibility shims work correctly

3. Migration path is valid

"""

import pytest
import warnings
import numpy as np

# Test legacy HDV (deprecated)
from mnemocore.core.hdv import HDV
from mnemocore.core.exceptions import DimensionMismatchError

# Test BinaryHDV with compatibility shims
from mnemocore.core.binary_hdv import BinaryHDV


class TestLegacyHDV:
    """Tests for the deprecated HDV class."""

    def test_initialization(self):
        """Test HDV initialization."""
        with warnings.catch_warnings(record=True) as w:
            warnings.simplefilter("always")
            hdv = HDV(dimension=1000)
            assert hdv.vector.shape[0] == 1000
            assert hdv.dimension == 1000
            # Import warning is emitted
            assert len(w) >= 1
            assert issubclass(w[0].category, DeprecationWarning)

    def test_xor_binding(self):
        """Test existing XOR binding behavior."""
        with warnings.catch_warnings(record=True):
            warnings.simplefilter("always")
            v1 = HDV(dimension=100)
            v2 = HDV(dimension=100)

            # Test that __xor__ works
            with warnings.catch_warnings(record=True) as w:
                warnings.simplefilter("always")
                v3 = v1 ^ v2
                assert isinstance(v3, HDV)
                assert v3.dimension == 100
                assert v3.vector is not None
                # Deprecation warning should be emitted
                assert any(issubclass(x.category, DeprecationWarning) for x in w)

            # Test commutative property (circular convolution is commutative)
            with warnings.catch_warnings(record=True):
                warnings.simplefilter("always")
                v4 = v2 ^ v1
            np.testing.assert_allclose(v3.vector, v4.vector, atol=1e-8, err_msg="Binding should be commutative")

    def test_bind_method(self):
        """Test the bind method."""
        with warnings.catch_warnings(record=True):
            warnings.simplefilter("always")
            v1 = HDV(dimension=100)
            v2 = HDV(dimension=100)

            with warnings.catch_warnings(record=True) as w:
                warnings.simplefilter("always")
                v3 = v1.bind(v2)
                assert isinstance(v3, HDV)
                assert v3.dimension == 100
                # Deprecation warning should be emitted
                assert any(issubclass(x.category, DeprecationWarning) for x in w)

            # Should be equivalent to XOR (which is an alias)
            with warnings.catch_warnings(record=True):
                warnings.simplefilter("always")
                v_xor = v1 ^ v2
            np.testing.assert_allclose(v3.vector, v_xor.vector)

    def test_dimension_mismatch(self):
        """Test that dimension mismatch raises error."""
        with warnings.catch_warnings(record=True):
            warnings.simplefilter("always")
            v1 = HDV(dimension=100)
            v2 = HDV(dimension=200)

            with pytest.raises(DimensionMismatchError, match="Dimension mismatch"):
                _ = v1 ^ v2


class TestBinaryHDVCompatibilityShims:
    """Tests for BinaryHDV compatibility shims that match HDV API."""

    def test_bind_shim(self):
        """Test that bind() works as alias for xor_bind()."""
        v1 = BinaryHDV.random(dimension=1000)
        v2 = BinaryHDV.random(dimension=1000)

        # bind() should be equivalent to xor_bind()
        v_bind = v1.bind(v2)
        v_xor = v1.xor_bind(v2)

        assert v_bind == v_xor

    def test_unbind_shim(self):
        """Test that unbind() works (XOR is self-inverse)."""
        v1 = BinaryHDV.random(dimension=1000)
        v2 = BinaryHDV.random(dimension=1000)

        # unbind() should be equivalent to xor_bind() for XOR
        v_unbind = v1.unbind(v2)
        v_xor = v1.xor_bind(v2)

        assert v_unbind == v_xor

        # Self-inverse property: (a XOR b) XOR b = a
        recovered = v_unbind.xor_bind(v2)
        assert recovered == v1

    def test_cosine_similarity_shim(self):
        """Test that cosine_similarity() is an alias for similarity()."""
        v1 = BinaryHDV.random(dimension=1000)
        v2 = BinaryHDV.random(dimension=1000)

        cosine_sim = v1.cosine_similarity(v2)
        sim = v1.similarity(v2)

        assert cosine_sim == sim
        assert 0.0 <= cosine_sim <= 1.0

    def test_normalize_shim(self):
        """Test that normalize() returns a copy."""
        v1 = BinaryHDV.random(dimension=1000)

        normalized = v1.normalize()

        # For binary vectors, normalize is a no-op (returns copy)
        assert normalized == v1
        assert normalized is not v1  # Should be a different object
        assert normalized.data is not v1.data  # Data should be copied

    def test_xor_operator(self):
        """Test that __xor__ operator works for binding."""
        v1 = BinaryHDV.random(dimension=1000)
        v2 = BinaryHDV.random(dimension=1000)

        # v1 ^ v2 should use xor_bind
        v_xor = v1 ^ v2
        v_bind = v1.xor_bind(v2)

        assert v_xor == v_bind

    def test_full_roundtrip(self):
        """Test a full roundtrip: bind, unbind, similarity."""
        v_a = BinaryHDV.random(dimension=1000)
        v_b = BinaryHDV.random(dimension=1000)

        # Bind A and B
        v_ab = v_a.bind(v_b)

        # Unbind to recover A
        v_recovered = v_ab.unbind(v_b)

        # Should be similar to original A
        similarity = v_a.similarity(v_recovered)
        assert similarity == 1.0  # XOR is exact, not approximate


class TestMigrationPath:
    """Tests verifying the migration path from HDV to BinaryHDV."""

    def test_api_equivalence(self):
        """

        Verify that BinaryHDV has all methods needed to replace HDV.

        This test documents the migration path.

        """
        # Create equivalent vectors
        # HDV: hdv = HDV(dimension=10000)
        # BinaryHDV: hdv = BinaryHDV.random(dimension=16384)
        binary_hdv = BinaryHDV.random(dimension=16384)

        # All these methods should exist on BinaryHDV:
        assert hasattr(binary_hdv, 'bind')
        assert hasattr(binary_hdv, 'unbind')
        assert hasattr(binary_hdv, 'permute')
        assert hasattr(binary_hdv, 'cosine_similarity')
        assert hasattr(binary_hdv, 'normalize')
        assert hasattr(binary_hdv, 'xor_bind')
        assert hasattr(binary_hdv, 'similarity')
        assert hasattr(binary_hdv, 'hamming_distance')

    def test_xor_binding_self_inverse(self):
        """

        Demonstrate that XOR binding is self-inverse (unlike HRR).

        This is a key difference but makes the API simpler.

        """
        v_a = BinaryHDV.random(dimension=1000)
        v_b = BinaryHDV.random(dimension=1000)

        # XOR binding
        v_ab = v_a.xor_bind(v_b)

        # XOR unbinding (same operation!)
        v_recovered = v_ab.xor_bind(v_b)

        # Exact recovery (not approximate like HRR)
        assert v_recovered == v_a

        # Same with bind/unbind shims
        v_ab_shim = v_a.bind(v_b)
        v_recovered_shim = v_ab_shim.unbind(v_b)
        assert v_recovered_shim == v_a