Skip to main content

weavatrix_search_vector/hnsw/
index_build.rs

1use super::graph_build::build_graphs;
2use super::{Graph, RoutingIndex, VectorIndex};
3use crate::config::IndexConfig;
4use crate::error::SearchError;
5use crate::vector::VectorStore;
6use std::sync::Arc;
7
8impl VectorIndex {
9    /// Builds independently seeded HNSW replicas over validated normalized
10    /// vectors.
11    ///
12    /// Input order does not affect the resulting graph when keys, vectors,
13    /// config, and seed are unchanged.
14    ///
15    /// # Errors
16    ///
17    /// Returns typed configuration, vector, capacity, allocation, or worker
18    /// failures.
19    pub fn build(config: IndexConfig, vectors: &[(u64, &[f32])]) -> Result<Self, SearchError> {
20        config.validate()?;
21        let vectors = Arc::new(VectorStore::build(
22            config.dimensions,
23            config.metric,
24            vectors,
25        )?);
26        let graphs = build_graphs(&vectors, &config)?;
27        let routing = RoutingIndex::build(&vectors)?;
28        Ok(Self {
29            config,
30            vectors,
31            graphs,
32            routing,
33        })
34    }
35
36    pub(crate) fn from_parts(
37        config: IndexConfig,
38        vectors: VectorStore,
39        graphs: Vec<Graph>,
40        routing: RoutingIndex,
41    ) -> Result<Self, SearchError> {
42        config.validate()?;
43        if graphs.len() != config.replicas {
44            return Err(SearchError::CorruptSnapshot(
45                "graph replica count does not match index config",
46            ));
47        }
48        if graphs
49            .iter()
50            .any(|graph| graph.nodes.len() != vectors.len())
51        {
52            return Err(SearchError::CorruptSnapshot(
53                "graph node count does not match vector count",
54            ));
55        }
56        Ok(Self {
57            config,
58            vectors: Arc::new(vectors),
59            graphs,
60            routing,
61        })
62    }
63
64    #[must_use]
65    pub fn len(&self) -> usize {
66        self.vectors.len()
67    }
68
69    #[must_use]
70    pub fn is_empty(&self) -> bool {
71        self.vectors.is_empty()
72    }
73
74    #[must_use]
75    pub const fn dimensions(&self) -> usize {
76        self.config.dimensions
77    }
78
79    #[must_use]
80    pub fn config(&self) -> &IndexConfig {
81        &self.config
82    }
83
84    /// Iterates stable caller-provided keys in ascending order.
85    #[must_use]
86    pub fn keys(&self) -> impl ExactSizeIterator<Item = u64> + '_ {
87        self.vectors.keys().iter().copied()
88    }
89
90    /// Returns the normalized vector stored for `key`.
91    #[must_use]
92    pub fn vector(&self, key: u64) -> Option<&[f32]> {
93        self.vectors
94            .find_index(key)
95            .map(|index| self.vectors.vector(index))
96    }
97
98    pub(crate) fn vectors(&self) -> &VectorStore {
99        &self.vectors
100    }
101
102    pub(crate) fn graphs(&self) -> &[Graph] {
103        &self.graphs
104    }
105
106    pub(crate) fn routing(&self) -> &RoutingIndex {
107        &self.routing
108    }
109
110    /// Returns an allocation-based estimate of resident vector and graph
111    /// storage. It excludes allocator metadata and temporary query scratch.
112    #[must_use]
113    pub fn estimated_memory_bytes(&self) -> usize {
114        self.vectors
115            .estimated_bytes()
116            .saturating_add(self.graphs.iter().map(Graph::estimated_bytes).sum())
117            .saturating_add(self.routing.estimated_bytes())
118    }
119}