holographic_memory/core/cognition/
abstraction.rs1use fxhash::FxHashMap;
10
11use crate::core::atom_memory::AtomMemory;
12use crate::core::entangled::EntangledHVec;
13use crate::core::triple_store::TripleStore;
14
15#[derive(Clone, Debug)]
17pub struct Abstraction {
18 pub name: String,
20 pub relation: String,
22 pub role: AbstractionRole,
24 pub members: Vec<String>,
26 pub prototype: EntangledHVec,
28}
29
30#[derive(Clone, Debug, PartialEq, Eq)]
32pub enum AbstractionRole {
33 Subject,
34 Object,
35}
36
37pub struct AbstractionEngine;
39
40impl AbstractionEngine {
41 pub fn discover(
46 triple_store: &TripleStore,
47 atom_memory: &AtomMemory,
48 min_members: usize,
49 ) -> Vec<Abstraction> {
50 let snapshot = triple_store.snapshot();
51
52 let mut subjects_by_rel: FxHashMap<String, Vec<String>> = FxHashMap::default();
54 let mut objects_by_rel: FxHashMap<String, Vec<String>> = FxHashMap::default();
55
56 for t in &snapshot {
57 subjects_by_rel
58 .entry(t.relation_id.clone())
59 .or_default()
60 .push(t.subject_id.clone());
61 objects_by_rel
62 .entry(t.relation_id.clone())
63 .or_default()
64 .push(t.object_id.clone());
65 }
66
67 let mut results = Vec::new();
68
69 for (relation, mut members) in subjects_by_rel {
71 members.sort();
72 members.dedup();
73 if members.len() < min_members {
74 continue;
75 }
76 if let Some(proto) = Self::bundle_members(&members, atom_memory) {
77 results.push(Abstraction {
78 name: format!("concept:{}:subject", relation),
79 relation: relation.clone(),
80 role: AbstractionRole::Subject,
81 members,
82 prototype: proto,
83 });
84 }
85 }
86
87 for (relation, mut members) in objects_by_rel {
89 members.sort();
90 members.dedup();
91 if members.len() < min_members {
92 continue;
93 }
94 if let Some(proto) = Self::bundle_members(&members, atom_memory) {
95 results.push(Abstraction {
96 name: format!("concept:{}:object", relation),
97 relation,
98 role: AbstractionRole::Object,
99 members,
100 prototype: proto,
101 });
102 }
103 }
104
105 results
106 }
107
108 fn bundle_members(members: &[String], atom_memory: &AtomMemory) -> Option<EntangledHVec> {
115 let vecs: Vec<EntangledHVec> = members.iter().filter_map(|m| atom_memory.get(m)).collect();
116 if vecs.is_empty() {
117 return None;
118 }
119 let dim = vecs[0].dim;
120 if vecs.len() >= 10 {
121 return Some(EntangledHVec::bundle(&vecs));
122 }
123 let mut freq: FxHashMap<u32, u32> = FxHashMap::default();
125 for v in &vecs {
126 for &idx in v.indices() {
127 *freq.entry(idx).or_insert(0) += 1;
128 }
129 }
130 let target_count = (dim / 256).max(1);
131 let mut scored: Vec<(u32, u32)> = freq.into_iter().collect();
132 scored.sort_unstable_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(&b.0)));
133 scored.truncate(target_count);
134 let mut indices: Vec<u32> = scored.into_iter().map(|(idx, _)| idx).collect();
135 indices.sort_unstable();
136 Some(EntangledHVec::from_indices(indices, dim))
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143
144 #[test]
145 fn test_discover_abstractions() {
146 let dim = 16384;
147 let atom_mem = AtomMemory::new(dim, 3.0);
148 let triple_store = TripleStore::new();
149
150 atom_mem.get_or_insert("paris");
152 atom_mem.get_or_insert("berlin");
153 atom_mem.get_or_insert("tokyo");
154 atom_mem.get_or_insert("france");
155 atom_mem.get_or_insert("germany");
156 atom_mem.get_or_insert("japan");
157
158 triple_store.add("paris", "capital_of", "france", "c1");
160 triple_store.add("berlin", "capital_of", "germany", "c2");
161 triple_store.add("tokyo", "capital_of", "japan", "c3");
162
163 let abstractions = AbstractionEngine::discover(&triple_store, &atom_mem, 3);
164 assert!(!abstractions.is_empty());
165
166 let cities = abstractions
168 .iter()
169 .find(|a| a.relation == "capital_of" && a.role == AbstractionRole::Subject);
170 assert!(cities.is_some());
171 let cities = cities.unwrap();
172 assert_eq!(cities.members.len(), 3);
173 assert!(
174 !cities.prototype.indices().is_empty(),
175 "Prototype should have indices via frequency counting"
176 );
177 }
178
179 #[test]
180 fn test_discover_below_threshold() {
181 let dim = 16384;
182 let atom_mem = AtomMemory::new(dim, 3.0);
183 let triple_store = TripleStore::new();
184
185 atom_mem.get_or_insert("a");
186 atom_mem.get_or_insert("b");
187 triple_store.add("a", "r", "b", "c1");
188
189 let abstractions = AbstractionEngine::discover(&triple_store, &atom_mem, 5);
190 assert!(abstractions.is_empty());
191 }
192
193 #[test]
194 fn test_discover_empty_store() {
195 let dim = 16384;
196 let atom_mem = AtomMemory::new(dim, 3.0);
197 let triple_store = TripleStore::new();
198
199 let abstractions = AbstractionEngine::discover(&triple_store, &atom_mem, 1);
200 assert!(abstractions.is_empty());
201 }
202}