Skip to main content

weavatrix_search_vector/
config.rs

1use crate::error::SearchError;
2
3/// Distance function used by an index.
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
5#[non_exhaustive]
6pub enum DistanceMetric {
7    /// One minus cosine similarity.
8    #[default]
9    Cosine,
10    /// Negative inner product. Smaller values are better.
11    Dot,
12    /// Squared Euclidean distance.
13    SquaredEuclidean,
14}
15
16/// Immutable HNSW construction and query policy.
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct IndexConfig {
19    /// Number of scalar components in every stored vector and query.
20    pub dimensions: usize,
21    /// Distance metric used for graph construction and search.
22    pub metric: DistanceMetric,
23    /// Outgoing upper-layer link budget. Layer zero uses twice this budget;
24    /// retained reverse links may increase a node's final degree.
25    pub connectivity: usize,
26    /// Candidate width used while constructing graph links.
27    pub expansion_build: usize,
28    /// Candidate width used by approximate queries.
29    pub expansion_query: usize,
30    /// Independently seeded deterministic HNSW graphs.
31    pub replicas: usize,
32    /// Maximum worker budget shared by replicas and bulk-construction waves.
33    pub build_threads: usize,
34    /// Maximum workers used by [`crate::VectorIndex::search_batch`].
35    pub query_threads: usize,
36    /// Fixed seed for levels and insertion order.
37    pub seed: u64,
38}
39
40impl IndexConfig {
41    /// Creates a portable default configuration for `dimensions`.
42    #[must_use]
43    pub fn new(dimensions: usize) -> Self {
44        let workers = std::thread::available_parallelism()
45            .map_or(1, std::num::NonZeroUsize::get)
46            .min(16);
47        Self {
48            dimensions,
49            metric: DistanceMetric::Cosine,
50            connectivity: 12,
51            expansion_build: 48,
52            expansion_query: 24,
53            replicas: 1,
54            build_threads: workers,
55            query_threads: workers,
56            seed: 0x6a09_e667_f3bc_c909,
57        }
58    }
59
60    /// Validates dimensions, graph widths, and worker bounds.
61    ///
62    /// # Errors
63    ///
64    /// Returns [`SearchError::InvalidConfig`] for an unusable value or
65    /// [`SearchError::CapacityOverflow`] when degree arithmetic overflows.
66    pub fn validate(&self) -> Result<(), SearchError> {
67        if self.dimensions == 0 {
68            return Err(SearchError::InvalidConfig("dimensions must be non-zero"));
69        }
70        if self.connectivity < 2 {
71            return Err(SearchError::InvalidConfig(
72                "connectivity must be at least two",
73            ));
74        }
75        if self.expansion_build < self.connectivity {
76            return Err(SearchError::InvalidConfig(
77                "expansion_build must be at least connectivity",
78            ));
79        }
80        if self.expansion_query == 0 {
81            return Err(SearchError::InvalidConfig(
82                "expansion_query must be non-zero",
83            ));
84        }
85        if self.replicas == 0 {
86            return Err(SearchError::InvalidConfig("replicas must be non-zero"));
87        }
88        if self.build_threads == 0 {
89            return Err(SearchError::InvalidConfig("build_threads must be non-zero"));
90        }
91        if self.query_threads == 0 {
92            return Err(SearchError::InvalidConfig("query_threads must be non-zero"));
93        }
94        self.connectivity
95            .checked_mul(2)
96            .ok_or(SearchError::CapacityOverflow)?;
97        Ok(())
98    }
99}