"""Tests for the dense→HV projection and the embedding expert.""" from __future__ import annotations import pytest import numpy as np from ensemble.projection import make_projection, dense_to_hv from ensemble import EmbeddingExpert, Brain, Expert from palimseste.hv import similarity class TestProjection: def test_make_projection_shape(self): R = make_projection(D=1000, dim=50, seed=1) assert R.shape == (1000, 50) assert R.dtype == np.int8 # entries are {+1,-1} assert set(np.unique(R)).issubset({-1, 1}) def test_make_projection_deterministic(self): a = make_projection(D=500, dim=20, seed=7) b = make_projection(D=500, dim=20, seed=7) assert np.array_equal(a, b) def test_different_seeds_differ(self): a = make_projection(D=500, dim=20, seed=1) b = make_projection(D=500, dim=20, seed=2) assert not np.array_equal(a, b) def test_dense_to_hv_identical_vectors(self): R = make_projection(D=2000, dim=30, seed=0) v = np.random.default_rng(1).standard_normal(30) h1 = dense_to_hv(v, R) h2 = dense_to_hv(v, R) assert h1 == h2 assert h1.D == 2000 def test_projection_preserves_cosine(self): """Higher cosine => higher HV similarity (monotone).""" R = make_projection(D=4000, dim=100, seed=42) rng = np.random.default_rng(0) base = rng.standard_normal(100) base /= np.linalg.norm(base) def with_cos(c): o = rng.standard_normal(100); o -= base*np.dot(o, base); o /= np.linalg.norm(o) return c*base + (1-c*c)**0.5*o sims = [] for c in [1.0, 0.5, 0.0]: hv = dense_to_hv(with_cos(c), R) sims.append(similarity(hv, dense_to_hv(base, R))) # monotone decreasing as cosine drops assert sims[0] > sims[1] > sims[2] def test_dim_mismatch_raises(self): R = make_projection(D=100, dim=10, seed=0) with pytest.raises(ValueError): dense_to_hv(np.zeros(20), R) class TestEmbeddingExpert: CORPUS = ( "the cat sat on the mat and purred. the dog ran and barked loudly. " "cats and dogs are animals pets. the car drove fast on the road. " ) * 30 def test_from_corpus_local_builds(self): e = EmbeddingExpert.from_corpus_local(self.CORPUS, D=2000, dim=40) assert e.D == 2000 assert e.vocab_size > 5 assert "cat" in e.vectors or "the" in e.vectors def test_get_word_hv(self): e = EmbeddingExpert.from_corpus_local(self.CORPUS, D=2000, dim=40) hv = e.get_word_hv("cat") assert hv is not None assert hv.D == 2000 def test_get_word_hv_oov(self): e = EmbeddingExpert.from_corpus_local(self.CORPUS, D=2000, dim=40) assert e.get_word_hv("xyzqwert") is None def test_relevance_positive(self): e = EmbeddingExpert.from_corpus_local(self.CORPUS, D=2000, dim=40) r = e.relevance("the cat sat") assert 0.0 <= r <= 1.0 def test_candidate_hv(self): e = EmbeddingExpert.from_corpus_local(self.CORPUS, D=2000, dim=40) hv = e.candidate_hv("the cat") assert hv is not None assert hv.D == 2000 def test_candidate_hv_empty(self): e = EmbeddingExpert.from_corpus_local(self.CORPUS, D=2000, dim=40) # all OOV words -> None assert e.candidate_hv("xyzqwert zzz") is None def test_answer_returns_string(self): e = EmbeddingExpert.from_corpus_local(self.CORPUS, D=2000, dim=40) a = e.answer("the cat") assert isinstance(a, str) def test_couples_to_brain(self): """The embedding expert plugs into a brain without changes.""" e = EmbeddingExpert.from_corpus_local(self.CORPUS, D=2000, dim=40) lex = Expert.from_text("hello world. " * 20, D=2000) brain = Brain() brain.add_expert(lex) brain.add_expert(e) assert brain.n_experts == 2 res = brain.query("the cat") assert isinstance(res.answer, str) def test_semantic_similarity_above_noise(self): """Related words should be more similar than unrelated in a rich corpus.""" corpus = ( "king queen royal throne crown prince princess monarchy. " "cat dog pet animal fur paw tail. " "car truck vehicle road wheel engine drive. " ) * 40 e = EmbeddingExpert.from_corpus_local(corpus, D=3000, dim=60) king, queen = e.get_word_hv("king"), e.get_word_hv("queen") cat, dog = e.get_word_hv("cat"), e.get_word_hv("dog") king_dog = similarity(king, dog) if king and dog else 0 king_queen = similarity(king, queen) if king and queen else 0 # related (king~queen) should beat unrelated (king~dog) — or at least # the within-category pairs should be positive if king and queen and cat and dog: assert king_queen > -0.2 # not strongly anti-correlated def test_solve_analogy_returns_answer(self): """Analogy with structured synthetic vectors recovers the right word.""" rng = np.random.default_rng(0) dim = 40 base_c = rng.standard_normal(dim) base_C = rng.standard_normal(dim) vecs = {} pairs = {'france': 'paris', 'germany': 'berlin', 'italy': 'rome'} for c, C in pairs.items(): noise = rng.standard_normal(dim) * 0.2 vecs[c] = base_c + noise vecs[C.lower()] = base_C + noise # unseen: spain -> madrid (same noise pattern) noise = rng.standard_normal(dim) * 0.2 vecs['spain'] = base_c + noise vecs['madrid'] = base_C + noise R = make_projection(D=2000, dim=dim, seed=0) word_hvs = {w: dense_to_hv(v, R) for w, v in vecs.items()} e = EmbeddingExpert(domain='t', D=2000, vectors=vecs, word_hvs=word_hvs, projection=R, signature_hv=word_hvs['france'], dim=dim) e.learn_relation('capital_of', pairs) best, sim = e.solve_analogy('capital_of', 'spain') assert best == 'madrid' assert sim > 0.5 def test_solve_analogy_unknown_relation(self): e = EmbeddingExpert.from_corpus_local("a b c d. " * 30, D=1000, dim=20) best, sim = e.solve_analogy('nonexistent', 'x') assert best is None def test_solve_analogy_excludes_slot(self): """The query slot must not be returned as its own answer.""" rng = np.random.default_rng(1) dim = 30 vecs = {w: rng.standard_normal(dim) for w in ['france', 'paris', 'germany', 'berlin', 'spain', 'madrid']} R = make_projection(D=1000, dim=dim, seed=0) whvs = {w: dense_to_hv(v, R) for w, v in vecs.items()} e = EmbeddingExpert(domain='t', D=1000, vectors=vecs, word_hvs=whvs, projection=R, signature_hv=whvs['france'], dim=dim) e.learn_relation('capital_of', {'france': 'paris', 'germany': 'berlin'}) best, _ = e.solve_analogy('capital_of', 'spain') assert best != 'spain'