File size: 9,683 Bytes
9644d0b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
"""
test_eval.py — Smoke test for eval/ package
============================================

Verifies that the eval/ package is self-consistent and that stats.py
can run on a synthetic dataset. Does NOT load any model — pure stdlib
+ json + math checks.

Run: PYTHONPATH=. python -m pytest tests/test_eval.py -v
"""

import json
import math
import os
import sys
import unittest
import tempfile

# Project root on path
_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if _ROOT not in sys.path:
    sys.path.insert(0, _ROOT)


class TestEvalImports(unittest.TestCase):
    """The eval/ package must be importable as a unit."""

    def test_eval_package_imports(self):
        import eval
        # __init__.py must be a real module
        self.assertTrue(hasattr(eval, "__file__"))

    def test_runner_imports(self):
        from eval import runner
        self.assertTrue(hasattr(runner, "_run_one_prompt"))
        self.assertTrue(hasattr(runner, "PROMPTS"))
        self.assertTrue(hasattr(runner, "shannon_entropy"))
        self.assertTrue(hasattr(runner, "token_diversity"))

    def test_stats_imports(self):
        from eval import stats
        self.assertTrue(hasattr(stats, "analyze"))
        self.assertTrue(hasattr(stats, "one_way_anova"))
        self.assertTrue(hasattr(stats, "linear_r_squared"))

    def test_run_4b_eval_imports(self):
        from eval.run_4b_eval import run_eval, SCALE_TO_MODEL
        self.assertIn("4B", SCALE_TO_MODEL)
        self.assertEqual(SCALE_TO_MODEL["4B"], "gemma3-4b-it")
        self.assertEqual(SCALE_TO_MODEL["E2B"], "gemma4-e2b-it")


class TestPromptsValid(unittest.TestCase):
    """The PROMPTS dictionary must be the right shape."""

    def test_prompt_categories(self):
        from eval.runner import PROMPTS
        self.assertEqual(set(PROMPTS.keys()), {"math", "logic", "creative", "synthesis"})

    def test_prompt_counts(self):
        from eval.runner import PROMPTS
        for cat, prompts in PROMPTS.items():
            self.assertGreaterEqual(len(prompts), 20,
                                    f"category {cat} has only {len(prompts)} prompts")
            for p in prompts:
                self.assertIsInstance(p, str)
                self.assertGreater(len(p), 5, f"prompt too short: {p!r}")


class TestShannonEntropy(unittest.TestCase):
    """Sanity checks on the entropy helper."""

    def test_empty_returns_zero(self):
        from eval.runner import shannon_entropy
        self.assertEqual(shannon_entropy({}), 0.0)

    def test_uniform_returns_max(self):
        from eval.runner import shannon_entropy
        # 4 equally weighted zones → H = log2(4) = 2
        h = shannon_entropy({"a": 1.0, "b": 1.0, "c": 1.0, "d": 1.0})
        self.assertAlmostEqual(h, 2.0, places=5)

    def test_concentrated_returns_low(self):
        from eval.runner import shannon_entropy
        # One zone with all the weight → H = 0
        h = shannon_entropy({"a": 1.0, "b": 0.0, "c": 0.0, "d": 0.0})
        self.assertAlmostEqual(h, 0.0, places=5)

    def test_half_split(self):
        from eval.runner import shannon_entropy
        # 50/50 → H = 1
        h = shannon_entropy({"a": 1.0, "b": 1.0})
        self.assertAlmostEqual(h, 1.0, places=5)


class TestTokenDiversity(unittest.TestCase):
    """Token diversity must handle empty, all-same, and mixed inputs."""

    def test_empty(self):
        from eval.runner import token_diversity
        self.assertEqual(token_diversity(None), 0.0)

    def test_all_same(self):
        from eval.runner import token_diversity
        # 10 copies of the same token → diversity = 1/10 = 0.1
        import torch
        ids = torch.tensor([5, 5, 5, 5, 5, 5, 5, 5, 5, 5])
        self.assertAlmostEqual(token_diversity(ids), 0.1, places=5)

    def test_all_distinct(self):
        from eval.runner import token_diversity
        import torch
        ids = torch.tensor([1, 2, 3, 4, 5])
        # 5 distinct / 5 total = 1.0
        self.assertEqual(token_diversity(ids), 1.0)


