velesdb-core 1.13.5

High-performance vector database engine written in Rust
Documentation
//! Fusion configuration types for hybrid search.
//!
//! This module defines fusion strategies and configurations
//! for combining vector and graph search results.

use serde::{Deserialize, Serialize};

/// Fusion strategy type for hybrid search (EPIC-040 US-005).
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub enum FusionStrategyType {
    /// Reciprocal Rank Fusion (default).
    #[default]
    Rrf,
    /// Weighted sum of normalized scores.
    Weighted,
    /// Take maximum score from either source.
    Maximum,
    /// Reciprocal Sparse Fusion for dense + sparse hybrid search.
    Rsf,
    /// Average score across all queries where the document appears.
    Average,
}

/// USING FUSION clause for hybrid search (EPIC-040 US-005).
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FusionClause {
    /// Fusion strategy (rrf, weighted, maximum, rsf, average).
    pub strategy: FusionStrategyType,
    /// RRF k parameter (default 60).
    pub k: Option<u32>,
    /// Vector weight for weighted fusion (0.0-1.0).
    pub vector_weight: Option<f64>,
    /// Graph weight for weighted fusion (0.0-1.0).
    pub graph_weight: Option<f64>,
    /// Dense vector weight for RSF fusion (0.0-1.0).
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub dense_weight: Option<f32>,
    /// Sparse vector weight for RSF fusion (0.0-1.0).
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub sparse_weight: Option<f32>,
}

impl Default for FusionClause {
    fn default() -> Self {
        Self {
            strategy: FusionStrategyType::Rrf,
            k: Some(60),
            vector_weight: None,
            graph_weight: None,
            dense_weight: None,
            sparse_weight: None,
        }
    }
}

/// Configuration for multi-vector fusion.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FusionConfig {
    /// Fusion strategy name: "average", "maximum", "rrf", "weighted".
    pub strategy: String,
    /// Strategy-specific parameters.
    pub params: std::collections::HashMap<String, f64>,
}

impl Default for FusionConfig {
    fn default() -> Self {
        Self {
            strategy: "rrf".to_string(),
            params: std::collections::HashMap::new(),
        }
    }
}

impl FusionConfig {
    /// Creates a new RRF fusion config with default k=60.
    #[must_use]
    pub fn rrf() -> Self {
        let mut params = std::collections::HashMap::new();
        params.insert("k".to_string(), 60.0);
        Self {
            strategy: "rrf".to_string(),
            params,
        }
    }

    /// Creates a weighted fusion config.
    ///
    /// # Errors
    ///
    /// Returns `Error::Config` if any weight is negative or if their sum
    /// is not approximately 1.0 (tolerance ±0.001).
    pub fn weighted(
        avg_weight: f64,
        max_weight: f64,
        hit_weight: f64,
    ) -> crate::error::Result<Self> {
        // Validate weights are non-negative
        if avg_weight < 0.0 || max_weight < 0.0 || hit_weight < 0.0 {
            return Err(crate::error::Error::Config(format!(
                "FusionConfig::weighted: all weights must be non-negative, \
                 got avg={avg_weight}, max={max_weight}, hit={hit_weight}"
            )));
        }

        // Validate weights sum to 1.0 (with tolerance for floating-point errors)
        let sum = avg_weight + max_weight + hit_weight;
        if (sum - 1.0).abs() >= 0.001 {
            return Err(crate::error::Error::Config(format!(
                "FusionConfig::weighted: weights must sum to 1.0, got sum={sum}"
            )));
        }

        let mut params = std::collections::HashMap::new();
        params.insert("avg_weight".to_string(), avg_weight);
        params.insert("max_weight".to_string(), max_weight);
        params.insert("hit_weight".to_string(), hit_weight);
        Ok(Self {
            strategy: "weighted".to_string(),
            params,
        })
    }
}