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}