class TestAnovaPurePython(unittest.TestCase):
    """The stdlib-only ANOVA implementation must match expected behavior."""

    def test_identical_groups_zero_eta(self):
        from eval.stats import one_way_anova
        # 3 groups, all with values [1, 2, 3] — between-group variance is 0
        res = one_way_anova({"a": [1, 2, 3], "b": [1, 2, 3], "c": [1, 2, 3]})
        self.assertAlmostEqual(res["eta_squared"], 0.0, places=5)

    def test_different_groups_high_eta(self):
        from eval.stats import one_way_anova
        # Strongly separated groups — high η²
        res = one_way_anova({
            "a": [1, 1, 1, 1],
            "b": [5, 5, 5, 5],
            "c": [9, 9, 9, 9],
        })
        self.assertGreater(res["eta_squared"], 0.95)
        # F should be very large
        self.assertGreater(res["F"], 50)

    def test_moderate_difference(self):
        from eval.stats import one_way_anova
        # Modest between-group difference (typical SR-59 territory)
        res = one_way_anova({
            "math":      [1.20, 1.22, 1.18, 1.21],
            "logic":     [1.20, 1.21, 1.19, 1.22],
            "creative":  [1.10, 1.12, 1.09, 1.11],
            "synthesis": [1.05, 1.07, 1.04, 1.06],
        })
        # η² should be moderate-to-high (creative+synthesis are clearly different)
        self.assertGreater(res["eta_squared"], 0.5)
        # p should be small (effect is real)
        self.assertLess(res["p_approx"], 0.05)

    def test_too_few_groups_returns_zero(self):
        from eval.stats import one_way_anova
        res = one_way_anova({"a": [1, 2, 3]})
        self.assertEqual(res["eta_squared"], 0.0)
        self.assertEqual(res["k"], 1)


class TestRSquared(unittest.TestCase):
    """linear_r_squared must be 1.0 for perfect correlation, 0.0 for none."""

    def test_perfect_positive(self):
        from eval.stats import linear_r_squared
        xs = [1.0, 2.0, 3.0, 4.0, 5.0]
        ys = [2.0, 4.0, 6.0, 8.0, 10.0]
        self.assertAlmostEqual(linear_r_squared(xs, ys), 1.0, places=5)

    def test_perfect_negative(self):
        from eval.stats import linear_r_squared
        xs = [1.0, 2.0, 3.0, 4.0, 5.0]
        ys = [5.0, 4.0, 3.0, 2.0, 1.0]
        self.assertAlmostEqual(linear_r_squared(xs, ys), 1.0, places=5)

    def test_no_correlation(self):
        from eval.stats import linear_r_squared
        # Random-ish, no relationship
        xs = [1.0, 2.0, 3.0, 4.0, 5.0]
        ys = [3.0, 3.0, 3.0, 3.0, 3.0]
        # ys has zero variance → R² = 0 (our implementation guards against this)
        self.assertEqual(linear_r_squared(xs, ys), 0.0)


class TestAnalyzeOnMockData(unittest.TestCase):
    """End-to-end: synthesize an aggregate.json, run analyze(), check verdict."""

    def test_synthetic_high_eta_low_r2(self):
        """Mock the SR-59 'ANTI_P_ZOMBIE_CONFIRMED' scenario."""
        from eval.stats import analyze

        # Strong category separation, token diversity NOT correlated with entropy
        results = []
        for cat_idx, cat in enumerate(["math", "logic", "creative", "synthesis"]):
            base_entropy = [1.20, 1.85, 1.50, 1.10][cat_idx]
            for i in range(5):
                results.append({
                    "category": cat,
                    "prompt": f"{cat} prompt {i}",
                    "phi": 0.95 - 0.01 * cat_idx,
                    "zone_entropy": base_entropy + 0.02 * (i - 2),  # small noise
                    "token_diversity_input": 0.5 + 0.01 * i,         # UNcorrelated
                    "zone_weights": {"a": 0.5, "b": 0.5},
                    "kurtosis": 250.0,
                })

        with tempfile.TemporaryDirectory() as tmp:
            agg_path = os.path.join(tmp, "test_aggregate.json")
            with open(agg_path, "w") as f:
                json.dump({
                    "scale": "4B",
                    "model_id": "gemma3-4b-it",
                    "preset": "ACTIVE_MANIFOLD",
                    "results": results,
                }, f)
            summary, out_path = analyze(agg_path)
            self.assertEqual(summary["verdict"], "ANTI_P_ZOMBIE_CONFIRMED")
            self.assertGreater(summary["anova_zone_entropy"]["eta_squared"], 0.10)
            self.assertLess(summary["r2_token_diversity_to_zone_entropy"], 0.30)

    def test_synthetic_low_eta(self):
        """Mock the SR-59 'low η²' scenario — zones all collapse to similar entropy."""
        from eval.stats import analyze

        results = []
        for cat in ["math", "logic", "creative", "synthesis"]:
            for i in range(5):
                results.append({
                    "category": cat,
                    "prompt": f"{cat} prompt {i}",
                    "phi": 0.95,
                    "zone_entropy": 1.5 + 0.01 * i,  # Same for all categories
                    "token_diversity_input": 0.5,
                    "zone_weights": {},
                    "kurtosis": 200.0,
                })

        with tempfile.TemporaryDirectory() as tmp:
            agg_path = os.path.join(tmp, "test_aggregate.json")
            with open(agg_path, "w") as f:
                json.dump({
                    "scale": "4B", "model_id": "gemma3-4b-it",
                    "preset": "ACTIVE_MANIFOLD", "results": results,
                }, f)
            summary, _ = analyze(agg_path)
            # Low η² should trigger ETA2_BELOW_THRESHOLD verdict
            self.assertEqual(summary["verdict"], "ETA2_BELOW_THRESHOLD")
            self.assertLess(summary["anova_zone_entropy"]["eta_squared"], 0.10)


if __name__ == "__main__":
    unittest.main(verbosity=2)