"""Tests for the HDC shader layer. The interesting cases are the ones that separate exact from approximate: ``test_adjacent_depths_are_indistinguishable_by_similarity`` shows why the depth buffer uses the bit-scan comparator rather than a similarity ranking. """ import math import random import sys import unittest from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from hdc.hypervec import hv_to_int, int_to_hv, shift, sim from hdc.shader import ( DepthBuffer, Image, NormalCodebook, ShaderFabric, demo_report, step_image, ) D = 2000 class TestShift(unittest.TestCase): """The dimming operator rests entirely on this identity.""" def test_left_and_right_shift_are_exact(self): rng = random.Random(11) values = [0, 1, 2, 3, 7, 42, 100, 200, 255, 1000, 65535] + [ rng.randrange(0, 1 << 20) for _ in range(40) ] for n in values: for k in (1, 2, 3, 8, 16): hv = int_to_hv(n, D) # A shift that overflows the 31-bit magnitude lands in the sign # bit by design; the identity is claimed for in-range results. if n << k < (1 << 31): self.assertEqual(hv_to_int(shift(hv, k, D), D), n << k, (n, k)) self.assertEqual(hv_to_int(shift(hv, -k, D), D), n >> k, (n, k)) def test_shift_is_not_a_decode_roundtrip(self): """Two rotations and two XORs; the value is never read out.""" n = 200 hv = int_to_hv(n, D) folded = shift(hv, -1, D) self.assertNotEqual(folded, hv) self.assertEqual(hv_to_int(folded, D), 100) class TestFabric(unittest.TestCase): def setUp(self): self.f = ShaderFabric(d=D) def test_dim_and_brighten(self): self.assertEqual(hv_to_int(self.f.dim(self.f.level(200)), D), 100) self.assertEqual(hv_to_int(self.f.dim(self.f.level(200), 2), D), 50) self.assertEqual(hv_to_int(self.f.dim(self.f.level(41), 1), D), 20) # truncating self.assertEqual( hv_to_int(self.f.brighten(self.f.dim(self.f.level(200), 1), 1), D), 200 ) def test_compare_is_a_comparator(self): rng = random.Random(12) for _ in range(30): a = rng.randrange(0, 1000) b = rng.randrange(0, 1000) expected = (a > b) - (a < b) self.assertEqual(self.f.compare(self.f.level(a), self.f.level(b)), expected, (a, b)) def test_compare_handles_equal_values(self): self.assertEqual(self.f.compare(self.f.level(77), self.f.level(77)), 0) def test_max_and_min(self): a, b = self.f.level(10), self.f.level(250) self.assertEqual(hv_to_int(self.f.max_of(a, b), D), 250) self.assertEqual(hv_to_int(self.f.min_of(a, b), D), 10) def test_abs_and_gradient(self): self.assertEqual(hv_to_int(self.f.abs_of(self.f.sub(self.f.level(40), self.f.level(200))), D), 160) self.assertEqual(hv_to_int(self.f.gradient(self.f.level(200), self.f.level(40)), D), 160) self.assertEqual(hv_to_int(self.f.gradient(self.f.level(40), self.f.level(200)), D), 160) def test_sign_probe(self): self.assertTrue(self.f.is_negative(self.f.sub(self.f.level(1), self.f.level(2)))) self.assertFalse(self.f.is_negative(self.f.level(5))) def test_discontinuity_is_similarity_based(self): same = self.f.discontinuity(self.f.level(200), self.f.level(200)) different = self.f.discontinuity(self.f.level(200), self.f.level(40)) self.assertEqual(same, 0.0) self.assertGreater(different, 0.5) class TestImage(unittest.TestCase): def setUp(self): self.f = ShaderFabric(d=D) def test_roundtrip_levels(self): rows = [[0, 50, 100], [150, 200, 255]] img = Image.from_levels(rows, self.f) self.assertEqual(img.levels(), rows) self.assertEqual((img.w, img.h), (3, 2)) def test_ragged_rows_rejected(self): with self.assertRaises(ValueError): Image.from_levels([[1, 2], [3]], self.f) def test_dim_scales_every_pixel_exactly(self): img = Image.from_levels([[200, 40]], self.f) self.assertEqual(img.dim(1).levels(), [[100, 20]]) self.assertEqual(img.dim(3).levels(), [[25, 5]]) def test_edges_find_the_step(self): img = step_image(8, 4, edge=4, fabric=self.f, bright_b=200) mags = img.edges().levels() self.assertEqual(mags[0], [0, 0, 0, 160, 0, 0, 0, 0]) for row in mags: self.assertEqual(sum(row), 160) def test_edges_of_a_flat_image_are_zero(self): img = Image.from_levels([[7, 7, 7], [7, 7, 7]], self.f) self.assertEqual(img.edges().levels(), [[0, 0, 0], [0, 0, 0]]) def test_discontinuity_map_marks_the_edge(self): img = step_image(8, 2, fabric=self.f) grid = img.discontinuities() self.assertGreater(grid[0][3], 0.5) self.assertEqual(grid[0][0], 0.0) def test_pixel_record_roundtrip(self): img = Image.from_levels([[11, 22]], self.f) record = img.pixel_record(1, 0, depth=9) self.assertEqual(img.read_level(record), 22) def test_ascii_and_stat(self): img = Image.from_levels([[0, 100, 200]], self.f) self.assertEqual(len(img.ascii()), 3) stat = img.stat() self.assertEqual((stat["min"], stat["max"], stat["distinct"]), (0, 200, 3)) class TestDepthBuffer(unittest.TestCase): def setUp(self): self.f = ShaderFabric(d=D) def test_keeps_the_nearest_fragment(self): zb = DepthBuffer(1, 1, self.f) self.assertTrue(zb.test(0, 0, self.f.level(20))) self.assertFalse(zb.test(0, 0, self.f.level(50))) self.assertFalse(zb.test(0, 0, self.f.level(30))) self.assertTrue(zb.test(0, 0, self.f.level(5))) self.assertEqual(zb.depths(), [[5]]) self.assertEqual((zb.tests, zb.rejected), (4, 2)) def test_similarity_cannot_rank_depths(self): """Why the buffer compares bits instead of calling sim. The integer codec is symbolic: 20 and 21 score about the same as 20 and 200, so there is no threshold that orders depths by similarity. """ adjacent = sim(self.f.level(20), self.f.level(21), D) distant = sim(self.f.level(20), self.f.level(200), D) self.assertLess(adjacent, 0.8) self.assertLess(abs(adjacent - distant), 0.5) # same band: not a ranking self.assertEqual(self.f.compare(self.f.level(20), self.f.level(21)), -1) class TestNormalCodebook(unittest.TestCase): def setUp(self): self.f = ShaderFabric(d=D) self.cb = NormalCodebook(entries=64, d=D, fabric=self.f) def test_nearest_recovers_a_stored_direction(self): for entry in self.cb.entries[:8]: found, match = self.cb.nearest(entry.direction) self.assertEqual(found.index, entry.index) self.assertGreater(match, 0.9) @staticmethod def _cos(u, v): nu = math.sqrt(sum(c * c for c in u)) nv = math.sqrt(sum(c * c for c in v)) return sum(a * b for a, b in zip(u, v)) / (nu * nv) def test_lookup_tracks_angle_not_bit_patterns(self): """The thermometer code makes cleanup agree with angle, not with bits. With the symbolic integer codec this fails badly: similarity there measures shared bit atoms, so cleanup returns a geometrically much worse entry (measured 0.537 cosine against a 0.712 best). With the thermometer code most misses are boundary cases, so the assertions are the measured floor — exact agreement >= 60% and mean cosine deficit <= 0.06 — rather than a perfect hit rate. """ rng = random.Random(21) trials = 60 exact = 0 deficits = [] for _ in range(trials): v = [rng.gauss(0, 1) for _ in range(3)] norm = math.sqrt(sum(c * c for c in v)) or 1.0 query = tuple(c / norm for c in v) found, _ = self.cb.nearest(query) best = max(self._cos(query, e.direction) for e in self.cb.entries) got = self._cos(query, found.direction) deficits.append(best - got) exact += 1 if got >= best - 1e-9 else 0 self.assertGreaterEqual(exact / trials, 0.60) self.assertLessEqual(sum(deficits) / len(deficits), 0.06) def test_lambert_terms(self): up = self.cb.nearest((0.0, 0.0, 1.0))[0] self.assertGreater(self.cb.lambert(up, (0.0, 0.0, 1.0)), 0.9) self.assertEqual(self.cb.lambert(up, (0.0, 0.0, -1.0)), 0.0) index, match, lambert, intensity = self.cb.shade((0.0, 0.0, 1.0), (0.0, 0.0, 1.0)) self.assertEqual(index, up.index) self.assertGreater(intensity, 200.0) def test_codebook_needs_at_least_four_entries(self): with self.assertRaises(ValueError): NormalCodebook(entries=3, d=D) class TestReport(unittest.TestCase): def test_demo_report_renders(self): text = demo_report(d=D) for needle in ("HDC shader fabric", "after dim(1)", "edge magnitudes", "normal LUT", "depth buffer"): self.assertIn(needle, text) if __name__ == "__main__": unittest.main()