1use crate::distance::distance;
4use crate::error::{Result, RuvectorError};
5use crate::index::VectorIndex;
6use crate::types::{DistanceMetric, HnswConfig, SearchResult, VectorId};
7use bincode::{Decode, Encode};
8use dashmap::DashMap;
9use hnsw_rs::prelude::*;
10use parking_lot::RwLock;
11use std::sync::Arc;
12
13struct DistanceFn {
15 metric: DistanceMetric,
16}
17
18impl DistanceFn {
19 fn new(metric: DistanceMetric) -> Self {
20 Self { metric }
21 }
22}
23
24impl Distance<f32> for DistanceFn {
25 #[inline(always)]
26 fn eval(&self, a: &[f32], b: &[f32]) -> f32 {
27 use crate::simd_intrinsics;
31 match self.metric {
32 DistanceMetric::Euclidean => simd_intrinsics::euclidean_distance_simd(a, b),
33 DistanceMetric::Cosine => {
34 (1.0_f32 - simd_intrinsics::cosine_similarity_simd(a, b)).max(0.0)
37 }
38 DistanceMetric::DotProduct => {
39 (-simd_intrinsics::dot_product_simd(a, b)).max(0.0)
41 }
42 DistanceMetric::Manhattan => simd_intrinsics::manhattan_distance_simd(a, b),
43 }
44 }
45}
46
47pub struct HnswIndex {
49 inner: Arc<RwLock<HnswInner>>,
50 config: HnswConfig,
51 metric: DistanceMetric,
52 dimensions: usize,
53}
54
55struct HnswInner {
56 hnsw: Hnsw<'static, f32, DistanceFn>,
57 vectors: DashMap<VectorId, Vec<f32>>,
58 id_to_idx: DashMap<VectorId, usize>,
59 idx_to_id: DashMap<usize, VectorId>,
60 next_idx: usize,
61}
62
63#[derive(Encode, Decode, Clone)]
65pub struct HnswState {
66 vectors: Vec<(String, Vec<f32>)>,
67 id_to_idx: Vec<(String, usize)>,
68 idx_to_id: Vec<(usize, String)>,
69 next_idx: usize,
70 config: SerializableHnswConfig,
71 dimensions: usize,
72 metric: SerializableDistanceMetric,
73}
74
75#[derive(Encode, Decode, Clone)]
76struct SerializableHnswConfig {
77 m: usize,
78 ef_construction: usize,
79 ef_search: usize,
80 max_elements: usize,
81}
82
83#[derive(Encode, Decode, Clone, Copy)]
84enum SerializableDistanceMetric {
85 Euclidean,
86 Cosine,
87 DotProduct,
88 Manhattan,
89}
90
91impl From<DistanceMetric> for SerializableDistanceMetric {
92 fn from(metric: DistanceMetric) -> Self {
93 match metric {
94 DistanceMetric::Euclidean => SerializableDistanceMetric::Euclidean,
95 DistanceMetric::Cosine => SerializableDistanceMetric::Cosine,
96 DistanceMetric::DotProduct => SerializableDistanceMetric::DotProduct,
97 DistanceMetric::Manhattan => SerializableDistanceMetric::Manhattan,
98 }
99 }
100}
101
102impl From<SerializableDistanceMetric> for DistanceMetric {
103 fn from(metric: SerializableDistanceMetric) -> Self {
104 match metric {
105 SerializableDistanceMetric::Euclidean => DistanceMetric::Euclidean,
106 SerializableDistanceMetric::Cosine => DistanceMetric::Cosine,
107 SerializableDistanceMetric::DotProduct => DistanceMetric::DotProduct,
108 SerializableDistanceMetric::Manhattan => DistanceMetric::Manhattan,
109 }
110 }
111}
112
113impl HnswIndex {
114 pub fn new(dimensions: usize, metric: DistanceMetric, config: HnswConfig) -> Result<Self> {
116 let distance_fn = DistanceFn::new(metric);
117
118 let hnsw = Hnsw::<f32, DistanceFn>::new(
120 config.m,
121 config.max_elements,
122 dimensions,
123 config.ef_construction,
124 distance_fn,
125 );
126
127 Ok(Self {
128 inner: Arc::new(RwLock::new(HnswInner {
129 hnsw,
130 vectors: DashMap::new(),
131 id_to_idx: DashMap::new(),
132 idx_to_id: DashMap::new(),
133 next_idx: 0,
134 })),
135 config,
136 metric,
137 dimensions,
138 })
139 }
140
141 #[doc(hidden)]
152 #[cfg(feature = "hnsw-structural-diagnostics")]
153 pub fn structural_violations(&self) -> (Vec<String>, Vec<usize>) {
154 let guard = self.inner.read();
155 (
156 guard.hnsw.edges_above_endpoint_level(),
157 guard.hnsw.points_unreachable_at_layer0(),
158 )
159 }
160
161 pub fn config(&self) -> &HnswConfig {
162 &self.config
163 }
164
165 pub fn set_ef_search(&mut self, ef_search: usize) {
170 self.config.ef_search = ef_search;
171 }
172
173 pub fn serialize(&self) -> Result<Vec<u8>> {
175 let inner = self.inner.read();
176
177 let state = HnswState {
178 vectors: inner
179 .vectors
180 .iter()
181 .map(|entry| (entry.key().clone(), entry.value().clone()))
182 .collect(),
183 id_to_idx: inner
184 .id_to_idx
185 .iter()
186 .map(|entry| (entry.key().clone(), *entry.value()))
187 .collect(),
188 idx_to_id: inner
189 .idx_to_id
190 .iter()
191 .map(|entry| (*entry.key(), entry.value().clone()))
192 .collect(),
193 next_idx: inner.next_idx,
194 config: SerializableHnswConfig {
195 m: self.config.m,
196 ef_construction: self.config.ef_construction,
197 ef_search: self.config.ef_search,
198 max_elements: self.config.max_elements,
199 },
200 dimensions: self.dimensions,
201 metric: self.metric.into(),
202 };
203
204 bincode::encode_to_vec(&state, bincode::config::standard()).map_err(|e| {
205 RuvectorError::SerializationError(format!("Failed to serialize HNSW index: {}", e))
206 })
207 }
208
209 pub fn deserialize(bytes: &[u8]) -> Result<Self> {
211 let (state, _): (HnswState, usize) =
212 bincode::decode_from_slice(bytes, bincode::config::standard()).map_err(|e| {
213 RuvectorError::SerializationError(format!(
214 "Failed to deserialize HNSW index: {}",
215 e
216 ))
217 })?;
218
219 let config = HnswConfig {
220 m: state.config.m,
221 ef_construction: state.config.ef_construction,
222 ef_search: state.config.ef_search,
223 max_elements: state.config.max_elements,
224 };
225
226 let dimensions = state.dimensions;
227 let metric: DistanceMetric = state.metric.into();
228
229 let distance_fn = DistanceFn::new(metric);
230 let mut hnsw = Hnsw::<'static, f32, DistanceFn>::new(
231 config.m,
232 config.max_elements,
233 dimensions,
234 config.ef_construction,
235 distance_fn,
236 );
237
238 let vectors_lookup: std::collections::HashMap<&str, &Vec<f32>> = state
241 .vectors
242 .iter()
243 .map(|(id, v)| (id.as_str(), v))
244 .collect();
245
246 let id_to_idx: DashMap<VectorId, usize> = state.id_to_idx.into_iter().collect();
247 let idx_to_id: DashMap<usize, VectorId> = state.idx_to_id.into_iter().collect();
248
249 let mut sorted_entries: Vec<_> = idx_to_id
251 .iter()
252 .map(|e| (*e.key(), e.value().clone()))
253 .collect();
254 sorted_entries.sort_unstable_by_key(|(idx, _)| *idx);
255
256 for (idx, id) in &sorted_entries {
257 if let Some(vector) = vectors_lookup.get(id.as_str()) {
258 hnsw.insert_data(vector, *idx);
259 }
260 }
261
262 let vectors_map: DashMap<VectorId, Vec<f32>> = state.vectors.into_iter().collect();
263
264 Ok(Self {
265 inner: Arc::new(RwLock::new(HnswInner {
266 hnsw,
267 vectors: vectors_map,
268 id_to_idx,
269 idx_to_id,
270 next_idx: state.next_idx,
271 })),
272 config,
273 metric,
274 dimensions,
275 })
276 }
277
278 pub fn search_with_ef(
284 &self,
285 query: &[f32],
286 k: usize,
287 ef_search: usize,
288 ) -> Result<Vec<SearchResult>> {
289 if query.len() != self.dimensions {
290 return Err(RuvectorError::DimensionMismatch {
291 expected: self.dimensions,
292 actual: query.len(),
293 });
294 }
295
296 if k == 0 {
297 return Ok(vec![]);
298 }
299
300 let inner = self.inner.read();
301
302 if inner.vectors.is_empty() {
307 return Ok(vec![]);
308 }
309
310 let effective_ef = ef_search.max(k);
312
313 let neighbors = inner.hnsw.search(query, k, effective_ef);
315
316 let mut results: Vec<SearchResult> = neighbors
317 .into_iter()
318 .filter_map(|neighbor| {
319 inner.idx_to_id.get(&neighbor.d_id).map(|id| SearchResult {
320 id: id.clone(),
321 score: neighbor.distance,
322 vector: None,
323 metadata: None,
324 })
325 })
326 .collect();
327
328 results.sort_unstable_by(|a, b| {
330 a.score
331 .partial_cmp(&b.score)
332 .unwrap_or(std::cmp::Ordering::Equal)
333 });
334
335 Ok(results)
336 }
337}
338
339impl VectorIndex for HnswIndex {
340 fn add(&mut self, id: VectorId, vector: Vec<f32>) -> Result<()> {
341 if vector.len() != self.dimensions {
342 return Err(RuvectorError::DimensionMismatch {
343 expected: self.dimensions,
344 actual: vector.len(),
345 });
346 }
347
348 let mut inner = self.inner.write();
349 let idx = inner.next_idx;
350 inner.next_idx += 1;
351
352 inner.hnsw.insert_data(&vector, idx);
354
355 inner.vectors.insert(id.clone(), vector);
357 inner.id_to_idx.insert(id.clone(), idx);
358 inner.idx_to_id.insert(idx, id);
359
360 Ok(())
361 }
362
363 fn add_batch(&mut self, entries: Vec<(VectorId, Vec<f32>)>) -> Result<()> {
364 for (_, vector) in &entries {
366 if vector.len() != self.dimensions {
367 return Err(RuvectorError::DimensionMismatch {
368 expected: self.dimensions,
369 actual: vector.len(),
370 });
371 }
372 }
373
374 let mut inner = self.inner.write();
375
376 let data_with_ids: Vec<_> = entries
379 .iter()
380 .enumerate()
381 .map(|(i, (id, vector))| {
382 let idx = inner.next_idx + i;
383 (id.clone(), idx, vector.clone())
384 })
385 .collect();
386
387 inner.next_idx += entries.len();
389
390 const PARALLEL_THRESHOLD: usize = 10_000;
399 if data_with_ids.len() >= PARALLEL_THRESHOLD {
400 let datas: Vec<(&[f32], usize)> = data_with_ids
401 .iter()
402 .map(|(_id, idx, vector)| (vector.as_slice(), *idx))
403 .collect();
404 inner.hnsw.parallel_insert_slice(&datas);
405 } else {
406 for (_id, idx, vector) in &data_with_ids {
407 inner.hnsw.insert_data(vector, *idx);
408 }
409 }
410
411 for (id, idx, vector) in data_with_ids {
413 inner.vectors.insert(id.clone(), vector);
414 inner.id_to_idx.insert(id.clone(), idx);
415 inner.idx_to_id.insert(idx, id);
416 }
417
418 Ok(())
419 }
420
421 fn search(&self, query: &[f32], k: usize) -> Result<Vec<SearchResult>> {
422 self.search_with_ef(query, k, self.config.ef_search)
424 }
425
426 fn remove(&mut self, id: &VectorId) -> Result<bool> {
427 let inner = self.inner.write();
428
429 let removed = inner.vectors.remove(id).is_some();
433
434 if removed {
435 if let Some((_, idx)) = inner.id_to_idx.remove(id) {
436 inner.idx_to_id.remove(&idx);
437 }
438 }
439
440 Ok(removed)
441 }
442
443 fn len(&self) -> usize {
444 self.inner.read().vectors.len()
445 }
446}
447
448#[cfg(test)]
449mod tests {
450 use super::*;
451
452 fn generate_random_vectors(count: usize, dimensions: usize) -> Vec<Vec<f32>> {
453 use rand::Rng;
454 let mut rng = rand::thread_rng();
455
456 (0..count)
457 .map(|_| (0..dimensions).map(|_| rng.gen::<f32>()).collect())
458 .collect()
459 }
460
461 fn normalize_vector(v: &[f32]) -> Vec<f32> {
462 let norm = v.iter().map(|x| x * x).sum::<f32>().sqrt();
463 if norm > 0.0 {
464 v.iter().map(|x| x / norm).collect()
465 } else {
466 v.to_vec()
467 }
468 }
469
470 #[test]
471 fn test_hnsw_index_creation() -> Result<()> {
472 let config = HnswConfig::default();
473 let index = HnswIndex::new(128, DistanceMetric::Cosine, config)?;
474 assert_eq!(index.len(), 0);
475 Ok(())
476 }
477
478 #[test]
479 fn test_hnsw_insert_and_search() -> Result<()> {
480 let config = HnswConfig {
481 m: 16,
482 ef_construction: 100,
483 ef_search: 50,
484 max_elements: 1000,
485 };
486
487 let mut index = HnswIndex::new(128, DistanceMetric::Cosine, config)?;
488
489 let vectors = generate_random_vectors(100, 128);
491 for (i, vector) in vectors.iter().enumerate() {
492 let normalized = normalize_vector(vector);
493 index.add(format!("vec_{}", i), normalized)?;
494 }
495
496 assert_eq!(index.len(), 100);
497
498 let query = normalize_vector(&vectors[0]);
500 let results = index.search(&query, 10)?;
501
502 assert!(!results.is_empty());
503 assert_eq!(results[0].id, "vec_0");
504
505 Ok(())
506 }
507
508 #[test]
509 fn test_hnsw_batch_insert() -> Result<()> {
510 let config = HnswConfig::default();
511 let mut index = HnswIndex::new(128, DistanceMetric::Cosine, config)?;
512
513 let vectors = generate_random_vectors(100, 128);
514 let entries: Vec<_> = vectors
515 .iter()
516 .enumerate()
517 .map(|(i, v)| (format!("vec_{}", i), normalize_vector(v)))
518 .collect();
519
520 index.add_batch(entries)?;
521 assert_eq!(index.len(), 100);
522
523 Ok(())
524 }
525
526 #[test]
527 fn test_hnsw_serialization() -> Result<()> {
528 let config = HnswConfig {
529 m: 16,
530 ef_construction: 100,
531 ef_search: 50,
532 max_elements: 1000,
533 };
534
535 let mut index = HnswIndex::new(128, DistanceMetric::Cosine, config)?;
536
537 let vectors = generate_random_vectors(50, 128);
539 for (i, vector) in vectors.iter().enumerate() {
540 let normalized = normalize_vector(vector);
541 index.add(format!("vec_{}", i), normalized)?;
542 }
543
544 let bytes = index.serialize()?;
546
547 let restored_index = HnswIndex::deserialize(&bytes)?;
549
550 assert_eq!(restored_index.len(), 50);
551
552 let query = normalize_vector(&vectors[0]);
554 let results = restored_index.search(&query, 5)?;
555
556 assert!(!results.is_empty());
557
558 Ok(())
559 }
560
561 #[test]
562 fn test_dimension_mismatch() -> Result<()> {
563 let config = HnswConfig::default();
564 let mut index = HnswIndex::new(128, DistanceMetric::Cosine, config)?;
565
566 let result = index.add("test".to_string(), vec![1.0; 64]);
567 assert!(result.is_err());
568
569 Ok(())
570 }
571}