use crate::AletheiaDB;
use crate::core::error::Result;
use crate::core::id::NodeId;
use crate::core::vector::ops::cosine_similarity;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct TelepathyConfig {
pub vector_property: String,
pub decay: f32,
pub threshold: f32,
pub max_steps: usize,
pub missing_vector_weight: f32,
}
impl Default for TelepathyConfig {
fn default() -> Self {
Self {
vector_property: "embedding".to_string(),
decay: 0.8,
threshold: 0.01,
max_steps: 3,
missing_vector_weight: 0.1, }
}
}
pub struct TelepathyEngine<'a> {
db: &'a AletheiaDB,
config: TelepathyConfig,
}
impl<'a> TelepathyEngine<'a> {
pub fn new(db: &'a AletheiaDB) -> Self {
Self {
db,
config: TelepathyConfig::default(),
}
}
pub fn with_config(mut self, config: TelepathyConfig) -> Self {
self.config = config;
self
}
pub fn propagate(&self, seeds: &HashMap<NodeId, f32>) -> Result<Vec<(NodeId, f32)>> {
let mut activations = seeds.clone();
let mut vector_cache: HashMap<NodeId, Option<Vec<f32>>> = HashMap::new();
let mut get_vector = |node_id: NodeId| -> Result<Option<Vec<f32>>> {
if let Some(v) = vector_cache.get(&node_id) {
return Ok(v.clone());
}
let node = self.db.get_node(node_id)?;
let vec = node
.properties
.get(&self.config.vector_property)
.and_then(|v| v.as_vector())
.map(|v| v.to_vec());
vector_cache.insert(node_id, vec.clone());
Ok(vec)
};
for &id in seeds.keys() {
get_vector(id)?;
}
for _step in 0..self.config.max_steps {
let mut next_activations = activations.clone();
let mut changed = false;
let active_nodes: Vec<NodeId> = activations
.iter()
.filter(|(_, v)| **v >= self.config.threshold)
.map(|(k, _)| *k)
.collect();
for source in active_nodes {
let source_strength = activations[&source];
let source_vec = get_vector(source)?;
let edges = self.db.get_outgoing_edges(source);
for edge_id in edges {
let edge = self.db.get_edge(edge_id)?;
let target = edge.target;
let target_vec = get_vector(target)?;
let weight = match (&source_vec, &target_vec) {
(Some(a), Some(b)) => {
let sim = cosine_similarity(a, b)?;
sim.max(0.0)
}
_ => self.config.missing_vector_weight,
};
let signal = source_strength * weight * self.config.decay;
if signal > 0.0 {
let current_val = next_activations.entry(target).or_insert(0.0);
if signal > *current_val {
let diff = signal - *current_val;
if diff > 0.0001 {
*current_val = signal;
changed = true;
}
}
}
}
}
activations = next_activations;
if !changed {
break;
}
}
let mut result: Vec<_> = activations.into_iter().collect();
result.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
Ok(result)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::property::PropertyMapBuilder;
use crate::index::vector::{DistanceMetric, HnswConfig};
#[test]
fn test_telepathy_semantic_gate() {
let db = AletheiaDB::new().unwrap();
db.enable_vector_index("vec", HnswConfig::new(2, DistanceMetric::Cosine))
.unwrap();
let props_a = PropertyMapBuilder::new()
.insert_vector("vec", &[1.0, 0.0])
.build();
let a = db.create_node("Node", props_a).unwrap();
let props_b = PropertyMapBuilder::new()
.insert_vector("vec", &[1.0, 0.0])
.build();
let b = db.create_node("Node", props_b).unwrap();
let props_c = PropertyMapBuilder::new()
.insert_vector("vec", &[0.0, 1.0])
.build();
let c = db.create_node("Node", props_c).unwrap();
db.create_edge(a, b, "LINK", PropertyMapBuilder::new().build())
.unwrap();
db.create_edge(a, c, "LINK", PropertyMapBuilder::new().build())
.unwrap();
let engine = TelepathyEngine::new(&db).with_config(TelepathyConfig {
vector_property: "vec".to_string(),
decay: 1.0, threshold: 0.0,
max_steps: 1,
missing_vector_weight: 0.0,
});
let mut seeds = HashMap::new();
seeds.insert(a, 1.0);
let results = engine.propagate(&seeds).unwrap();
let results_map: HashMap<NodeId, f32> = results.into_iter().collect();
assert!(results_map[&b] > 0.9, "Twin should be activated");
assert!(
*results_map.get(&c).unwrap_or(&0.0) < 0.1,
"Stranger should be blocked"
);
}
#[test]
fn test_telepathy_decay_chain() {
let db = AletheiaDB::new().unwrap();
db.enable_vector_index("vec", HnswConfig::new(2, DistanceMetric::Cosine))
.unwrap();
let props = PropertyMapBuilder::new()
.insert_vector("vec", &[1.0, 0.0])
.build();
let a = db.create_node("Node", props.clone()).unwrap();
let b = db.create_node("Node", props.clone()).unwrap();
let c = db.create_node("Node", props.clone()).unwrap();
db.create_edge(a, b, "NEXT", PropertyMapBuilder::new().build())
.unwrap();
db.create_edge(b, c, "NEXT", PropertyMapBuilder::new().build())
.unwrap();
let decay = 0.5;
let engine = TelepathyEngine::new(&db).with_config(TelepathyConfig {
vector_property: "vec".to_string(),
decay,
threshold: 0.0,
max_steps: 2,
missing_vector_weight: 0.0,
});
let mut seeds = HashMap::new();
seeds.insert(a, 1.0);
let results = engine.propagate(&seeds).unwrap();
let results_map: HashMap<NodeId, f32> = results.into_iter().collect();
assert!((results_map[&b] - 0.5).abs() < 0.01, "B should be 0.5");
assert!((results_map[&c] - 0.25).abs() < 0.01, "C should be 0.25");
}
}