weavatrix-search-vector 0.3.1

Persistent, mutable, bounded vector candidate search for Rust and Weavatrix
Documentation
use super::graph_build::build_graphs;
use super::{Graph, RoutingIndex, VectorIndex};
use crate::config::IndexConfig;
use crate::error::SearchError;
use crate::vector::VectorStore;
use std::sync::Arc;

impl VectorIndex {
    /// Builds independently seeded HNSW replicas over validated normalized
    /// vectors.
    ///
    /// Input order does not affect the resulting graph when keys, vectors,
    /// config, and seed are unchanged.
    ///
    /// # Errors
    ///
    /// Returns typed configuration, vector, capacity, allocation, or worker
    /// failures.
    pub fn build(config: IndexConfig, vectors: &[(u64, &[f32])]) -> Result<Self, SearchError> {
        config.validate()?;
        let vectors = Arc::new(VectorStore::build(
            config.dimensions,
            config.metric,
            vectors,
        )?);
        let graphs = build_graphs(&vectors, &config)?;
        let routing = RoutingIndex::build(&vectors)?;
        Ok(Self {
            config,
            vectors,
            graphs,
            routing,
        })
    }

    pub(crate) fn from_parts(
        config: IndexConfig,
        vectors: VectorStore,
        graphs: Vec<Graph>,
        routing: RoutingIndex,
    ) -> Result<Self, SearchError> {
        config.validate()?;
        if graphs.len() != config.replicas {
            return Err(SearchError::CorruptSnapshot(
                "graph replica count does not match index config",
            ));
        }
        if graphs
            .iter()
            .any(|graph| graph.nodes.len() != vectors.len())
        {
            return Err(SearchError::CorruptSnapshot(
                "graph node count does not match vector count",
            ));
        }
        Ok(Self {
            config,
            vectors: Arc::new(vectors),
            graphs,
            routing,
        })
    }

    #[must_use]
    pub fn len(&self) -> usize {
        self.vectors.len()
    }

    #[must_use]
    pub fn is_empty(&self) -> bool {
        self.vectors.is_empty()
    }

    #[must_use]
    pub const fn dimensions(&self) -> usize {
        self.config.dimensions
    }

    #[must_use]
    pub fn config(&self) -> &IndexConfig {
        &self.config
    }

    /// Iterates stable caller-provided keys in ascending order.
    #[must_use]
    pub fn keys(&self) -> impl ExactSizeIterator<Item = u64> + '_ {
        self.vectors.keys().iter().copied()
    }

    /// Returns the normalized vector stored for `key`.
    #[must_use]
    pub fn vector(&self, key: u64) -> Option<&[f32]> {
        self.vectors
            .find_index(key)
            .map(|index| self.vectors.vector(index))
    }

    pub(crate) fn vectors(&self) -> &VectorStore {
        &self.vectors
    }

    pub(crate) fn graphs(&self) -> &[Graph] {
        &self.graphs
    }

    pub(crate) fn routing(&self) -> &RoutingIndex {
        &self.routing
    }

    /// Returns an allocation-based estimate of resident vector and graph
    /// storage. It excludes allocator metadata and temporary query scratch.
    #[must_use]
    pub fn estimated_memory_bytes(&self) -> usize {
        self.vectors
            .estimated_bytes()
            .saturating_add(self.graphs.iter().map(Graph::estimated_bytes).sum())
            .saturating_add(self.routing.estimated_bytes())
    }
}