Files
turboquant_plus/turboquant/codebook.py
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

121 lines
4.5 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.
"""Codebook construction for PolarQuant.
After random rotation, each coordinate follows Beta(d/2, d/2) on [-1/√d, 1/√d],
which converges to N(0, 1/d) for large d. We use optimal scalar quantizers for this
distribution.
Paper provides closed-form centroids for 1-bit and 2-bit. For higher bit-widths,
we use Lloyd's algorithm on the Gaussian approximation.
"""
import numpy as np
from scipy import stats
def optimal_centroids(bit_width: int, d: int) -> np.ndarray:
"""Compute optimal MSE centroids for the post-rotation coordinate distribution.
Args:
bit_width: Number of bits per coordinate (1, 2, 3, 4, ...).
d: Vector dimension (affects centroid scale).
Returns:
Sorted array of 2^bit_width centroids.
"""
n_centroids = 1 << bit_width
if bit_width == 1:
c = np.sqrt(2.0 / (np.pi * d))
return np.array([-c, c])
if bit_width == 2:
return np.array([-1.51, -0.453, 0.453, 1.51]) / np.sqrt(d)
# For b >= 3, use Lloyd's algorithm on N(0, 1/d)
return _lloyds_gaussian(n_centroids, sigma=1.0 / np.sqrt(d))
def _lloyds_gaussian(n_centroids: int, sigma: float, n_iter: int = 100) -> np.ndarray:
"""Lloyd's algorithm (iterative k-means) for optimal scalar quantization of N(0, sigma²).
Args:
n_centroids: Number of quantization levels (2^b).
sigma: Standard deviation of the Gaussian.
n_iter: Number of Lloyd iterations.
Returns:
Sorted array of optimal centroids.
"""
# Initialize boundary positions from uniform quantiles
boundaries = stats.norm.ppf(
np.linspace(0, 1, n_centroids + 1)[1:-1], scale=sigma
)
centroids = np.zeros(n_centroids)
# Initial centroids: conditional expectations within each region
centroids[0] = _gaussian_conditional_expectation(sigma, -np.inf, boundaries[0])
for i in range(1, n_centroids - 1):
centroids[i] = _gaussian_conditional_expectation(sigma, boundaries[i - 1], boundaries[i])
centroids[-1] = _gaussian_conditional_expectation(sigma, boundaries[-1], np.inf)
for _ in range(n_iter):
# Update boundaries (midpoints between consecutive centroids)
boundaries = (centroids[:-1] + centroids[1:]) / 2.0
# Update centroids (conditional expectations within each region)
centroids[0] = _gaussian_conditional_expectation(sigma, -np.inf, boundaries[0])
for i in range(1, n_centroids - 1):
centroids[i] = _gaussian_conditional_expectation(sigma, boundaries[i - 1], boundaries[i])
centroids[-1] = _gaussian_conditional_expectation(sigma, boundaries[-1], np.inf)
return np.sort(centroids)
def _gaussian_conditional_expectation(sigma: float, a: float, b: float) -> float:
"""E[X | a < X < b] where X ~ N(0, sigma²).
Uses the formula: E[X | a < X < b] = sigma² * (φ(a/σ) - φ(b/σ)) / (Φ(b/σ) - Φ(a/σ))
where φ is the PDF and Φ is the CDF of standard normal.
"""
a_std = a / sigma if np.isfinite(a) else a
b_std = b / sigma if np.isfinite(b) else b
# Use sf() for upper tail to avoid CDF cancellation at extreme values
# prob = P(a < X/σ < b) using the more numerically stable formulation
if not np.isfinite(a_std):
prob = stats.norm.cdf(b_std)
elif not np.isfinite(b_std):
prob = stats.norm.sf(a_std)
else:
prob = stats.norm.cdf(b_std) - stats.norm.cdf(a_std)
if prob < 1e-15:
# For semi-infinite intervals, use asymptotic approximation
if np.isfinite(a) and not np.isfinite(b):
return a + sigma # E[X | X > a] ≈ a + σ for extreme a
elif not np.isfinite(a) and np.isfinite(b):
return b - sigma
elif np.isfinite(a) and np.isfinite(b):
return (a + b) / 2.0
else: # pragma: no cover — both infinite always has prob=1
return 0.0
pdf_diff = stats.norm.pdf(a_std) - stats.norm.pdf(b_std)
return sigma * pdf_diff / prob
def nearest_centroid_indices(values: np.ndarray, centroids: np.ndarray) -> np.ndarray:
"""Find nearest centroid index for each value. Vectorized.
Args:
values: Array of values to quantize, shape (...).
centroids: Sorted centroid array, shape (n_centroids,).
Returns:
Integer indices into centroids array, same shape as values.
"""
# Use searchsorted for sorted centroids — O(n log k) instead of O(n * k)
# Find the insertion point, then check left and right neighbors
boundaries = (centroids[:-1] + centroids[1:]) / 2.0
return np.searchsorted(boundaries, values.ravel()).reshape(values.shape)