MCPcopy Create free account
hub / github.com/NodeDB-Lab/nodedb / dot_product_topk

Function dot_product_topk

nodedb/src/engine/vector/sparse/search.rs:32–83  ·  view source on GitHub ↗

Search the sparse inverted index for documents most similar to the query. Computes dot-product scores by iterating query dimensions and accumulating weights from posting lists. Returns top-K results sorted by score descending. Complexity: O(Σ postings_length for query dimensions + K log K).

(
    index: &SparseInvertedIndex,
    query: &SparseVector,
    top_k: usize,
)

Source from the content-addressed store, hash-verified

30///
31/// Complexity: O(Σ postings_length for query dimensions + K log K).
32pub fn dot_product_topk(
33 index: &SparseInvertedIndex,
34 query: &SparseVector,
35 top_k: usize,
36) -> Vec<SparseSearchResult> {
37 if query.is_empty() || index.is_empty() || top_k == 0 {
38 return Vec::new();
39 }
40
41 // Accumulate scores per document.
42 let mut scores: HashMap<u32, f32> = HashMap::new();
43
44 for &(dim, q_weight) in query.entries() {
45 if let Some(postings) = index.get_postings(dim) {
46 for &(doc_id, doc_weight) in postings {
47 *scores.entry(doc_id).or_insert(0.0) += q_weight * doc_weight;
48 }
49 }
50 }
51
52 if scores.is_empty() {
53 return Vec::new();
54 }
55
56 // Top-K selection via min-heap bounded to K entries.
57 let mut heap: BinaryHeap<std::cmp::Reverse<HeapEntry>> = BinaryHeap::with_capacity(top_k + 1);
58
59 for (doc_id, score) in &scores {
60 heap.push(std::cmp::Reverse(HeapEntry {
61 score: *score,
62 doc_id: *doc_id,
63 }));
64 if heap.len() > top_k {
65 heap.pop(); // Remove smallest.
66 }
67 }
68
69 // Drain heap into results (highest score first).
70 // `into_sorted_vec` on `BinaryHeap<Reverse<T>>` returns ascending `Reverse`
71 // order, which is descending actual score order.
72 let results: Vec<SparseSearchResult> = heap
73 .into_sorted_vec()
74 .into_iter()
75 .map(|std::cmp::Reverse(entry)| SparseSearchResult {
76 internal_id: entry.doc_id,
77 score: entry.score,
78 doc_id: index.resolve_doc_id(entry.doc_id).map(String::from),
79 })
80 .collect();
81
82 results
83}
84
85/// Min-heap entry: ordered by score ascending so the heap root is the minimum.
86#[derive(Debug)]

Callers 5

execute_sparse_searchMethod · 0.85
basic_searchFunction · 0.85
topk_limits_resultsFunction · 0.85
empty_queryFunction · 0.85
no_overlapFunction · 0.85

Calls 8

entryMethod · 0.80
collectMethod · 0.80
resolve_doc_idMethod · 0.80
is_emptyMethod · 0.45
entriesMethod · 0.45
get_postingsMethod · 0.45
pushMethod · 0.45
lenMethod · 0.45

Tested by 4

basic_searchFunction · 0.68
topk_limits_resultsFunction · 0.68
empty_queryFunction · 0.68
no_overlapFunction · 0.68