px-explorer-v4 / tests /test_eval.py
BuildBot
push_hf: sparse-branch für HF-Push (nur Code, 0 LFS)
9644d0b
Raw
History Blame Contribute Delete
9.68 kB
"""
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)