#![allow(clippy::collapsible_if)]
use crate::AletheiaDB;
use crate::core::error::Result;
use crate::core::id::NodeId;
use crate::core::temporal::time;
use crate::core::vector::ops;
#[derive(Debug, Clone)]
pub struct AuraResult {
pub aura_vector: Option<Vec<f32>>,
pub current_vector: Option<Vec<f32>>,
pub divergence_score: f32,
}
pub struct AuraEngine<'a> {
db: &'a AletheiaDB,
}
impl<'a> AuraEngine<'a> {
pub fn new(db: &'a AletheiaDB) -> Self {
Self { db }
}
pub fn calculate_aura(
&self,
node_id: NodeId,
property_name: &str,
half_life_us: i64,
) -> Result<AuraResult> {
let history = self.db.get_node_history(node_id)?;
if history.versions.is_empty() {
return Ok(AuraResult {
aura_vector: None,
current_vector: None,
divergence_score: 0.0,
});
}
let now = time::now().wallclock();
let mut weighted_sum: Option<Vec<f32>> = None;
let mut total_weight = 0.0;
let mut current_vector: Option<Vec<f32>> = None;
for version in history.versions.iter() {
if let Some(prop) = version.properties.get(property_name) {
if let Some(vec) = prop.as_vector() {
let ts = version.temporal.valid_time().start().wallclock();
current_vector = Some(vec.to_vec());
let age_us = (now - ts).max(0);
let weight = if half_life_us > 0 {
0.5_f64.powf(age_us as f64 / half_life_us as f64) as f32
} else {
1.0_f32
};
total_weight += weight;
match &mut weighted_sum {
Some(sum) => {
if sum.len() == vec.len() {
for i in 0..sum.len() {
sum[i] += vec[i] * weight;
}
}
}
None => {
let mut initial_sum = vec.to_vec();
for v in &mut initial_sum {
*v *= weight;
}
weighted_sum = Some(initial_sum);
}
}
}
}
}
let mut aura_vector = None;
let mut divergence_score = 0.0;
if let Some(mut sum) = weighted_sum {
if total_weight > 0.0 {
for v in &mut sum {
*v /= total_weight;
}
ops::normalize_in_place(&mut sum);
if let Some(current) = ¤t_vector {
if sum.len() == current.len() {
let mut curr_normalized = current.clone();
ops::normalize_in_place(&mut curr_normalized);
let sim = ops::cosine_similarity(&sum, &curr_normalized)?;
divergence_score = (1.0 - sim).max(0.0);
} else {
divergence_score = 1.0;
}
}
aura_vector = Some(sum);
}
}
Ok(AuraResult {
aura_vector,
current_vector,
divergence_score,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::api::transaction::WriteOps;
use crate::core::property::PropertyMapBuilder;
#[test]
fn test_aura_stable() {
let db = AletheiaDB::new().unwrap();
std::thread::sleep(std::time::Duration::from_millis(10));
let mut n1 = crate::core::id::NodeId::new(0).unwrap();
db.write(|tx| {
n1 = tx
.create_node(
"Concept",
PropertyMapBuilder::new()
.insert_vector("vec", &[1.0, 0.0])
.build(),
)
.unwrap();
Ok::<(), crate::core::error::Error>(())
})
.unwrap();
std::thread::sleep(std::time::Duration::from_millis(20));
db.write(|tx| {
tx.update_node(
n1,
PropertyMapBuilder::new()
.insert_vector("vec", &[0.99, 0.01])
.build(),
)
.unwrap();
Ok::<(), crate::core::error::Error>(())
})
.unwrap();
std::thread::sleep(std::time::Duration::from_millis(20));
let engine = AuraEngine::new(&db);
let result = engine.calculate_aura(n1, "vec", 1_000_000).unwrap();
assert!(result.aura_vector.is_some());
assert!(result.divergence_score < 0.05);
}
#[test]
fn test_aura_dimension_mismatch() {
let db = AletheiaDB::new().unwrap();
std::thread::sleep(std::time::Duration::from_millis(10));
let mut n1 = crate::core::id::NodeId::new(0).unwrap();
db.write(|tx| {
n1 = tx
.create_node(
"Concept",
PropertyMapBuilder::new()
.insert_vector("vec", &[1.0, 0.0])
.build(),
)
.unwrap();
Ok::<(), crate::core::error::Error>(())
})
.unwrap();
std::thread::sleep(std::time::Duration::from_millis(100));
db.write(|tx| {
tx.update_node(
n1,
PropertyMapBuilder::new()
.insert_vector("vec", &[0.0, 1.0, 0.5])
.build(),
)
.unwrap();
Ok::<(), crate::core::error::Error>(())
})
.unwrap();
let engine = AuraEngine::new(&db);
let result = engine.calculate_aura(n1, "vec", 1_000_000).unwrap();
assert_eq!(
result.divergence_score, 1.0,
"Expected a total divergence of 1.0 on dimension mismatch"
);
}
#[test]
fn test_aura_divergent() {
let db = AletheiaDB::new().unwrap();
std::thread::sleep(std::time::Duration::from_millis(10));
let mut n1 = crate::core::id::NodeId::new(0).unwrap();
db.write(|tx| {
n1 = tx
.create_node(
"Concept",
PropertyMapBuilder::new()
.insert_vector("vec", &[1.0, 0.0])
.build(),
)
.unwrap();
Ok::<(), crate::core::error::Error>(())
})
.unwrap();
std::thread::sleep(std::time::Duration::from_millis(100));
db.write(|tx| {
tx.update_node(
n1,
PropertyMapBuilder::new()
.insert_vector("vec", &[0.0, 1.0])
.build(),
)
.unwrap();
Ok::<(), crate::core::error::Error>(())
})
.unwrap();
let engine = AuraEngine::new(&db);
let result = engine.calculate_aura(n1, "vec", 1_000_000).unwrap();
assert!(
result.divergence_score > 0.1,
"Expected significant divergence, got {}",
result.divergence_score
);
}
}