chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
"""
|
||||
Unit tests for Average Precision algorithm.
|
||||
"""
|
||||
|
||||
from typing import List
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
|
||||
def calculate_average_precision_original(verdict_list: List[int]) -> float:
|
||||
"""Original implementation for comparison."""
|
||||
if not verdict_list:
|
||||
return 0.0
|
||||
|
||||
numerator = sum(
|
||||
[
|
||||
(sum(verdict_list[: i + 1]) / (i + 1)) * verdict_list[i]
|
||||
for i in range(len(verdict_list))
|
||||
]
|
||||
)
|
||||
denominator = sum(verdict_list) + 1e-10
|
||||
return numerator / denominator
|
||||
|
||||
|
||||
def calculate_average_precision_optimized(verdict_list: List[int]) -> float:
|
||||
"""Optimized implementation matching the codebase."""
|
||||
cumsum = 0
|
||||
numerator = 0.0
|
||||
for i, v in enumerate(verdict_list):
|
||||
cumsum += v
|
||||
if v:
|
||||
numerator += cumsum / (i + 1)
|
||||
|
||||
denominator = cumsum + 1e-10
|
||||
return numerator / denominator
|
||||
|
||||
|
||||
class TestAveragePrecisionAlgorithm:
|
||||
"""Test suite for Average Precision algorithm correctness."""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"verdict_list",
|
||||
[
|
||||
[], # empty
|
||||
[1], # single positive
|
||||
[0], # single negative
|
||||
[1, 1, 1, 1, 1], # all ones
|
||||
[0, 0, 0, 0, 0], # all zeros
|
||||
[1, 0, 1], # alternating
|
||||
[1, 1, 0, 1], # mixed
|
||||
[0, 0, 1, 1, 1], # late positives
|
||||
[1, 1, 0, 0, 1, 1, 0, 1], # realistic pattern
|
||||
],
|
||||
)
|
||||
def test_optimized_matches_original(self, verdict_list):
|
||||
"""Test that optimized algorithm produces identical results to original."""
|
||||
original = calculate_average_precision_original(verdict_list)
|
||||
optimized = calculate_average_precision_optimized(verdict_list)
|
||||
assert np.isclose(original, optimized, rtol=1e-10, atol=1e-10)
|
||||
|
||||
def test_known_example_1_0_1(self):
|
||||
"""Test [1,0,1]: score = (1 + 2/3) / 2 = 5/6."""
|
||||
assert np.isclose(
|
||||
calculate_average_precision_optimized([1, 0, 1]), 5 / 6, rtol=1e-10
|
||||
)
|
||||
|
||||
def test_known_example_1_1_0_1(self):
|
||||
"""Test [1,1,0,1]: score = (1 + 1 + 3/4) / 3 = 11/12."""
|
||||
assert np.isclose(
|
||||
calculate_average_precision_optimized([1, 1, 0, 1]), 11 / 12, rtol=1e-10
|
||||
)
|
||||
|
||||
def test_early_positives_score_higher(self):
|
||||
"""Earlier positives should score higher than later positives."""
|
||||
early = calculate_average_precision_optimized([1, 1, 0, 0, 0])
|
||||
late = calculate_average_precision_optimized([0, 0, 0, 1, 1])
|
||||
assert early > late
|
||||
|
||||
@pytest.mark.parametrize("seed", [42, 123, 456])
|
||||
def test_random_inputs(self, seed):
|
||||
"""Test with random inputs for robustness."""
|
||||
np.random.seed(seed)
|
||||
for length in [10, 50, 100]:
|
||||
verdict_list = np.random.choice([0, 1], size=length).tolist()
|
||||
original = calculate_average_precision_original(verdict_list)
|
||||
optimized = calculate_average_precision_optimized(verdict_list)
|
||||
assert np.isclose(original, optimized, rtol=1e-10, atol=1e-10)
|
||||
Reference in New Issue
Block a user