use crate::core::hash::FastMap;
use crate::core::atom_memory::AtomMemory;
use crate::core::entangled::EntangledHVec;
use crate::core::triple_store::TripleStore;
#[derive(Clone, Debug)]
pub struct Abstraction {
pub name: String,
pub relation: String,
pub role: AbstractionRole,
pub members: Vec<String>,
pub prototype: EntangledHVec,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum AbstractionRole {
Subject,
Object,
}
pub struct AbstractionEngine;
impl AbstractionEngine {
pub fn discover(
triple_store: &TripleStore,
atom_memory: &AtomMemory,
min_members: usize,
) -> Vec<Abstraction> {
let mut results = Vec::new();
for relation in triple_store.relation_ids() {
let mut subjects = crate::core::hash::FastSet::default();
let mut objects = crate::core::hash::FastSet::default();
triple_store.visit(None, Some(&relation), None, |t| {
subjects.insert(t.subject_id.clone());
objects.insert(t.object_id.clone());
true
});
for (role, members) in [
(AbstractionRole::Subject, subjects),
(AbstractionRole::Object, objects),
] {
if members.len() < min_members {
continue;
}
let mut members: Vec<_> = members.into_iter().collect();
members.sort();
if let Some(prototype) = Self::bundle_members(&members, atom_memory) {
let suffix = if role == AbstractionRole::Subject {
"subject"
} else {
"object"
};
results.push(Abstraction {
name: format!("concept:{relation}:{suffix}"),
relation: relation.clone(),
role,
members,
prototype,
});
}
}
}
results
}
fn bundle_members(members: &[String], atom_memory: &AtomMemory) -> Option<EntangledHVec> {
let mut freq: FastMap<u32, u32> = FastMap::default();
let mut dim = None;
let mut count = 0u32;
for member in members {
if let Some(vector) = atom_memory.get(member) {
dim.get_or_insert(vector.dim);
count += 1;
for &index in vector.indices() {
*freq.entry(index).or_default() += 1;
}
}
}
let dim = dim?;
if count >= 10 {
let mut selected: Vec<_> = freq
.into_iter()
.filter(|(_, n)| *n >= count.div_ceil(2))
.collect();
selected.sort_unstable_by_key(|&(index, _)| index);
return Some(EntangledHVec::from_selected(
selected,
dim,
(dim / 256).max(1),
));
}
let target_count = (dim / 256).max(1);
let mut scored: Vec<(u32, u32)> = freq.into_iter().collect();
if scored.len() > target_count {
scored
.select_nth_unstable_by(target_count - 1, |a, b| b.1.cmp(&a.1).then(a.0.cmp(&b.0)));
scored.truncate(target_count);
}
let mut indices: Vec<u32> = scored.into_iter().map(|(idx, _)| idx).collect();
indices.sort_unstable();
Some(EntangledHVec::from_indices(indices, dim))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn streamed_prototypes_preserve_majority_selection() {
let atoms = AtomMemory::new(256, 3.0);
let mut members = Vec::new();
for i in 0..11 {
let id = i.to_string();
atoms.insert_with_vec(&id, &EntangledHVec::from_indices(vec![1, 2, 3, 4 + i], 256));
members.push(id);
if members.len() >= 10 {
let vectors: Vec<_> = members.iter().map(|id| atoms.get(id).unwrap()).collect();
assert_eq!(
AbstractionEngine::bundle_members(&members, &atoms)
.unwrap()
.indices(),
EntangledHVec::bundle(&vectors).indices()
);
}
}
}
#[test]
fn test_discover_abstractions() {
let dim = 16384;
let atom_mem = AtomMemory::new(dim, 3.0);
let triple_store = TripleStore::new();
atom_mem.get_or_insert("paris");
atom_mem.get_or_insert("berlin");
atom_mem.get_or_insert("tokyo");
atom_mem.get_or_insert("france");
atom_mem.get_or_insert("germany");
atom_mem.get_or_insert("japan");
triple_store.add("paris", "capital_of", "france", "c1");
triple_store.add("berlin", "capital_of", "germany", "c2");
triple_store.add("tokyo", "capital_of", "japan", "c3");
let abstractions = AbstractionEngine::discover(&triple_store, &atom_mem, 3);
assert!(!abstractions.is_empty());
let cities = abstractions
.iter()
.find(|a| a.relation == "capital_of" && a.role == AbstractionRole::Subject);
assert!(cities.is_some());
let cities = cities.unwrap();
assert_eq!(cities.members.len(), 3);
assert!(
!cities.prototype.indices().is_empty(),
"Prototype should have indices via frequency counting"
);
}
#[test]
fn test_discover_below_threshold() {
let dim = 16384;
let atom_mem = AtomMemory::new(dim, 3.0);
let triple_store = TripleStore::new();
atom_mem.get_or_insert("a");
atom_mem.get_or_insert("b");
triple_store.add("a", "r", "b", "c1");
let abstractions = AbstractionEngine::discover(&triple_store, &atom_mem, 5);
assert!(abstractions.is_empty());
}
#[test]
fn test_discover_empty_store() {
let dim = 16384;
let atom_mem = AtomMemory::new(dim, 3.0);
let triple_store = TripleStore::new();
let abstractions = AbstractionEngine::discover(&triple_store, &atom_mem, 1);
assert!(abstractions.is_empty());
}
}