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