weavatrix-search-vector 0.3.1

Persistent, mutable, bounded vector candidate search for Rust and Weavatrix
Documentation
use crate::error::SearchError;

/// Distance function used by an index.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum DistanceMetric {
    /// One minus cosine similarity.
    #[default]
    Cosine,
    /// Negative inner product. Smaller values are better.
    Dot,
    /// Squared Euclidean distance.
    SquaredEuclidean,
}

/// Immutable HNSW construction and query policy.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IndexConfig {
    /// Number of scalar components in every stored vector and query.
    pub dimensions: usize,
    /// Distance metric used for graph construction and search.
    pub metric: DistanceMetric,
    /// Outgoing upper-layer link budget. Layer zero uses twice this budget;
    /// retained reverse links may increase a node's final degree.
    pub connectivity: usize,
    /// Candidate width used while constructing graph links.
    pub expansion_build: usize,
    /// Candidate width used by approximate queries.
    pub expansion_query: usize,
    /// Independently seeded deterministic HNSW graphs.
    pub replicas: usize,
    /// Maximum worker budget shared by replicas and bulk-construction waves.
    pub build_threads: usize,
    /// Maximum workers used by [`crate::VectorIndex::search_batch`].
    pub query_threads: usize,
    /// Fixed seed for levels and insertion order.
    pub seed: u64,
}

impl IndexConfig {
    /// Creates a portable default configuration for `dimensions`.
    #[must_use]
    pub fn new(dimensions: usize) -> Self {
        let workers = std::thread::available_parallelism()
            .map_or(1, std::num::NonZeroUsize::get)
            .min(16);
        Self {
            dimensions,
            metric: DistanceMetric::Cosine,
            connectivity: 12,
            expansion_build: 48,
            expansion_query: 24,
            replicas: 1,
            build_threads: workers,
            query_threads: workers,
            seed: 0x6a09_e667_f3bc_c909,
        }
    }

    /// Validates dimensions, graph widths, and worker bounds.
    ///
    /// # Errors
    ///
    /// Returns [`SearchError::InvalidConfig`] for an unusable value or
    /// [`SearchError::CapacityOverflow`] when degree arithmetic overflows.
    pub fn validate(&self) -> Result<(), SearchError> {
        if self.dimensions == 0 {
            return Err(SearchError::InvalidConfig("dimensions must be non-zero"));
        }
        if self.connectivity < 2 {
            return Err(SearchError::InvalidConfig(
                "connectivity must be at least two",
            ));
        }
        if self.expansion_build < self.connectivity {
            return Err(SearchError::InvalidConfig(
                "expansion_build must be at least connectivity",
            ));
        }
        if self.expansion_query == 0 {
            return Err(SearchError::InvalidConfig(
                "expansion_query must be non-zero",
            ));
        }
        if self.replicas == 0 {
            return Err(SearchError::InvalidConfig("replicas must be non-zero"));
        }
        if self.build_threads == 0 {
            return Err(SearchError::InvalidConfig("build_threads must be non-zero"));
        }
        if self.query_threads == 0 {
            return Err(SearchError::InvalidConfig("query_threads must be non-zero"));
        }
        self.connectivity
            .checked_mul(2)
            .ok_or(SearchError::CapacityOverflow)?;
        Ok(())
    }
}