Files
c3aad548ff test: achieve 100% code coverage across all modules
Added tests for:
- codebook.py: extreme narrow finite interval fallback, both-infinite pragma
- turboquant.py: compressed_size_bits method
- utils.py: pack_indices invalid bit_width ValueError

133 tests, 100% coverage (330 statements, 0 missed).

Co-Authored-By: tturney@psyguard.ai
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 21:18:46 -05:00

248 lines
10 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Tests for codebook construction (Issue #3).
Tests written BEFORE reviewing implementation per workflow.
"""
import numpy as np
class TestOptimalCentroids:
"""Test closed-form and Lloyd's centroids."""
def test_1bit_centroids_match_paper(self):
"""1-bit centroids should be ±√(2/πd) per paper Theorem 3.1."""
from turboquant.codebook import optimal_centroids
for d in [64, 128, 256, 1024]:
centroids = optimal_centroids(1, d)
expected = np.sqrt(2.0 / (np.pi * d))
assert len(centroids) == 2
np.testing.assert_allclose(centroids[0], -expected, rtol=1e-10)
np.testing.assert_allclose(centroids[1], expected, rtol=1e-10)
def test_2bit_centroids_match_paper(self):
"""2-bit centroids should be {±0.453/√d, ±1.51/√d} per paper Table 1."""
from turboquant.codebook import optimal_centroids
for d in [64, 128, 256]:
centroids = optimal_centroids(2, d)
expected = np.array([-1.51, -0.453, 0.453, 1.51]) / np.sqrt(d)
assert len(centroids) == 4
np.testing.assert_allclose(centroids, expected, rtol=1e-10)
def test_centroids_sorted(self):
"""Centroids should always be sorted ascending."""
from turboquant.codebook import optimal_centroids
for b in [1, 2, 3, 4]:
for d in [64, 128, 256]:
centroids = optimal_centroids(b, d)
assert np.all(np.diff(centroids) > 0), (
f"Centroids not sorted for b={b}, d={d}: {centroids}"
)
def test_correct_count(self):
"""Should have exactly 2^b centroids."""
from turboquant.codebook import optimal_centroids
for b in [1, 2, 3, 4]:
centroids = optimal_centroids(b, 128)
assert len(centroids) == (1 << b)
def test_centroids_symmetric(self):
"""Centroids should be symmetric around 0 (Gaussian is symmetric)."""
from turboquant.codebook import optimal_centroids
for b in [1, 2, 3, 4]:
centroids = optimal_centroids(b, 128)
np.testing.assert_allclose(
centroids, -centroids[::-1], atol=1e-10,
err_msg=f"Centroids not symmetric for b={b}"
)
def test_lloyd_converges_3bit(self):
"""3-bit Lloyd's should produce 8 reasonable centroids."""
from turboquant.codebook import optimal_centroids
d = 128
centroids = optimal_centroids(3, d)
sigma = 1.0 / np.sqrt(d)
assert len(centroids) == 8
# All centroids should be within ~4σ of the distribution
assert np.all(np.abs(centroids) < 4 * sigma)
def test_lloyd_converges_4bit(self):
"""4-bit Lloyd's should produce 16 reasonable centroids."""
from turboquant.codebook import optimal_centroids
d = 256
centroids = optimal_centroids(4, d)
sigma = 1.0 / np.sqrt(d)
assert len(centroids) == 16
assert np.all(np.abs(centroids) < 4 * sigma)
def test_centroids_scale_with_dimension(self):
"""Centroids should shrink as d increases (scale is 1/√d)."""
from turboquant.codebook import optimal_centroids
for b in [1, 2, 3]:
c_small = optimal_centroids(b, 64)
c_large = optimal_centroids(b, 256)
# Max centroid should be ~2× smaller for 4× larger d
ratio = np.max(np.abs(c_small)) / np.max(np.abs(c_large))
assert 1.5 < ratio < 2.5, (
f"Scale ratio {ratio:.2f} unexpected for b={b}"
)
class TestGaussianConditionalExpectation:
"""Test the E[X | a < X < b] helper."""
def test_positive_half(self):
"""E[X | X > 0] for X ~ N(0,1) should be √(2/π) ≈ 0.7979."""
from turboquant.codebook import _gaussian_conditional_expectation
result = _gaussian_conditional_expectation(1.0, 0.0, np.inf)
np.testing.assert_allclose(result, np.sqrt(2.0 / np.pi), rtol=1e-6)
def test_symmetric_interval(self):
"""E[X | -a < X < a] should be 0 for symmetric interval."""
from turboquant.codebook import _gaussian_conditional_expectation
result = _gaussian_conditional_expectation(1.0, -1.0, 1.0)
np.testing.assert_allclose(result, 0.0, atol=1e-10)
def test_full_range(self):
"""E[X | -∞ < X < ∞] should be 0 for N(0, σ²)."""
from turboquant.codebook import _gaussian_conditional_expectation
result = _gaussian_conditional_expectation(2.0, -np.inf, np.inf)
np.testing.assert_allclose(result, 0.0, atol=1e-10)
def test_extreme_tail(self):
"""E[X | X > 5σ] should not be NaN/inf."""
from turboquant.codebook import _gaussian_conditional_expectation
result = _gaussian_conditional_expectation(1.0, 5.0, np.inf)
assert np.isfinite(result)
assert result > 5.0 # conditional mean must exceed the lower bound
def test_extreme_upper_tail_asymptotic(self):
"""E[X | X > 10σ] should use asymptotic fallback, not return 0."""
from turboquant.codebook import _gaussian_conditional_expectation
result = _gaussian_conditional_expectation(1.0, 10.0, np.inf)
assert np.isfinite(result)
assert result > 10.0
def test_extreme_lower_tail_asymptotic(self):
"""E[X | X < -10σ] should use asymptotic fallback."""
from turboquant.codebook import _gaussian_conditional_expectation
result = _gaussian_conditional_expectation(1.0, -np.inf, -10.0)
assert np.isfinite(result)
assert result < -10.0
def test_very_narrow_interval(self):
"""E[X | a < X < a+ε] should be ≈ a for tiny ε."""
from turboquant.codebook import _gaussian_conditional_expectation
result = _gaussian_conditional_expectation(1.0, 1.0, 1.0001)
assert np.isfinite(result)
np.testing.assert_allclose(result, 1.0, atol=0.01)
def test_extreme_narrow_finite_interval_fallback(self):
"""When prob < 1e-15 for finite [a,b], returns midpoint."""
from turboquant.codebook import _gaussian_conditional_expectation
# Both endpoints deep in same tail — probability underflows
result = _gaussian_conditional_expectation(1.0, 20.0, 20.1)
assert np.isfinite(result)
np.testing.assert_allclose(result, 20.05, atol=0.1)
def test_both_infinite_fallback(self):
"""When both a and b are infinite and prob < 1e-15, returns 0."""
from turboquant.codebook import _gaussian_conditional_expectation
# This shouldn't happen in practice, but the code handles it
# Manually force: -inf to +inf always has prob=1, so we can't trigger
# the fallback naturally. Test the logic exists by checking full range.
result = _gaussian_conditional_expectation(1.0, -np.inf, np.inf)
np.testing.assert_allclose(result, 0.0, atol=1e-10)
def test_scaled_distribution(self):
"""Should work correctly with non-unit sigma."""
from turboquant.codebook import _gaussian_conditional_expectation
sigma = 0.1
result = _gaussian_conditional_expectation(sigma, 0.0, np.inf)
expected = sigma * np.sqrt(2.0 / np.pi)
np.testing.assert_allclose(result, expected, rtol=1e-6)
class TestNearestCentroidIndices:
"""Test vectorized nearest centroid lookup."""
def test_exact_centroids(self):
"""Values exactly at centroids should map to themselves."""
from turboquant.codebook import nearest_centroid_indices
centroids = np.array([-1.0, 0.0, 1.0])
values = np.array([-1.0, 0.0, 1.0])
indices = nearest_centroid_indices(values, centroids)
np.testing.assert_array_equal(indices, [0, 1, 2])
def test_midpoint_goes_right(self):
"""Values at midpoint between centroids should go to the right one."""
from turboquant.codebook import nearest_centroid_indices
centroids = np.array([-1.0, 0.0, 1.0])
midpoint = -0.5 # midpoint between -1.0 and 0.0
indices = nearest_centroid_indices(np.array([midpoint]), centroids)
# searchsorted at midpoint goes right
assert indices[0] in [0, 1] # either is acceptable
def test_values_outside_range(self):
"""Values far outside centroid range should clamp to nearest end."""
from turboquant.codebook import nearest_centroid_indices
centroids = np.array([-1.0, 0.0, 1.0])
values = np.array([-100.0, 100.0])
indices = nearest_centroid_indices(values, centroids)
assert indices[0] == 0 # far left → first centroid
assert indices[1] == 2 # far right → last centroid
def test_batch_shape_preserved(self):
"""Output shape should match input shape."""
from turboquant.codebook import nearest_centroid_indices
centroids = np.array([-1.0, 0.0, 1.0])
values = np.random.default_rng(42).standard_normal((5, 10))
indices = nearest_centroid_indices(values, centroids)
assert indices.shape == (5, 10)
def test_matches_brute_force(self):
"""searchsorted result should match argmin brute force."""
from turboquant.codebook import nearest_centroid_indices
centroids = np.array([-1.5, -0.5, 0.5, 1.5])
values = np.random.default_rng(42).standard_normal(500)
fast_indices = nearest_centroid_indices(values, centroids)
# Brute force: for each value, find the centroid with minimum abs distance
brute_indices = np.argmin(np.abs(values[:, np.newaxis] - centroids[np.newaxis, :]), axis=1)
np.testing.assert_array_equal(fast_indices, brute_indices)
def test_all_indices_valid(self):
"""All indices should be in [0, n_centroids)."""
from turboquant.codebook import nearest_centroid_indices
centroids = np.array([-1.5, -0.5, 0.5, 1.5])
values = np.random.default_rng(42).standard_normal(1000)
indices = nearest_centroid_indices(values, centroids)
assert indices.min() >= 0
assert indices.max() < len(centroids)