Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
171 lines
5.0 KiB
Python
171 lines
5.0 KiB
Python
"""
|
|
Unit tests for Elo rating system
|
|
"""
|
|
import math
|
|
|
|
import pytest
|
|
|
|
from _bootstrap import bootstrap_experiment_root
|
|
|
|
bootstrap_experiment_root()
|
|
|
|
from elo_rating import EloRatingSystem
|
|
|
|
|
|
def test_initial_rating():
|
|
"""Test that models start with initial rating."""
|
|
elo = EloRatingSystem(initial_rating=1000.0)
|
|
assert elo.get_rating("model_a") == 1000.0
|
|
assert elo.get_rating("model_b") == 1000.0
|
|
|
|
|
|
def test_expected_score():
|
|
"""Test expected score calculation."""
|
|
elo = EloRatingSystem()
|
|
|
|
# Equal ratings should give 50% probability
|
|
assert elo.expected_score(1000, 1000) == 0.5
|
|
|
|
# Higher rated player should have > 50% probability
|
|
assert elo.expected_score(1200, 1000) > 0.5
|
|
assert elo.expected_score(1000, 1200) < 0.5
|
|
|
|
# 400 point difference should give ~91% probability
|
|
prob = elo.expected_score(1400, 1000)
|
|
assert 0.90 < prob < 0.92
|
|
|
|
|
|
def test_rating_update_win():
|
|
"""Test rating update when model_a wins."""
|
|
elo = EloRatingSystem(initial_rating=1000.0, k_factor=32.0)
|
|
|
|
new_a, new_b = elo.update_ratings("model_a", "model_b", "model_a")
|
|
|
|
# Winner should gain rating, loser should lose rating
|
|
assert new_a > 1000.0
|
|
assert new_b < 1000.0
|
|
|
|
# Total rating should be conserved (zero-sum)
|
|
assert abs((new_a + new_b) - 2000.0) < 0.01
|
|
|
|
|
|
def test_rating_update_tie():
|
|
"""Test rating update for a tie."""
|
|
elo = EloRatingSystem(initial_rating=1000.0, k_factor=32.0)
|
|
|
|
new_a, new_b = elo.update_ratings("model_a", "model_b", "tie")
|
|
|
|
# With equal ratings, tie should not change ratings much
|
|
assert abs(new_a - 1000.0) < 0.01
|
|
assert abs(new_b - 1000.0) < 0.01
|
|
|
|
|
|
def test_upset_gives_larger_change():
|
|
"""Test that unexpected results cause larger rating changes."""
|
|
elo = EloRatingSystem(initial_rating=1000.0, k_factor=32.0)
|
|
|
|
# Give model_a higher rating
|
|
elo.ratings["model_a"] = 1200.0
|
|
elo.ratings["model_b"] = 1000.0
|
|
|
|
# If weaker model wins (upset), changes should be larger
|
|
new_a_upset, new_b_upset = elo.update_ratings("model_a", "model_b", "model_b")
|
|
|
|
# Reset
|
|
elo.ratings["model_a"] = 1200.0
|
|
elo.ratings["model_b"] = 1000.0
|
|
|
|
# If stronger model wins (expected), changes should be smaller
|
|
new_a_expected, new_b_expected = elo.update_ratings("model_a", "model_b", "model_a")
|
|
|
|
# Upset should cause larger change
|
|
change_upset = abs(new_a_upset - 1200.0)
|
|
change_expected = abs(new_a_expected - 1200.0)
|
|
|
|
assert change_upset > change_expected
|
|
|
|
|
|
def test_leaderboard_sorting():
|
|
"""Test that leaderboard is sorted by rating."""
|
|
elo = EloRatingSystem(initial_rating=1000.0, k_factor=32.0)
|
|
|
|
# Create some matches to differentiate ratings
|
|
elo.update_ratings("model_a", "model_b", "model_a")
|
|
elo.update_ratings("model_a", "model_c", "model_a")
|
|
elo.update_ratings("model_b", "model_c", "model_b")
|
|
|
|
leaderboard = elo.get_leaderboard()
|
|
|
|
# Check descending order
|
|
for i in range(len(leaderboard) - 1):
|
|
assert leaderboard[i][1] >= leaderboard[i+1][1]
|
|
|
|
# model_a should be first (won all matches)
|
|
assert leaderboard[0][0] == "model_a"
|
|
|
|
|
|
def test_win_probability_symmetry():
|
|
"""Test that win probabilities sum to 1."""
|
|
elo = EloRatingSystem()
|
|
elo.ratings["model_a"] = 1200.0
|
|
elo.ratings["model_b"] = 1000.0
|
|
|
|
prob_a = elo.calculate_win_probability("model_a", "model_b")
|
|
prob_b = elo.calculate_win_probability("model_b", "model_a")
|
|
|
|
# Should sum to 1
|
|
assert abs(prob_a + prob_b - 1.0) < 0.001
|
|
|
|
|
|
def test_match_counting():
|
|
"""Test that match and win counts are tracked correctly."""
|
|
elo = EloRatingSystem(initial_rating=1000.0, k_factor=32.0)
|
|
|
|
elo.update_ratings("model_a", "model_b", "model_a") # model_a wins
|
|
elo.update_ratings("model_a", "model_c", "model_b") # model_a loses (2nd slot wins)
|
|
elo.update_ratings("model_a", "model_b", "tie") # tie -> 0.5 each
|
|
|
|
# model_a played 3 matches
|
|
assert elo.match_counts["model_a"] == 3
|
|
|
|
# model_a won 1 match and tied 1 (1.5 total)
|
|
assert elo.win_counts["model_a"] == 1.5
|
|
|
|
# model_b played 2 matches
|
|
assert elo.match_counts["model_b"] == 2
|
|
|
|
|
|
def test_copy():
|
|
"""Test that copy creates independent instance."""
|
|
elo1 = EloRatingSystem(initial_rating=1000.0, k_factor=32.0)
|
|
elo1.update_ratings("model_a", "model_b", "model_a")
|
|
|
|
elo2 = elo1.copy()
|
|
|
|
# Modify elo2
|
|
elo2.update_ratings("model_a", "model_b", "model_b")
|
|
|
|
# elo1 should be unchanged
|
|
assert elo1.ratings["model_a"] != elo2.ratings["model_a"]
|
|
|
|
|
|
def test_reset():
|
|
"""Test that reset clears all data."""
|
|
elo = EloRatingSystem(initial_rating=1000.0, k_factor=32.0)
|
|
|
|
elo.update_ratings("model_a", "model_b", "model_a")
|
|
elo.update_ratings("model_a", "model_c", "model_a")
|
|
|
|
assert len(elo.ratings) > 0
|
|
|
|
elo.reset()
|
|
|
|
assert len(elo.ratings) == 0
|
|
assert len(elo.match_counts) == 0
|
|
assert len(elo.win_counts) == 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# Run tests
|
|
pytest.main([__file__, "-v"])
|