1use crate::{HawDBError, Result, ValueRef, VectorDimension};
9
10#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
11pub struct Vector {
12 encoded: Vec<u8>,
13}
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
16pub struct VectorRef<'a> {
17 encoded: &'a [u8],
18 dimension: VectorDimension,
19}
20
21impl Vector {
22 pub fn new(coordinates: &[f32]) -> Result<Self> {
23 let dimension = u32::try_from(coordinates.len())
24 .ok()
25 .and_then(VectorDimension::new)
26 .ok_or_else(|| invalid("dimension must be a positive u32"))?;
27 if coordinates.iter().any(|value| !value.is_finite()) {
28 return Err(invalid("coordinates must be finite"));
29 }
30 let bytes = coordinates
31 .len()
32 .checked_mul(4)
33 .and_then(|n| n.checked_add(4))
34 .ok_or_else(|| invalid("payload length overflow"))?;
35 let mut encoded = Vec::with_capacity(bytes);
36 encoded.extend_from_slice(&dimension.get().to_le_bytes());
37 for value in coordinates {
38 encoded.extend_from_slice(&value.to_le_bytes());
39 }
40 Ok(Self { encoded })
41 }
42
43 pub fn from_encoded(encoded: Vec<u8>) -> Result<Self> {
45 Self::from_encoded_with_work(encoded, || Ok(()), |_| Ok(()), std::convert::identity)
46 }
47
48 pub fn from_encoded_with_work<T, E>(
50 encoded: Vec<u8>,
51 start: impl FnMut() -> std::result::Result<T, E>,
52 finish: impl FnMut(T) -> std::result::Result<(), E>,
53 invalid: impl Fn(HawDBError) -> E,
54 ) -> std::result::Result<Self, E> {
55 VectorRef::from_encoded_with_work(&encoded, start, finish, invalid)?;
56 Ok(Self { encoded })
57 }
58
59 pub fn dimension(&self) -> VectorDimension {
60 VectorDimension::new(u32::from_le_bytes(
61 self.encoded[..4].try_into().expect("validated dimension"),
62 ))
63 .expect("nonzero dimension")
64 }
65 pub fn retained_capacity(&self) -> usize {
66 self.encoded.capacity()
67 }
68
69 pub fn as_ref(&self) -> VectorRef<'_> {
70 VectorRef {
71 dimension: self.dimension(),
72 encoded: &self.encoded,
73 }
74 }
75
76 pub fn encoded(&self) -> &[u8] {
77 &self.encoded
78 }
79}
80
81impl<'a> VectorRef<'a> {
82 pub fn from_encoded(encoded: &'a [u8]) -> Result<Self> {
84 Self::from_encoded_with_work(encoded, || Ok(()), |_| Ok(()), std::convert::identity)
85 }
86
87 pub fn from_encoded_with_work<T, E>(
89 encoded: &'a [u8],
90 mut start: impl FnMut() -> std::result::Result<T, E>,
91 mut finish: impl FnMut(T) -> std::result::Result<(), E>,
92 map_invalid: impl Fn(HawDBError) -> E,
93 ) -> std::result::Result<Self, E> {
94 let shape = || -> Result<VectorDimension> {
95 let prefix: [u8; 4] = encoded
96 .get(..4)
97 .and_then(|bytes| bytes.try_into().ok())
98 .ok_or_else(|| invalid("truncated dimension"))?;
99 let dimension = VectorDimension::new(u32::from_le_bytes(prefix))
100 .ok_or_else(|| invalid("dimension must be positive"))?;
101 let expected = usize::try_from(dimension.get())
102 .ok()
103 .and_then(|n| n.checked_mul(4))
104 .and_then(|n| n.checked_add(4))
105 .ok_or_else(|| invalid("payload length overflow"))?;
106 if encoded.len() != expected {
107 return Err(invalid("payload length does not match dimension"));
108 }
109 Ok(dimension)
110 };
111 let dimension = shape().map_err(&map_invalid)?;
112 for block in encoded[4..].chunks(64 * 1024) {
113 let unit = start()?;
114 let finite = block.chunks_exact(4).all(|bytes| {
115 f32::from_le_bytes(bytes.try_into().expect("fixed-width coordinate")).is_finite()
116 });
117 finish(unit)?;
118 if !finite {
119 return Err(map_invalid(invalid("coordinates must be finite")));
120 }
121 }
122 Ok(Self { dimension, encoded })
123 }
124
125 pub const fn dimension(self) -> VectorDimension {
126 self.dimension
127 }
128 pub const fn encoded(self) -> &'a [u8] {
129 self.encoded
130 }
131
132 pub fn coordinates(self) -> impl ExactSizeIterator<Item = f32> + 'a {
133 self.encoded[4..]
134 .chunks_exact(4)
135 .map(|bytes| f32::from_le_bytes(bytes.try_into().expect("fixed-width coordinate")))
136 }
137
138 pub fn to_owned(self) -> Vector {
140 Vector {
141 encoded: self.encoded.to_vec(),
142 }
143 }
144}
145
146#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
147pub enum VectorDistance {
148 Cosine,
149 Euclidean,
150}
151
152impl VectorDistance {
153 pub fn from_function_name(name: &str) -> Option<Self> {
154 if name.eq_ignore_ascii_case("cosine_distance") {
155 Some(Self::Cosine)
156 } else if name.eq_ignore_ascii_case("l2_distance") {
157 Some(Self::Euclidean)
158 } else {
159 None
160 }
161 }
162
163 pub const fn function_name(self) -> &'static str {
164 match self {
165 Self::Cosine => "cosine_distance",
166 Self::Euclidean => "l2_distance",
167 }
168 }
169
170 pub fn distance(self, left: VectorRef<'_>, right: VectorRef<'_>) -> Result<f64> {
171 self.distance_with_work(left, right, || Ok(()))
172 }
173
174 pub fn distance_with_work(
176 self,
177 left: VectorRef<'_>,
178 right: VectorRef<'_>,
179 checkpoint: impl FnMut() -> Result<()>,
180 ) -> Result<f64> {
181 if left.dimension != right.dimension {
182 return Err(invalid("distance dimensions must match"));
183 }
184 self.distance_coordinates(left.coordinates(), right.coordinates(), checkpoint)
185 }
186
187 pub fn distance_slices_with_work(
190 self,
191 left: &[f32],
192 right: &[f32],
193 checkpoint: impl FnMut() -> Result<()>,
194 ) -> Result<f64> {
195 if left.len() != right.len() {
196 return Err(invalid("distance dimensions must match"));
197 }
198 if left.is_empty() || u32::try_from(left.len()).is_err() {
199 return Err(invalid("dimension must be a positive u32"));
200 }
201 self.distance_coordinates(left.iter().copied(), right.iter().copied(), checkpoint)
202 }
203
204 fn distance_coordinates(
205 self,
206 left: impl Iterator<Item = f32>,
207 right: impl Iterator<Item = f32>,
208 mut checkpoint: impl FnMut() -> Result<()>,
209 ) -> Result<f64> {
210 let (mut dot, mut left_norm, mut right_norm, mut squared) =
211 (0.0f64, 0.0f64, 0.0f64, 0.0f64);
212 for (index, (left, right)) in left.zip(right).enumerate() {
213 if index % (64 * 1024 / (2 * std::mem::size_of::<f32>())) == 0 {
215 checkpoint()?;
216 }
217 if !left.is_finite() || !right.is_finite() {
218 return Err(invalid("coordinates must be finite"));
219 }
220 let (left, right) = (f64::from(left), f64::from(right));
221 match self {
222 Self::Euclidean => squared += (left - right).powi(2),
223 Self::Cosine => {
224 dot += left * right;
225 left_norm += left * left;
226 right_norm += right * right;
227 }
228 }
229 }
230 checkpoint()?;
231 match self {
232 Self::Euclidean => Ok(squared.sqrt()),
233 Self::Cosine if left_norm == 0.0 || right_norm == 0.0 => {
234 Err(invalid("cosine distance requires nonzero vectors"))
235 }
236 Self::Cosine => {
237 Ok(1.0 - (dot / (left_norm.sqrt() * right_norm.sqrt())).clamp(-1.0, 1.0))
238 }
239 }
240 }
241
242 pub fn evaluate(self, left: ValueRef<'_>, right: ValueRef<'_>) -> Result<Option<f64>> {
243 match (left, right) {
244 (ValueRef::Null, _) | (_, ValueRef::Null) => Ok(None),
245 (ValueRef::Vector(left), ValueRef::Vector(right)) => {
246 self.distance(left, right).map(Some)
247 }
248 _ => Err(invalid("distance arguments must be vectors")),
249 }
250 }
251}
252
253fn invalid(reason: &str) -> HawDBError {
254 HawDBError::Semantic(format!("invalid vector: {reason}"))
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260
261 #[test]
262 fn projection_distance_preserves_signed_cosine_and_canonical_parity() {
263 let query = [1.0, 0.0];
264 let values = [[1.0, 0.0], [0.0, 1.0], [-0.6, 0.8], [-1.0, 0.0]];
265 for metric in [VectorDistance::Cosine, VectorDistance::Euclidean] {
266 for coordinates in values {
267 let canonical = metric
268 .distance(
269 Vector::new(&query).unwrap().as_ref(),
270 Vector::new(&coordinates).unwrap().as_ref(),
271 )
272 .unwrap();
273 let projection = metric
274 .distance_slices_with_work(&query, &coordinates, || Ok(()))
275 .unwrap();
276 assert_eq!(projection.to_bits(), canonical.to_bits());
277 }
278 }
279 let distance = |coordinates: &[f32]| {
280 VectorDistance::Cosine
281 .distance_slices_with_work(&query, coordinates, || Ok(()))
282 .unwrap()
283 };
284 assert!(distance(&values[2]) > 1.0);
285 assert!(distance(&values[2]) < distance(&values[3]));
286 assert_eq!(distance(&values[3]), 2.0);
287 for coordinates in [&[][..], &[0.0][..], &[f32::NAN, 0.0][..], &[0.0, 0.0][..]] {
288 assert!(VectorDistance::Cosine
289 .distance_slices_with_work(&query, coordinates, || Ok(()))
290 .is_err());
291 }
292 }
293
294 #[test]
295 fn projection_distance_preserves_cooperative_failure() {
296 let coordinates = vec![1.0; 20_000];
297 let mut checkpoints = 0;
298 let error = VectorDistance::Cosine
299 .distance_slices_with_work(&coordinates, &coordinates, || {
300 checkpoints += 1;
301 if checkpoints == 2 {
302 Err(HawDBError::ProjectionBusy)
303 } else {
304 Ok(())
305 }
306 })
307 .unwrap_err();
308 assert!(matches!(error, HawDBError::ProjectionBusy));
309 assert_eq!(checkpoints, 2);
310 }
311
312 #[test]
313 fn vector_encoding_rejects_malformed_and_nonfinite_payloads() {
314 let value = Vector::new(&[1.0, -2.5]).unwrap();
315 assert_eq!(value.encoded(), &[2, 0, 0, 0, 0, 0, 128, 63, 0, 0, 32, 192]);
316 assert_eq!(
317 Vector::from_encoded(value.encoded().to_vec()).unwrap(),
318 value
319 );
320 let mut unaligned = vec![0];
321 unaligned.extend_from_slice(value.encoded());
322 assert_eq!(
323 VectorRef::from_encoded(&unaligned[1..]).unwrap(),
324 value.as_ref()
325 );
326 for size in 0..value.encoded().len() {
327 assert!(VectorRef::from_encoded(&value.encoded()[..size]).is_err());
328 }
329 assert!(VectorRef::from_encoded(&[0, 0, 0, 0]).is_err());
330 assert!(VectorRef::from_encoded(&u32::MAX.to_le_bytes()).is_err());
331 for component in [f32::NAN, f32::INFINITY, f32::NEG_INFINITY] {
332 assert!(Vector::new(&[component]).is_err());
333 let mut encoded = 1u32.to_le_bytes().to_vec();
334 encoded.extend_from_slice(&component.to_le_bytes());
335 assert!(VectorRef::from_encoded(&encoded).is_err());
336 }
337 assert!(Vector::new(&[]).is_err());
338 }
339
340 #[test]
341 fn vector_distance_is_mathematical_and_shared() {
342 let left = Vector::new(&[1.0, 0.0]).unwrap();
343 let opposite = Vector::new(&[-1.0, 0.0]).unwrap();
344 let zero = Vector::new(&[0.0, 0.0]).unwrap();
345 assert_eq!(
346 VectorDistance::Cosine
347 .distance(left.as_ref(), opposite.as_ref())
348 .unwrap(),
349 2.0
350 );
351 assert_eq!(
352 VectorDistance::Euclidean
353 .distance(left.as_ref(), zero.as_ref())
354 .unwrap(),
355 1.0
356 );
357 assert!(VectorDistance::Cosine
358 .distance(left.as_ref(), zero.as_ref())
359 .is_err());
360 assert!(VectorDistance::Euclidean
361 .distance(left.as_ref(), Vector::new(&[1.0]).unwrap().as_ref())
362 .is_err());
363 assert_eq!(
364 VectorDistance::Cosine
365 .evaluate(ValueRef::Null, ValueRef::Vector(zero.as_ref()))
366 .unwrap(),
367 None
368 );
369 let large = Vector::new(&[f32::MAX, f32::MAX]).unwrap();
370 assert!(VectorDistance::Euclidean
371 .distance(large.as_ref(), zero.as_ref())
372 .unwrap()
373 .is_finite());
374 assert!(
375 VectorDistance::Cosine
376 .distance(large.as_ref(), large.as_ref())
377 .unwrap()
378 .abs()
379 < 1e-12
380 );
381 }
382}
383
384impl std::hash::Hash for VectorRef<'_> {
385 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
386 self.encoded.hash(state);
387 }
388}
389
390#[cfg(test)]
391mod work_tests {
392 use super::*;
393
394 #[test]
395 fn shared_vector_validation_stops_between_bounded_coordinate_blocks() {
396 let vector = Vector::new(&vec![1.0; 32769]).unwrap();
397 let calls = std::cell::Cell::new(0);
398 let error = Vector::from_encoded_with_work(
399 vector.encoded().to_vec(),
400 || {
401 calls.set(calls.get() + 1);
402 if calls.get() == 2 {
403 Err("cancelled".to_owned())
404 } else {
405 Ok(())
406 }
407 },
408 |_| Ok(()),
409 |error| error.to_string(),
410 )
411 .unwrap_err();
412 assert_eq!(error, "cancelled");
413 assert_eq!(calls.get(), 2);
414 let mut encoded = vector.encoded().to_vec();
415 let last = encoded.len() - 4;
416 encoded[last..].copy_from_slice(&f32::NAN.to_le_bytes());
417 assert!(Vector::from_encoded(encoded).is_err());
418 }
419}