1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
//! Vector+graph score fusion — the ranking layer behind
//! [`MemoryService::recall_fused`](crate::service::MemoryService::recall_fused).
//!
//! Ported from the LoCoMo benchmark harness (`examples/locomo/eval.rs`), where
//! this exact re-ranking measured a generation-free lift on public multi-hop
//! benchmarks (+6.9pp both-facts recall, HotpotQA 3000-Q; +9.7pp gold-sentence
//! recall, TimeQA) that the shipped `recall()` — pure vector search — never
//! captured, because it never combined the vector and graph facets.
use ;
use crateRecollection;
/// Depth of the oversampled vector pool a fused recall re-ranks, and its floor
/// regardless of `k`: deep enough that a graph-promoted fact has room to
/// surface without evicting a genuinely stronger vector hit. Values proven on
/// HotpotQA/TimeQA/LoCoMo.
pub const POOL_FACTOR: usize = 8;
pub const POOL_MIN: usize = 64;
/// Oversampled candidate pool depth for a `k`-sized fused recall.
pub
/// A recall candidate carrying its raw vector score and (if graph-reached) a
/// graph promotion weight. Internal fusion currency — distinct from
/// [`Recollection`], the public return shape.
pub
/// Re-rank `pool ∪ reached` by `vector_score/max_score + graph_boost·graph_weight`,
/// take the top `k`. A fact both vector-ranked and graph-reached keeps its pool
/// copy (its real vector score, plus the reached weight folded in by
/// [`fused_score`]); a fact the graph reaches but the pool never ranked
/// carries `vector_score = 0.0` and rides on its `graph_weight` alone.
///
/// Equal-budget promotion, not blind eviction: a strong vector fact keeps its
/// place unless a graph-connected fact's boosted score outranks it.
pub
/// `max(vector_score, 0)/max_score + graph_boost·graph_weight`. A pure
/// vector hit (`graph_weight` absent from `weights`) keeps its bare
/// normalised similarity.
///
/// The numerator is floored at `0`, not just the divisor: Cosine scores
/// range over `[-1, 1]`, so a negative `vector_score` is a legitimate,
/// in-range "dissimilar" result, not an error state — dividing a negative
/// numerator by an epsilon-floored *positive* divisor would otherwise invert
/// its sign into an unbounded negative score (regression: an all-negative
/// pool scored around `-2.3e14`, dwarfing any `graph_boost` regardless of
/// actual relevance). Flooring the numerator instead means a fact with no
/// positive vector signal contributes `0` — the same neutral baseline a
/// graph-only candidate (`vector_score = 0.0`) already gets — so it can
/// still be promoted by a real graph connection, but by the same bounded
/// margin as any other zero-vector-signal candidate, never by an
/// astronomical, sign-flipped one.