holographic_memory/core/cognition/
loop.rs1use parking_lot::RwLock;
11use std::sync::atomic::{AtomicBool, Ordering};
12use std::sync::Arc;
13use std::time::Duration;
14
15use crate::core::atom_memory::AtomMemory;
16use crate::core::cognition::abstraction::{Abstraction, AbstractionEngine};
17use crate::core::cognition::analogy::{Analogy, AnalogyDetector};
18use crate::core::cognition::gaps::{GapDetector, KnowledgeGap};
19use crate::core::cognition::hypothesis::{Hypothesis, HypothesisEngine};
20use crate::core::cognition::patterns::{PatternScanner, RelationPattern};
21use crate::core::triple_store::TripleStore;
22
23#[derive(Clone, Debug)]
25pub enum Insight {
26 Pattern(RelationPattern),
27 Abstraction(Abstraction),
28 Gap(KnowledgeGap),
29 Hypothesis(Hypothesis),
30 Analogy(Analogy),
31}
32
33#[derive(Clone, Debug)]
35pub struct CognitionConfig {
36 pub interval: Duration,
38 pub min_pattern_freq: usize,
40 pub min_abstraction_members: usize,
42 pub min_shared_relations: usize,
44 pub min_peer_coverage: f64,
46 pub hypothesis_beta: f64,
48 pub min_hypothesis_confidence: f64,
50 pub min_analogy_relations: usize,
52}
53
54impl Default for CognitionConfig {
55 fn default() -> Self {
56 Self {
57 interval: Duration::from_secs(60),
58 min_pattern_freq: 3,
59 min_abstraction_members: 3,
60 min_shared_relations: 2,
61 min_peer_coverage: 0.5,
62 hypothesis_beta: 24.0,
63 min_hypothesis_confidence: 0.3,
64 min_analogy_relations: 2,
65 }
66 }
67}
68
69pub struct CognitionState {
71 insights: RwLock<Vec<Insight>>,
72 running: AtomicBool,
73 stop: AtomicBool,
74 cycle_count: std::sync::atomic::AtomicU64,
75}
76
77impl CognitionState {
78 fn new() -> Self {
79 Self {
80 insights: RwLock::new(Vec::new()),
81 running: AtomicBool::new(false),
82 stop: AtomicBool::new(false),
83 cycle_count: std::sync::atomic::AtomicU64::new(0),
84 }
85 }
86
87 pub fn is_running(&self) -> bool {
88 self.running.load(Ordering::SeqCst)
89 }
90
91 pub fn cycle_count(&self) -> u64 {
92 self.cycle_count.load(Ordering::SeqCst)
93 }
94
95 pub fn take_insights(&self) -> Vec<Insight> {
96 std::mem::take(&mut *self.insights.write())
97 }
98
99 pub fn insight_count(&self) -> usize {
100 self.insights.read().len()
101 }
102}
103
104pub struct CognitionLoop {
107 state: Arc<CognitionState>,
108 handle: Option<std::thread::JoinHandle<()>>,
109}
110
111impl CognitionLoop {
112 pub fn start(
117 atom_memory: Arc<AtomMemory>,
118 triple_store: Arc<TripleStore>,
119 config: CognitionConfig,
120 ) -> Self {
121 let state = Arc::new(CognitionState::new());
122 let thread_state = Arc::clone(&state);
123
124 state.running.store(true, Ordering::SeqCst);
125
126 let handle = std::thread::Builder::new()
127 .name("hms-cognition".to_string())
128 .spawn(move || {
129 cognition_thread(&thread_state, &atom_memory, &triple_store, &config);
130 })
131 .expect("failed to spawn cognition thread");
132
133 Self {
134 state,
135 handle: Some(handle),
136 }
137 }
138
139 pub fn stop(&mut self) {
141 self.state.stop.store(true, Ordering::SeqCst);
142 if let Some(handle) = self.handle.take() {
143 let _ = handle.join();
144 }
145 self.state.running.store(false, Ordering::SeqCst);
146 }
147
148 pub fn state(&self) -> &Arc<CognitionState> {
150 &self.state
151 }
152
153 pub fn run_once(
155 atom_memory: &AtomMemory,
156 triple_store: &TripleStore,
157 config: &CognitionConfig,
158 ) -> Vec<Insight> {
159 run_cycle(atom_memory, triple_store, config)
160 }
161}
162
163impl Drop for CognitionLoop {
164 fn drop(&mut self) {
165 self.stop();
166 }
167}
168
169fn cognition_thread(
170 state: &CognitionState,
171 atom_memory: &AtomMemory,
172 triple_store: &TripleStore,
173 config: &CognitionConfig,
174) {
175 while !state.stop.load(Ordering::SeqCst) {
176 let new_insights = run_cycle(atom_memory, triple_store, config);
177
178 if !new_insights.is_empty() {
179 state.insights.write().extend(new_insights);
180 }
181
182 state.cycle_count.fetch_add(1, Ordering::SeqCst);
183
184 let mut remaining = config.interval;
186 let tick = Duration::from_millis(100);
187 while remaining > Duration::ZERO && !state.stop.load(Ordering::SeqCst) {
188 let sleep_time = remaining.min(tick);
189 std::thread::sleep(sleep_time);
190 remaining = remaining.saturating_sub(sleep_time);
191 }
192 }
193
194 state.running.store(false, Ordering::SeqCst);
195}
196
197fn run_cycle(
198 atom_memory: &AtomMemory,
199 triple_store: &TripleStore,
200 config: &CognitionConfig,
201) -> Vec<Insight> {
202 let mut insights = Vec::new();
203
204 let patterns = PatternScanner::scan_relation_patterns(triple_store, config.min_pattern_freq);
206 for p in patterns {
207 insights.push(Insight::Pattern(p));
208 }
209
210 let abstractions =
212 AbstractionEngine::discover(triple_store, atom_memory, config.min_abstraction_members);
213 for a in abstractions {
214 insights.push(Insight::Abstraction(a));
215 }
216
217 let gaps = GapDetector::detect(
219 triple_store,
220 config.min_shared_relations,
221 config.min_peer_coverage,
222 );
223
224 let hypotheses = HypothesisEngine::propose(
226 &gaps,
227 triple_store,
228 atom_memory,
229 config.hypothesis_beta,
230 config.min_hypothesis_confidence,
231 );
232 for h in hypotheses {
233 insights.push(Insight::Hypothesis(h));
234 }
235
236 for g in gaps {
238 insights.push(Insight::Gap(g));
239 }
240
241 let analogies = AnalogyDetector::detect(triple_store, config.min_analogy_relations);
243 for a in analogies {
244 insights.push(Insight::Analogy(a));
245 }
246
247 insights
248}
249
250#[cfg(test)]
251mod tests {
252 use super::*;
253
254 fn make_test_data() -> (AtomMemory, TripleStore) {
255 let dim = 16384;
256 let atom_mem = AtomMemory::new(dim, 3.0);
257 let triple_store = TripleStore::new();
258
259 for city in &["paris", "berlin", "tokyo", "london", "madrid"] {
260 atom_mem.get_or_insert(city);
261 }
262 for country in &["france", "germany", "japan", "uk", "spain", "europe"] {
263 atom_mem.get_or_insert(country);
264 }
265
266 triple_store.add("paris", "capital_of", "france", "c1");
267 triple_store.add("berlin", "capital_of", "germany", "c2");
268 triple_store.add("tokyo", "capital_of", "japan", "c3");
269 triple_store.add("london", "capital_of", "uk", "c4");
270 triple_store.add("madrid", "capital_of", "spain", "c5");
271 triple_store.add("paris", "located_in", "europe", "c6");
272 triple_store.add("berlin", "located_in", "europe", "c7");
273 triple_store.add("london", "located_in", "europe", "c8");
274 triple_store.add("madrid", "located_in", "europe", "c9");
275 (atom_mem, triple_store)
278 }
279
280 #[test]
281 fn test_run_once() {
282 let (atom_mem, triple_store) = make_test_data();
283 let config = CognitionConfig::default();
284 let insights = CognitionLoop::run_once(&atom_mem, &triple_store, &config);
285 assert!(!insights.is_empty());
286
287 assert!(insights.iter().any(|i| matches!(i, Insight::Pattern(_))));
289 }
290
291 #[test]
292 fn test_run_once_finds_gaps() {
293 let (atom_mem, triple_store) = make_test_data();
294 let config = CognitionConfig {
295 min_shared_relations: 1,
296 min_peer_coverage: 0.5,
297 ..Default::default()
298 };
299 let insights = CognitionLoop::run_once(&atom_mem, &triple_store, &config);
300
301 let has_gap = insights.iter().any(|i| {
302 if let Insight::Gap(g) = i {
303 g.entity == "tokyo" && g.missing_relation == "located_in"
304 } else {
305 false
306 }
307 });
308 assert!(has_gap, "Should detect tokyo missing located_in");
309 }
310
311 #[test]
312 fn test_background_loop_start_stop() {
313 let (atom_mem, triple_store) = make_test_data();
314 let config = CognitionConfig {
315 interval: Duration::from_millis(50),
316 ..Default::default()
317 };
318
319 let mut cognition =
320 CognitionLoop::start(Arc::new(atom_mem), Arc::new(triple_store), config);
321
322 assert!(cognition.state().is_running());
323
324 std::thread::sleep(Duration::from_millis(200));
326
327 let cycles = cognition.state().cycle_count();
328 assert!(
329 cycles >= 1,
330 "Should have completed at least 1 cycle, got {}",
331 cycles
332 );
333
334 cognition.stop();
335 assert!(!cognition.state().is_running());
336 }
337
338 #[test]
339 fn test_take_insights() {
340 let (atom_mem, triple_store) = make_test_data();
341 let config = CognitionConfig {
342 interval: Duration::from_millis(50),
343 ..Default::default()
344 };
345
346 let mut cognition =
347 CognitionLoop::start(Arc::new(atom_mem), Arc::new(triple_store), config);
348
349 std::thread::sleep(Duration::from_millis(200));
350
351 let insights = cognition.state().take_insights();
352 assert!(!insights.is_empty());
353
354 cognition.stop();
357 let remaining = cognition.state().take_insights();
358 let _ = remaining;
360 }
361
362 #[test]
363 fn test_empty_store_no_panic() {
364 let atom_mem = AtomMemory::new(16384, 3.0);
365 let triple_store = TripleStore::new();
366 let config = CognitionConfig::default();
367 let insights = CognitionLoop::run_once(&atom_mem, &triple_store, &config);
368 assert!(insights.is_empty());
369 }
370}