weavatrix-search-vector 0.3.1

Persistent, mutable, bounded vector candidate search for Rust and Weavatrix
Documentation
use super::{MutableState, VectorRecord};
use crate::config::{DistanceMetric, IndexConfig};
use crate::error::SearchError;
use crate::metadata::{Metadata, MetadataIndex};
use crate::vector::squared_norm;
use std::collections::BTreeSet;

pub(super) fn prepare_vector(
    dimensions: usize,
    metric: DistanceMetric,
    vector: &[f32],
) -> Result<Vec<f32>, SearchError> {
    if vector.len() != dimensions {
        return Err(SearchError::DimensionMismatch {
            expected: dimensions,
            actual: vector.len(),
            vector: Some(0),
        });
    }
    let squared = squared_norm(vector, Some(0))?;
    if metric == DistanceMetric::Cosine && squared == 0.0 {
        return Err(SearchError::ZeroVector { vector: Some(0) });
    }
    let mut prepared = Vec::new();
    prepared
        .try_reserve_exact(dimensions)
        .map_err(|_| SearchError::AllocationFailed)?;
    if metric == DistanceMetric::Cosine {
        let inverse = squared.sqrt().recip();
        prepared.extend(vector.iter().map(|value| value * inverse));
    } else {
        prepared.extend_from_slice(vector);
    }
    Ok(prepared)
}

pub(super) fn prepare_records(
    config: &IndexConfig,
    records: &[VectorRecord],
) -> Result<Vec<(u64, Vec<f32>, Metadata)>, SearchError> {
    let mut keys = BTreeSet::new();
    let mut prepared = Vec::new();
    prepared
        .try_reserve_exact(records.len())
        .map_err(|_| SearchError::AllocationFailed)?;
    for record in records {
        if !keys.insert(record.key) {
            return Err(SearchError::DuplicateKey(record.key));
        }
        prepared.push((
            record.key,
            prepare_vector(config.dimensions, config.metric, &record.vector)?,
            record.metadata.clone(),
        ));
    }
    Ok(prepared)
}

pub(super) fn replace_metadata(metadata: &mut MetadataIndex, key: u64, value: Metadata) {
    if value.is_empty() {
        metadata.remove(key);
    } else {
        metadata.insert(key, value);
    }
}

pub(super) fn current_vector(state: &MutableState, key: u64) -> Option<&[f32]> {
    if state.deleted.contains(&key) {
        return None;
    }
    state
        .pending
        .get(&key)
        .map(Vec::as_slice)
        .or_else(|| state.sealed.as_ref().and_then(|index| index.vector(key)))
        .or_else(|| state.base.vector(key))
}

pub(super) fn key_exists(state: &MutableState, key: u64) -> bool {
    state.pending.contains_key(&key)
        || (!state.deleted.contains(&key)
            && (state
                .sealed
                .as_ref()
                .is_some_and(|index| index.vector(key).is_some())
                || state.base.vector(key).is_some()))
}

pub(super) fn current_len(state: &MutableState) -> usize {
    let mut keys = BTreeSet::new();
    keys.extend(state.base.keys().filter(|key| !state.deleted.contains(key)));
    if let Some(sealed) = &state.sealed {
        keys.extend(sealed.keys().filter(|key| !state.deleted.contains(key)));
    }
    keys.extend(state.pending.keys().copied());
    keys.len()
}