Skip to main content

storage_kit/core/
vector.rs

1use serde::{Deserialize, Serialize};
2
3use super::error::StorageError;
4
5/// Dense vector payload stored in a backend-supported numeric representation.
6///
7/// # Examples
8///
9/// ```
10/// let vector = storage_kit::database::data::DenseVector::F32(vec![0.1, 0.2, 0.3]);
11/// assert_eq!(vector.len(), 3);
12/// ```
13#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
14pub enum DenseVector {
15    /// Dense vector stored as `f32`.
16    F32(Vec<f32>),
17    /// Dense vector stored as `f64`.
18    F64(Vec<f64>),
19    /// Dense vector stored as `i16`.
20    I16(Vec<i16>),
21    /// Dense vector stored as `i32`.
22    I32(Vec<i32>),
23    /// Dense vector stored as `i64`.
24    I64(Vec<i64>),
25}
26
27/// Controls numeric conversion when a backend requires `f32` vectors.
28///
29/// # Examples
30///
31/// ```
32/// let vector = storage_kit::database::data::DenseVector::I16(vec![1, 2, 3]);
33/// let values = vector
34///     .to_f32(storage_kit::database::query::VectorCoercionPolicy::Lossless)
35///     .expect("i16 values fit in f32 exactly");
36/// assert_eq!(values, vec![1.0, 2.0, 3.0]);
37/// ```
38#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
39pub enum VectorCoercionPolicy {
40    /// Reject every conversion except an already-`f32` vector.
41    Exact,
42    /// Permit only conversions whose concrete values round-trip exactly.
43    Lossless,
44    /// Allows numeric downcasts even when precision may be changed.
45    Lossy,
46}
47
48impl DenseVector {
49    /// Returns the vector dimensionality.
50    ///
51    /// # Examples
52    ///
53    /// ```
54    /// let vector = storage_kit::database::data::DenseVector::I32(vec![1, 2, 3, 4]);
55    /// assert_eq!(vector.len(), 4);
56    /// ```
57    pub fn len(&self) -> usize {
58        match self {
59            Self::F32(v) => v.len(),
60            Self::F64(v) => v.len(),
61            Self::I16(v) => v.len(),
62            Self::I32(v) => v.len(),
63            Self::I64(v) => v.len(),
64        }
65    }
66
67    /// Returns `true` when the vector has zero elements.
68    ///
69    /// # Examples
70    ///
71    /// ```
72    /// let vector = storage_kit::database::data::DenseVector::F64(Vec::new());
73    /// assert!(vector.is_empty());
74    /// ```
75    pub fn is_empty(&self) -> bool {
76        self.len() == 0
77    }
78
79    /// Converts this vector into `f32` values under an explicit coercion policy.
80    ///
81    /// # Examples
82    ///
83    /// ```
84    /// let vector = storage_kit::database::data::DenseVector::F64(vec![1.0, 2.5]);
85    /// let values = vector
86    ///     .to_f32(storage_kit::database::query::VectorCoercionPolicy::Lossless)
87    ///     .expect("these f64 values are exactly representable as f32");
88    /// assert_eq!(values, vec![1.0, 2.5]);
89    /// ```
90    ///
91    /// # Errors
92    ///
93    /// Returns an error when the requested policy rejects the source vector's
94    /// representation or one of its concrete values.
95    pub fn to_f32(&self, policy: VectorCoercionPolicy) -> Result<Vec<f32>, StorageError> {
96        match self {
97            Self::F32(v) => Ok(v.clone()),
98            Self::F64(v) => Self::convert_f64_to_f32(v, policy),
99            Self::I16(v) => Self::convert_i16_to_f32(v, policy),
100            Self::I32(v) => Self::convert_i32_to_f32(v, policy),
101            Self::I64(v) => Self::convert_i64_to_f32(v, policy),
102        }
103    }
104
105    fn conversion_rejected(source: &'static str, policy: VectorCoercionPolicy) -> StorageError {
106        StorageError::InvalidRequest(format!(
107            "cannot convert {source} dense vector to f32 under {policy:?} policy"
108        ))
109    }
110
111    fn value_rejected(
112        source: &'static str,
113        index: usize,
114        value: impl std::fmt::Display,
115        policy: VectorCoercionPolicy,
116    ) -> StorageError {
117        StorageError::InvalidRequest(format!(
118            "cannot convert {source} dense vector value at index {index} (`{value}`) to f32 without precision loss under {policy:?} policy"
119        ))
120    }
121
122    fn convert_f64_to_f32(
123        values: &[f64],
124        policy: VectorCoercionPolicy,
125    ) -> Result<Vec<f32>, StorageError> {
126        if !policy.allows_representation_change() {
127            return Err(Self::conversion_rejected("f64", policy));
128        }
129
130        values
131            .iter()
132            .enumerate()
133            .map(|(index, value)| {
134                let converted = *value as f32;
135                if policy.allows_lossy()
136                    || (converted.is_finite() && f64::from(converted) == *value)
137                {
138                    Ok(converted)
139                } else {
140                    Err(Self::value_rejected("f64", index, value, policy))
141                }
142            })
143            .collect()
144    }
145
146    fn convert_i16_to_f32(
147        values: &[i16],
148        policy: VectorCoercionPolicy,
149    ) -> Result<Vec<f32>, StorageError> {
150        if !policy.allows_representation_change() {
151            return Err(Self::conversion_rejected("i16", policy));
152        }
153
154        Ok(values.iter().map(|value| f32::from(*value)).collect())
155    }
156
157    fn convert_i32_to_f32(
158        values: &[i32],
159        policy: VectorCoercionPolicy,
160    ) -> Result<Vec<f32>, StorageError> {
161        if !policy.allows_representation_change() {
162            return Err(Self::conversion_rejected("i32", policy));
163        }
164
165        values
166            .iter()
167            .enumerate()
168            .map(|(index, value)| {
169                let converted = *value as f32;
170                if policy.allows_lossy() || converted as i128 == i128::from(*value) {
171                    Ok(converted)
172                } else {
173                    Err(Self::value_rejected("i32", index, value, policy))
174                }
175            })
176            .collect()
177    }
178
179    fn convert_i64_to_f32(
180        values: &[i64],
181        policy: VectorCoercionPolicy,
182    ) -> Result<Vec<f32>, StorageError> {
183        if !policy.allows_representation_change() {
184            return Err(Self::conversion_rejected("i64", policy));
185        }
186
187        values
188            .iter()
189            .enumerate()
190            .map(|(index, value)| {
191                let converted = *value as f32;
192                if policy.allows_lossy() || converted as i128 == i128::from(*value) {
193                    Ok(converted)
194                } else {
195                    Err(Self::value_rejected("i64", index, value, policy))
196                }
197            })
198            .collect()
199    }
200}
201
202impl VectorCoercionPolicy {
203    fn allows_representation_change(self) -> bool {
204        matches!(self, Self::Lossless | Self::Lossy)
205    }
206
207    fn allows_lossy(self) -> bool {
208        matches!(self, Self::Lossy)
209    }
210}
211
212/// Distance metric used for vector search and index configuration.
213///
214/// # Examples
215///
216/// ```
217/// let metric = storage_kit::database::query::DistanceMetric::Cosine;
218/// assert!(matches!(metric, storage_kit::database::query::DistanceMetric::Cosine));
219/// ```
220#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
221pub enum DistanceMetric {
222    /// Uses Euclidean distance for vector comparisons.
223    L2,
224    /// Cosine distance or similarity, depending on backend semantics.
225    Cosine,
226    /// Uses Hamming distance for vector comparisons.
227    Hamming,
228}