Skip to main content

bitpolar/
error.rs

1//! Error types for bitpolar.
2//!
3//! All public API methods return `Result<T, TurboQuantError>`. No panics
4//! in the public API — all errors are recoverable by the caller.
5
6use thiserror::Error;
7
8/// Errors that can occur during TurboQuant operations.
9///
10/// Every variant includes enough context for the caller to diagnose
11/// the issue without inspecting internal state.
12#[derive(Debug, Clone, Error)]
13pub enum TurboQuantError {
14    /// Dimension must be even (required for polar coordinate pairing).
15    ///
16    /// TurboQuant groups coordinates into pairs for polar encoding.
17    /// An odd dimension would leave one coordinate unpaired.
18    #[error("dimension must be even, got {0}")]
19    OddDimension(usize),
20
21    /// Dimension is zero — cannot create a quantizer for zero-dimensional vectors.
22    #[error("dimension must be > 0")]
23    ZeroDimension,
24
25    /// Bit width out of supported range.
26    ///
27    /// Supported range is 1..=16. At 1 bit, each angle gets 2 levels.
28    /// At 16 bits, each angle gets 65536 levels (effectively lossless angles).
29    #[error("bit width must be 1..=16, got {0}")]
30    InvalidBitWidth(u8),
31
32    /// Projection count must be positive for QJL sketching.
33    #[error("projections must be > 0")]
34    ZeroProjections,
35
36    /// Dimension exceeds the maximum supported by the compact binary format.
37    ///
38    /// The compact serialization uses u16 for pair/projection counts,
39    /// limiting dimension to 131070 (65535 pairs) and projections to 65535.
40    #[error("dimension {0} too large for compact format (max {1})")]
41    DimensionTooLarge(usize, usize),
42
43    /// Input vector dimension does not match the quantizer's expected dimension.
44    #[error("dimension mismatch: quantizer expects {expected}, got {actual}")]
45    DimensionMismatch {
46        /// The dimension the quantizer was created for
47        expected: usize,
48        /// The dimension of the input vector
49        actual: usize,
50    },
51
52    /// Attempted operation on empty data.
53    #[error("empty input: {0}")]
54    EmptyInput(&'static str),
55
56    /// Input vector contains NaN or infinity values.
57    ///
58    /// Quantization of non-finite values produces garbage output.
59    /// The caller should sanitize input before encoding.
60    #[error("input contains non-finite values (NaN or Inf) at index {index}")]
61    NonFiniteInput {
62        /// Index of the first non-finite value found
63        index: usize,
64    },
65
66    /// Deserialization of compact binary data failed.
67    ///
68    /// The byte buffer is too short, has an unrecognized version,
69    /// or contains internally inconsistent lengths.
70    #[error("deserialization failed: {reason}")]
71    DeserializationError {
72        /// Human-readable description of what went wrong
73        reason: String,
74    },
75
76    /// Index out of bounds (e.g., requesting a KV cache position that doesn't exist).
77    #[error("index {index} out of bounds (length {length})")]
78    IndexOutOfBounds {
79        /// The requested index
80        index: usize,
81        /// The actual length of the collection
82        length: usize,
83    },
84}
85
86/// Result type alias using [`TurboQuantError`].
87pub type Result<T> = core::result::Result<T, TurboQuantError>;
88
89/// Validate that a vector contains only finite f32 values.
90///
91/// Returns `Ok(())` if all values are finite, or `Err(NonFiniteInput)`
92/// with the index of the first non-finite value.
93#[inline]
94pub fn validate_finite(vector: &[f32]) -> Result<()> {
95    for (i, &v) in vector.iter().enumerate() {
96        if !v.is_finite() {
97            return Err(TurboQuantError::NonFiniteInput { index: i });
98        }
99    }
100    Ok(())
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    #[test]
108    fn test_validate_finite_ok() {
109        let v = vec![1.0, -2.5, 0.0, 1e30, -1e-30];
110        assert!(validate_finite(&v).is_ok());
111    }
112
113    #[test]
114    fn test_validate_finite_nan() {
115        let v = vec![1.0, 2.0, f32::NAN, 4.0];
116        match validate_finite(&v) {
117            Err(TurboQuantError::NonFiniteInput { index }) => assert_eq!(index, 2),
118            other => panic!("expected NonFiniteInput, got {:?}", other),
119        }
120    }
121
122    #[test]
123    fn test_validate_finite_inf() {
124        let v = vec![f32::INFINITY, 1.0];
125        match validate_finite(&v) {
126            Err(TurboQuantError::NonFiniteInput { index }) => assert_eq!(index, 0),
127            other => panic!("expected NonFiniteInput, got {:?}", other),
128        }
129    }
130
131    #[test]
132    fn test_validate_finite_neg_inf() {
133        let v = vec![1.0, f32::NEG_INFINITY];
134        match validate_finite(&v) {
135            Err(TurboQuantError::NonFiniteInput { index }) => assert_eq!(index, 1),
136            other => panic!("expected NonFiniteInput, got {:?}", other),
137        }
138    }
139
140    #[test]
141    fn test_validate_finite_empty() {
142        assert!(validate_finite(&[]).is_ok());
143    }
144
145    #[test]
146    fn test_error_display() {
147        let e = TurboQuantError::DimensionMismatch {
148            expected: 128,
149            actual: 256,
150        };
151        assert_eq!(
152            e.to_string(),
153            "dimension mismatch: quantizer expects 128, got 256"
154        );
155    }
156}