holographic_memory/core/agency/
questions.rs1use crate::core::agency::goals::GoalStore;
11use crate::core::atom_memory::AtomMemory;
12use crate::core::cognition::gaps::KnowledgeGap;
13use crate::core::cognition::hypothesis::Hypothesis;
14use crate::core::entangled::EntangledHVec;
15
16#[derive(Clone, Debug)]
18pub struct Question {
19 pub text: String,
20 pub source: QuestionSource,
21 pub goal_relevance: f64,
22}
23
24#[derive(Clone, Debug)]
26pub enum QuestionSource {
27 Gap {
28 entity: String,
29 relation: String,
30 },
31 Hypothesis {
32 entity: String,
33 relation: String,
34 proposed: String,
35 },
36 Exploration {
37 relation: String,
38 },
39}
40
41pub struct QuestionGenerator;
43
44impl QuestionGenerator {
45 pub fn from_gaps(
47 gaps: &[KnowledgeGap],
48 atom_memory: &AtomMemory,
49 goal_store: &GoalStore,
50 ) -> Vec<Question> {
51 gaps.iter()
52 .map(|gap| {
53 let text = format!("What is the {} of {}?", gap.missing_relation, gap.entity);
54 let relevance = Self::score_relevance(&text, atom_memory, goal_store);
55 Question {
56 text,
57 source: QuestionSource::Gap {
58 entity: gap.entity.clone(),
59 relation: gap.missing_relation.clone(),
60 },
61 goal_relevance: relevance,
62 }
63 })
64 .collect()
65 }
66
67 pub fn from_hypotheses(
69 hypotheses: &[Hypothesis],
70 atom_memory: &AtomMemory,
71 goal_store: &GoalStore,
72 ) -> Vec<Question> {
73 hypotheses
74 .iter()
75 .map(|hyp| {
76 let text = format!(
77 "Is {} the {} of {}? (confidence: {:.0}%)",
78 hyp.proposed_filler,
79 hyp.relation,
80 hyp.entity,
81 hyp.confidence * 100.0
82 );
83 let relevance = Self::score_relevance(&text, atom_memory, goal_store);
84 Question {
85 text,
86 source: QuestionSource::Hypothesis {
87 entity: hyp.entity.clone(),
88 relation: hyp.relation.clone(),
89 proposed: hyp.proposed_filler.clone(),
90 },
91 goal_relevance: relevance,
92 }
93 })
94 .collect()
95 }
96
97 pub fn from_relations(
99 relations: &[String],
100 atom_memory: &AtomMemory,
101 goal_store: &GoalStore,
102 ) -> Vec<Question> {
103 relations
104 .iter()
105 .map(|rel| {
106 let text = format!("What entities have the {} relation?", rel);
107 let relevance = Self::score_relevance(&text, atom_memory, goal_store);
108 Question {
109 text,
110 source: QuestionSource::Exploration {
111 relation: rel.clone(),
112 },
113 goal_relevance: relevance,
114 }
115 })
116 .collect()
117 }
118
119 pub fn prioritize(mut questions: Vec<Question>) -> Vec<Question> {
121 questions.sort_by(|a, b| {
122 b.goal_relevance
123 .partial_cmp(&a.goal_relevance)
124 .unwrap_or(std::cmp::Ordering::Equal)
125 });
126 questions
127 }
128
129 fn score_relevance(text: &str, atom_memory: &AtomMemory, goal_store: &GoalStore) -> f64 {
130 let words: Vec<&str> = text.split_whitespace().collect();
132 let vecs: Vec<EntangledHVec> = words
133 .iter()
134 .filter_map(|w| atom_memory.get(&w.to_lowercase()))
135 .collect();
136 if vecs.is_empty() {
137 return 0.0;
138 }
139 let question_vec = EntangledHVec::bundle(&vecs);
140 if question_vec.indices().is_empty() {
141 return 0.0;
142 }
143 goal_store.goal_relevance(&question_vec)
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150 use crate::core::agency::goals::Goal;
151 use crate::core::cognition::gaps::KnowledgeGap;
152 use crate::core::cognition::hypothesis::Hypothesis;
153
154 fn make_test_env() -> (AtomMemory, GoalStore) {
155 let dim = 16384;
156 let atom_mem = AtomMemory::new(dim, 3.0);
157 let goal_store = GoalStore::new();
158
159 atom_mem.get_or_insert("located_in");
160 atom_mem.get_or_insert("tokyo");
161 atom_mem.get_or_insert("europe");
162
163 goal_store.add(Goal {
164 name: "find_locations".to_string(),
165 description: "Find where things are located".to_string(),
166 vector: atom_mem.get("located_in").unwrap(),
167 relevance: 1.0,
168 urgency: 1.0,
169 cost: 0.0,
170 active: true,
171 });
172
173 (atom_mem, goal_store)
174 }
175
176 #[test]
177 fn test_from_gaps() {
178 let (atom_mem, goal_store) = make_test_env();
179 let gaps = vec![KnowledgeGap {
180 entity: "tokyo".to_string(),
181 missing_relation: "located_in".to_string(),
182 peers_with_relation: vec!["paris".to_string()],
183 peer_coverage: 1.0,
184 }];
185
186 let questions = QuestionGenerator::from_gaps(&gaps, &atom_mem, &goal_store);
187 assert_eq!(questions.len(), 1);
188 assert!(questions[0].text.contains("located_in"));
189 assert!(questions[0].text.contains("tokyo"));
190 }
191
192 #[test]
193 fn test_from_hypotheses() {
194 let (atom_mem, goal_store) = make_test_env();
195 let hypotheses = vec![Hypothesis {
196 entity: "tokyo".to_string(),
197 relation: "located_in".to_string(),
198 proposed_filler: "asia".to_string(),
199 confidence: 0.85,
200 evidence_count: 3,
201 confirmed: false,
202 }];
203
204 let questions = QuestionGenerator::from_hypotheses(&hypotheses, &atom_mem, &goal_store);
205 assert_eq!(questions.len(), 1);
206 assert!(questions[0].text.contains("asia"));
207 assert!(questions[0].text.contains("85%"));
208 }
209
210 #[test]
211 fn test_prioritize() {
212 let q1 = Question {
213 text: "low".to_string(),
214 source: QuestionSource::Exploration {
215 relation: "r".to_string(),
216 },
217 goal_relevance: 0.1,
218 };
219 let q2 = Question {
220 text: "high".to_string(),
221 source: QuestionSource::Exploration {
222 relation: "r".to_string(),
223 },
224 goal_relevance: 0.9,
225 };
226 let ranked = QuestionGenerator::prioritize(vec![q1, q2]);
227 assert_eq!(ranked[0].text, "high");
228 assert_eq!(ranked[1].text, "low");
229 }
230
231 #[test]
232 fn test_no_goals_zero_relevance() {
233 let atom_mem = AtomMemory::new(16384, 3.0);
234 let goal_store = GoalStore::new(); let gaps = vec![KnowledgeGap {
236 entity: "x".to_string(),
237 missing_relation: "r".to_string(),
238 peers_with_relation: vec![],
239 peer_coverage: 1.0,
240 }];
241 let questions = QuestionGenerator::from_gaps(&gaps, &atom_mem, &goal_store);
242 assert_eq!(questions[0].goal_relevance, 0.0);
243 }
244}