Download tests/test_hypervec.py from AEUPH/hdcstorage: direct link, hf CLI and curl.
- Browser
- Download file 5.82 kB
-
https://huggingface.co/AEUPH/hdcstorage/resolve/main/tests/test_hypervec.py
- Command line
-
hf download hf://AEUPH/hdcstorage/tests/test_hypervec.py
-
curl -L -o test_hypervec.py https://huggingface.co/AEUPH/hdcstorage/resolve/main/tests/test_hypervec.py
5.82 kB
| """Algebra-level tests for the binary hypervector core.""" | |
| import os | |
| import random | |
| import sys | |
| import unittest | |
| sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | |
| from hdc.hypervec import ( # noqa: E402 | |
| DEFAULT_DIM, | |
| bind, | |
| bundle, | |
| hamming, | |
| hvc, | |
| hv_to_int, | |
| int_to_hv, | |
| permute, | |
| permute_inv, | |
| popcount, | |
| random_hv, | |
| sim, | |
| ) | |
| def brute_majority(vecs, d): | |
| """Per-bit reference majority; ties resolve to 0, as documented.""" | |
| out = 0 | |
| for pos in range(d): | |
| ones = sum((v >> pos) & 1 for v in vecs) | |
| if ones * 2 > len(vecs): | |
| out |= 1 << pos | |
| return out | |
| class TestBind(unittest.TestCase): | |
| def test_involutive(self): | |
| rng = random.Random(11) | |
| for _ in range(20): | |
| a = random_hv(2000, rng) | |
| b = random_hv(2000, rng) | |
| self.assertEqual(bind(bind(a, b, 2000), b, 2000), a) | |
| def test_bind_is_xor(self): | |
| a, b = 0b1100, 0b1010 | |
| self.assertEqual(bind(a, b), a ^ b) | |
| def test_bind_preserves_similarity(self): | |
| rng = random.Random(12) | |
| a = random_hv(4000, rng) | |
| near = a ^ (1 << 5) # one bit away | |
| x = random_hv(4000, rng) | |
| self.assertAlmostEqual( | |
| sim(bind(a, x), bind(near, x), 4000), sim(a, near, 4000), places=12 | |
| ) | |
| class TestBundle(unittest.TestCase): | |
| def test_matches_brute_force_odd(self): | |
| rng = random.Random(21) | |
| d = 64 | |
| for n in (3, 5, 7): | |
| vecs = [random_hv(d, rng) for _ in range(n)] | |
| self.assertEqual(bundle(vecs, d), brute_majority(vecs, d)) | |
| def test_matches_brute_force_even_with_ties(self): | |
| rng = random.Random(22) | |
| d = 48 | |
| for n in (2, 4, 6): | |
| vecs = [random_hv(d, rng) for _ in range(n)] | |
| self.assertEqual(bundle(vecs, d), brute_majority(vecs, d)) | |
| def test_single_vector_and_empty(self): | |
| rng = random.Random(23) | |
| v = random_hv(128, rng) | |
| self.assertEqual(bundle([v], 128), v) | |
| self.assertEqual(bundle([], 128), 0) | |
| def test_members_recover_above_noise(self): | |
| rng = random.Random(24) | |
| d = 4000 | |
| vecs = [random_hv(d, rng) for _ in range(5)] | |
| b = bundle(vecs, d) | |
| for v in vecs: | |
| self.assertGreater(sim(b, v, d), 0.25) | |
| class TestPermute(unittest.TestCase): | |
| def test_roundtrip(self): | |
| rng = random.Random(31) | |
| d = 2000 | |
| v = random_hv(d, rng) | |
| for k in (1, 7, 999, d - 1): | |
| self.assertEqual(permute_inv(permute(v, k, d), k, d), v) | |
| def test_preserves_similarity(self): | |
| rng = random.Random(32) | |
| d = 2000 | |
| a, b = random_hv(d, rng), random_hv(d, rng) | |
| self.assertAlmostEqual( | |
| sim(permute(a, 13, d), permute(b, 13, d), d), sim(a, b, d), places=12 | |
| ) | |
| def test_rotation_keeps_popcount(self): | |
| rng = random.Random(33) | |
| v = random_hv(1000, rng) | |
| self.assertEqual(popcount(permute(v, 37, 1000)), popcount(v)) | |
| class TestSimilarity(unittest.TestCase): | |
| def test_identical_is_one(self): | |
| rng = random.Random(41) | |
| v = random_hv(2000, rng) | |
| self.assertEqual(sim(v, v, 2000), 1.0) | |
| def test_complement_is_minus_one(self): | |
| d = 256 | |
| v = random_hv(d, random.Random(42)) | |
| # the d-bit complement, not Python's unbounded ~v | |
| self.assertEqual(sim(v, v ^ ((1 << d) - 1), d), -1.0) | |
| def test_symmetry_and_bounds(self): | |
| rng = random.Random(43) | |
| d = 2000 | |
| for _ in range(20): | |
| a, b = random_hv(d, rng), random_hv(d, rng) | |
| self.assertEqual(sim(a, b, d), sim(b, a, d)) | |
| self.assertGreaterEqual(sim(a, b, d), -1.0) | |
| self.assertLessEqual(sim(a, b, d), 1.0) | |
| def test_random_pairs_near_orthogonal(self): | |
| rng = random.Random(44) | |
| d = 2000 | |
| vals = [sim(random_hv(d, rng), random_hv(d, rng), d) for _ in range(300)] | |
| mean = sum(vals) / len(vals) | |
| self.assertLess(abs(mean), 0.05) | |
| def test_hamming_agrees_with_sim(self): | |
| rng = random.Random(45) | |
| d = 2000 | |
| a, b = random_hv(d, rng), random_hv(d, rng) | |
| self.assertAlmostEqual( | |
| sim(a, b, d), (d - 2 * hamming(a, b)) / d, places=12 | |
| ) | |
| class TestSymbols(unittest.TestCase): | |
| def test_deterministic(self): | |
| self.assertEqual(hvc("op/ADD", 2000), hvc("op/ADD", 2000)) | |
| def test_distinct_names_differ(self): | |
| a, b = hvc("op/ADD", 2000), hvc("op/SUB", 2000) | |
| self.assertNotEqual(a, b) | |
| self.assertLess(abs(sim(a, b, 2000)), 0.1) | |
| def test_dimension_matters(self): | |
| self.assertNotEqual(hvc("op/ADD", 2000), hvc("op/ADD", 1000)) | |
| class TestIntegerCodec(unittest.TestCase): | |
| VALUES = [0, 1, 7, 42, -5, 255, 65535, -12345] | |
| def test_roundtrip_default_dim(self): | |
| for n in self.VALUES: | |
| self.assertEqual(hv_to_int(int_to_hv(n)), n, msg="value %d" % n) | |
| def test_roundtrip_small_dim(self): | |
| d = 2000 | |
| for n in self.VALUES: | |
| self.assertEqual(hv_to_int(int_to_hv(n, d), d), n, msg="value %d" % n) | |
| def test_encoded_values_are_hypervectors(self): | |
| v = int_to_hv(42) | |
| self.assertEqual(v.bit_length() <= DEFAULT_DIM, True) | |
| # different integers must not collide | |
| self.assertNotEqual(int_to_hv(42), int_to_hv(43)) | |
| self.assertNotEqual(int_to_hv(42), int_to_hv(-42)) | |
| # 42 and 7 share bit atom 1 by construction, so they are correlated | |
| # but nowhere near identical | |
| self.assertLess(sim(int_to_hv(42), int_to_hv(7), DEFAULT_DIM), 0.6) | |
| def test_out_of_range_rejected(self): | |
| with self.assertRaises(ValueError): | |
| int_to_hv(1 << 40) | |
| if __name__ == "__main__": | |
| unittest.main() | |