Skip to main content

weavatrix_search_vector/hnsw/
policy.rs

1use crate::error::SearchError;
2use crate::vector::{MAX_ROUTING_PROBES as VECTOR_MAX_ROUTING_PROBES, ROUTING_PROBES};
3
4/// Completeness policy for filtered approximate search.
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
6pub enum FilterSearchPolicy {
7    /// Apply the predicate while traversing HNSW and return the candidates
8    /// reached without a full vector scan.
9    Traversal,
10    /// Use traversal filtering first, then use the exact oracle only when the
11    /// graph cannot supply the requested number of accepted results.
12    #[default]
13    ExactFallback,
14}
15
16/// Per-query approximation policy.
17///
18/// Increasing `expansion` explores more graph nodes. Increasing
19/// `routing_probes` recovers more candidates from nearby deterministic routing
20/// buckets, which is useful when graph expansion alone no longer improves
21/// recall.
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub struct SearchPolicy {
24    /// Minimum number of graph candidates retained per replica.
25    pub expansion: usize,
26    /// Number of deterministic routing buckets probed after graph traversal.
27    ///
28    /// Values from `1` through [`Self::MAX_ROUTING_PROBES`] are supported.
29    pub routing_probes: usize,
30}
31
32impl SearchPolicy {
33    /// Largest supported routing recovery width.
34    pub const MAX_ROUTING_PROBES: usize = VECTOR_MAX_ROUTING_PROBES;
35
36    /// Creates the low-overhead default policy used by
37    /// [`crate::VectorIndex::search`].
38    #[must_use]
39    pub const fn new(expansion: usize) -> Self {
40        Self {
41            expansion,
42            routing_probes: ROUTING_PROBES,
43        }
44    }
45
46    /// Creates a stronger deterministic recovery policy for recall-sensitive
47    /// queries.
48    #[must_use]
49    pub const fn high_recall(expansion: usize) -> Self {
50        Self {
51            expansion,
52            routing_probes: 12,
53        }
54    }
55
56    /// Changes only the routing recovery width.
57    #[must_use]
58    pub const fn with_routing_probes(mut self, routing_probes: usize) -> Self {
59        self.routing_probes = routing_probes;
60        self
61    }
62
63    pub(crate) fn validate(self) -> Result<Self, SearchError> {
64        if self.expansion == 0 {
65            return Err(SearchError::InvalidConfig(
66                "search policy expansion must be greater than zero",
67            ));
68        }
69        if self.routing_probes == 0 || self.routing_probes > Self::MAX_ROUTING_PROBES {
70            return Err(SearchError::InvalidConfig(
71                "search policy routing_probes must be between 1 and 470",
72            ));
73        }
74        Ok(self)
75    }
76}