Initial release: ENSEMBLE training-free AI — compressed .exp experts + Kuramoto brain
1f71c7d verified | """Tests for the multi-expert Kuramoto attractor.""" | |
| from __future__ import annotations | |
| import numpy as np | |
| import pytest | |
| from ensemble import Expert, ExpertOscillator, ExpertKuramotoAttractor | |
| QA_A = [ | |
| ("what is cats", "cats are animals that purr"), | |
| ("what is dogs", "dogs are animals that bark"), | |
| ] * 3 | |
| QA_B = [ | |
| ("what is cats", "cats are felines with whiskers"), | |
| ("what is lions", "lions are big felines"), | |
| ] * 3 | |
| def two_experts(): | |
| a = Expert.from_qa_pairs(QA_A, domain="a", D=2000) | |
| b = Expert.from_qa_pairs(QA_B, domain="b", D=2000) | |
| return a, b | |
| class TestOscillator: | |
| def test_prepare_sets_state(self, two_experts): | |
| a, _ = two_experts | |
| osc = ExpertOscillator(a) | |
| osc.prepare("what is cats") | |
| assert -1.0 <= osc.relevance <= 1.0 | |
| assert 0.0 <= osc.natural_frequency <= 1.0 | |
| assert osc.candidate is not None | |
| def test_signature_matches_expert(self, two_experts): | |
| a, _ = two_experts | |
| osc = ExpertOscillator(a) | |
| assert osc.signature == a.signature_hv | |
| def test_weight_settable(self, two_experts): | |
| a, _ = two_experts | |
| osc = ExpertOscillator(a, weight=2.5) | |
| assert osc.weight == 2.5 | |
| class TestAttractor: | |
| def test_single_expert_returns_candidate(self, two_experts): | |
| a, _ = two_experts | |
| osc = ExpertOscillator(a) | |
| osc.prepare("what is cats") | |
| attr = ExpertKuramotoAttractor() | |
| result = attr.evolve([osc]) | |
| assert result.coherence == 1.0 | |
| assert result.n_with_candidates == 1 | |
| assert result.dominant_expert == osc.name | |
| def test_two_experts_synthesize(self, two_experts): | |
| a, b = two_experts | |
| oscs = [ExpertOscillator(a), ExpertOscillator(b)] | |
| for o in oscs: | |
| o.prepare("what is cats") | |
| attr = ExpertKuramotoAttractor(n_iterations=20) | |
| result = attr.evolve(oscs) | |
| assert result.n_with_candidates == 2 | |
| assert 0.0 <= result.coherence <= 1.0 | |
| assert len(result.oscillator_states) == 2 | |
| def test_empty_returns_gracefully(self): | |
| attr = ExpertKuramotoAttractor() | |
| # no oscillators -> returns a random HV, coherence 0 | |
| result = attr.evolve([]) | |
| assert result.n_experts == 0 | |
| assert result.coherence == 0.0 | |
| def test_no_candidate_experts_filtered(self, two_experts): | |
| """An expert with no candidate contributes nothing.""" | |
| a, _ = two_experts | |
| osc = ExpertOscillator(a) | |
| # don't prepare -> candidate is None | |
| attr = ExpertKuramotoAttractor() | |
| result = attr.evolve([osc]) | |
| assert result.n_with_candidates == 0 | |
| def test_weight_contributions_sum_to_one(self, two_experts): | |
| a, b = two_experts | |
| oscs = [ExpertOscillator(a), ExpertOscillator(b)] | |
| for o in oscs: | |
| o.prepare("what is cats") | |
| attr = ExpertKuramotoAttractor(n_iterations=20) | |
| result = attr.evolve(oscs) | |
| total = sum(s.weight_contribution for s in result.oscillator_states) | |
| assert abs(total - 1.0) < 1e-6 | |