hippmem_write/
candidates.rs1use hippmem_core::model::links::{AssociationKeys, MatchDimension};
5use std::collections::HashSet;
6
7#[derive(Debug, Clone)]
9pub struct CandidateResult {
10 pub matched_dimensions: Vec<MatchDimension>,
12 pub entity_jaccard: f32,
14 pub topic_jaccard: f32,
16 pub temporal_overlap: usize,
19 pub goal_jaccard: f32,
21 pub event_jaccard: f32,
23 pub causal_overlap: usize,
25 pub emotion_overlap: usize,
28 pub importance_value: f32,
31 pub co_context_score: f32,
36 pub lexical_similarity: f32,
38 pub semantic_binary_similarity: f32,
40}
41
42pub fn discover_candidates(a: &AssociationKeys, b: &AssociationKeys) -> CandidateResult {
44 let mut dims = Vec::new();
45
46 let set = |v: &[u64]| -> HashSet<u64> { v.iter().copied().collect() };
47
48 let a_ent = set(&a.entity_keys);
50 let b_ent = set(&b.entity_keys);
51 let ent_intersect = a_ent.intersection(&b_ent).count();
52 let ent_union = a_ent.len() + b_ent.len() - ent_intersect;
53 let entity_jaccard = if ent_union > 0 {
54 ent_intersect as f32 / ent_union as f32
55 } else {
56 0.0
57 };
58 if entity_jaccard > 0.0 {
59 dims.push(MatchDimension::Entity);
60 }
61
62 let a_top = set(&a.topic_keys);
64 let b_top = set(&b.topic_keys);
65 let top_intersect = a_top.intersection(&b_top).count();
66 let top_union = a_top.len() + b_top.len() - top_intersect;
67 let topic_jaccard = if top_union > 0 {
68 top_intersect as f32 / top_union as f32
69 } else {
70 0.0
71 };
72 if topic_jaccard > 0.0 {
73 dims.push(MatchDimension::Topic);
74 }
75
76 let a_tmp: HashSet<u32> = a.temporal_keys.iter().copied().collect();
78 let b_tmp: HashSet<u32> = b.temporal_keys.iter().copied().collect();
79 let temporal_overlap = a_tmp.intersection(&b_tmp).count();
80 if temporal_overlap > 0 {
81 dims.push(MatchDimension::Temporal);
82 }
83
84 let a_goal = set(&a.goal_keys);
86 let b_goal = set(&b.goal_keys);
87 let goal_intersect = a_goal.intersection(&b_goal).count();
88 let goal_union = a_goal.len() + b_goal.len() - goal_intersect;
89 let goal_jaccard = if goal_union > 0 {
90 goal_intersect as f32 / goal_union as f32
91 } else {
92 0.0
93 };
94 if goal_jaccard > 0.0 {
95 dims.push(MatchDimension::Goal);
96 }
97
98 let a_evt = set(&a.event_keys);
100 let b_evt = set(&b.event_keys);
101 let evt_intersect = a_evt.intersection(&b_evt).count();
102 let evt_union = a_evt.len() + b_evt.len() - evt_intersect;
103 let event_jaccard = if evt_union > 0 {
104 evt_intersect as f32 / evt_union as f32
105 } else {
106 0.0
107 };
108 if event_jaccard > 0.0 {
109 dims.push(MatchDimension::Event);
110 }
111
112 let a_cau = set(&a.causal_keys);
114 let b_cau = set(&b.causal_keys);
115 let causal_overlap = a_cau.intersection(&b_cau).count();
116 if causal_overlap > 0 {
117 dims.push(MatchDimension::Causal);
118 }
119
120 let a_emo: HashSet<u8> = a.emotion_keys.iter().copied().collect();
122 let b_emo: HashSet<u8> = b.emotion_keys.iter().copied().collect();
123 let emotion_overlap = a_emo.intersection(&b_emo).count();
124 if emotion_overlap > 0 {
125 dims.push(MatchDimension::Emotion);
126 }
127
128 let lexical_similarity =
129 simhash_similarity(&a.lexical_signature.simhash, &b.lexical_signature.simhash);
130 let a_has_signal = a.lexical_signature.simhash.iter().any(|&x| x != 0);
133 let b_has_signal = b.lexical_signature.simhash.iter().any(|&x| x != 0);
134 if a_has_signal && b_has_signal && lexical_similarity > 0.7 {
135 dims.push(MatchDimension::Semantic);
136 }
137
138 let binary_sim = binary_similarity(
139 &a.semantic_signature.binary_code,
140 &b.semantic_signature.binary_code,
141 );
142
143 CandidateResult {
144 matched_dimensions: dims,
145 entity_jaccard,
146 topic_jaccard,
147 temporal_overlap,
148 goal_jaccard,
149 event_jaccard,
150 causal_overlap,
151 emotion_overlap,
152 importance_value: 0.0,
153 co_context_score: 0.0,
154 lexical_similarity,
155 semantic_binary_similarity: binary_sim,
156 }
157}
158
159pub(crate) fn simhash_similarity(a: &[u64; 4], b: &[u64; 4]) -> f32 {
160 let same: u32 = a
161 .iter()
162 .zip(b.iter())
163 .map(|(x, y)| 64 - (x ^ y).count_ones())
164 .sum();
165 same as f32 / 256.0
166}
167
168fn binary_similarity(a: &[u64; 2], b: &[u64; 2]) -> f32 {
169 let same: u32 = a
170 .iter()
171 .zip(b.iter())
172 .map(|(x, y)| 64 - (x ^ y).count_ones())
173 .sum();
174 same as f32 / 128.0
175}