Skip to main content

hermes_core/query/vector/
binary_dense.rs

1//! Binary dense vector query for Hamming distance search
2
3use crate::dsl::Field;
4use crate::segment::SegmentReader;
5use std::sync::{Arc, Mutex};
6
7use super::VectorResultScorer;
8use super::combiner::MultiValueCombiner;
9use crate::query::traits::{CountFuture, Query, Scorer, ScorerFuture};
10
11/// Binary dense vector query for Hamming distance similarity search
12///
13/// Uses global IVF routing when built and a brute-force fallback while the
14/// field is accumulating. Leaf scoring remains exact XOR + popcount.
15#[derive(Debug, Clone)]
16pub struct BinaryDenseVectorQuery {
17    /// Field containing the binary dense vectors
18    pub field: Field,
19    /// Query vector (packed bits, ceil(dim/8) bytes)
20    pub vector: Vec<u8>,
21    /// How to combine scores for multi-valued documents
22    pub combiner: MultiValueCombiner,
23    probe_cache: Arc<Mutex<Option<crate::structures::IvfProbePlan>>>,
24    /// Shared copy of `vector` for the async per-segment scorer futures,
25    /// which cannot borrow `self`. Built once; revalidated against `vector`
26    /// (which is `pub`) before reuse.
27    shared_vector: std::sync::OnceLock<Arc<[u8]>>,
28}
29
30impl std::fmt::Display for BinaryDenseVectorQuery {
31    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32        write!(
33            f,
34            "BinaryDense({}, bytes={})",
35            self.field.0,
36            self.vector.len(),
37        )
38    }
39}
40
41impl BinaryDenseVectorQuery {
42    pub fn new(field: Field, vector: Vec<u8>) -> Self {
43        Self {
44            field,
45            vector,
46            combiner: MultiValueCombiner::Max,
47            probe_cache: Arc::new(Mutex::new(None)),
48            shared_vector: std::sync::OnceLock::new(),
49        }
50    }
51
52    /// The packed query as a shared slice for scorer futures (one allocation
53    /// per query, not per segment). See `DenseVectorQuery::shared_vector`.
54    fn shared_vector(&self) -> Arc<[u8]> {
55        let shared = self
56            .shared_vector
57            .get_or_init(|| Arc::from(self.vector.as_slice()));
58        if shared.as_ref() == self.vector.as_slice() {
59            Arc::clone(shared)
60        } else {
61            Arc::from(self.vector.as_slice())
62        }
63    }
64
65    pub fn with_combiner(mut self, combiner: MultiValueCombiner) -> Self {
66        self.combiner = combiner;
67        self
68    }
69}
70
71impl Query for BinaryDenseVectorQuery {
72    fn scorer<'a>(&self, reader: &'a SegmentReader, limit: usize) -> ScorerFuture<'a> {
73        let field = self.field;
74        let vector = self.shared_vector();
75        let combiner = self.combiner;
76        let probe_cache = Arc::clone(&self.probe_cache);
77        Box::pin(async move {
78            let results = reader
79                .search_binary_dense_vector_with_probe_cache(
80                    field,
81                    &vector,
82                    limit,
83                    combiner,
84                    &probe_cache,
85                )
86                .await?;
87
88            Ok(Box::new(VectorResultScorer::new(results, field.0)) as Box<dyn Scorer>)
89        })
90    }
91
92    #[cfg(feature = "sync")]
93    fn scorer_sync<'a>(
94        &self,
95        reader: &'a SegmentReader,
96        limit: usize,
97    ) -> crate::Result<Box<dyn Scorer + 'a>> {
98        let results = reader.search_binary_dense_vector_sync_with_probe_cache(
99            self.field,
100            &self.vector,
101            limit,
102            self.combiner,
103            &self.probe_cache,
104        )?;
105        Ok(Box::new(VectorResultScorer::new(results, self.field.0)) as Box<dyn Scorer>)
106    }
107
108    fn count_estimate<'a>(&self, _reader: &'a SegmentReader) -> CountFuture<'a> {
109        Box::pin(async move { Ok(u32::MAX) })
110    }
111}