#![allow(clippy::needless_range_loop, clippy::collapsible_if)]
use crate::AletheiaDB;
use crate::core::error::{Error, Result};
use crate::core::id::NodeId;
#[cfg(feature = "semantic-search")]
use crate::core::vector::ops::magnitude;
#[derive(Debug, Clone, PartialEq)]
pub struct MosaicPiece {
pub node_id: NodeId,
pub contribution_score: f32,
}
#[derive(Debug, Clone)]
pub struct MosaicResult {
pub pieces: Vec<MosaicPiece>,
pub final_residual_magnitude: f32,
}
pub struct Mosaic<'a> {
db: &'a AletheiaDB,
}
impl<'a> Mosaic<'a> {
pub fn new(db: &'a AletheiaDB) -> Self {
Self { db }
}
pub fn compose(
&self,
target_vector: &[f32],
property: &str,
max_pieces: usize,
stop_threshold: f32,
) -> Result<MosaicResult> {
let mut residual = target_vector.to_vec();
let mut pieces = Vec::new();
let mut used_nodes = std::collections::HashSet::new();
for _ in 0..max_pieces {
let res_mag = magnitude(&residual);
if res_mag <= stop_threshold {
break;
}
let candidates = self.db.search_vectors_in(property, &residual, 10)?;
let mut best_piece = None;
let mut best_vec = None;
for (node_id, score) in candidates {
if used_nodes.contains(&node_id) {
continue;
}
if let Ok(node) = self.db.get_node(node_id) {
if let Some(prop) = node.properties.get(property) {
if let Some(vec) = prop.as_vector() {
best_piece = Some(MosaicPiece {
node_id,
contribution_score: score,
});
best_vec = Some(vec.to_vec());
break; }
}
}
}
match (best_piece, best_vec) {
(Some(piece), Some(vec)) => {
used_nodes.insert(piece.node_id);
pieces.push(piece);
if residual.len() == vec.len() {
for i in 0..residual.len() {
residual[i] -= vec[i];
}
} else {
return Err(Error::other("Vector dimension mismatch during composition"));
}
}
_ => {
break;
}
}
}
Ok(MosaicResult {
pieces,
final_residual_magnitude: magnitude(&residual),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::property::PropertyMapBuilder;
use crate::index::vector::{DistanceMetric, HnswConfig};
#[test]
fn test_mosaic_composition() {
let db = AletheiaDB::new().unwrap();
db.enable_vector_index("vec", HnswConfig::new(3, DistanceMetric::Cosine))
.unwrap();
let px = PropertyMapBuilder::new()
.insert_vector("vec", &[1.0, 0.0, 0.0])
.build();
let nx = db.create_node("Concept", px).unwrap();
let py = PropertyMapBuilder::new()
.insert_vector("vec", &[0.0, 1.0, 0.0])
.build();
let ny = db.create_node("Concept", py).unwrap();
let pz = PropertyMapBuilder::new()
.insert_vector("vec", &[0.0, 0.0, 1.0])
.build();
let nz = db.create_node("Concept", pz).unwrap();
let mosaic = Mosaic::new(&db);
let target = vec![1.0, 1.0, 0.0];
let result = mosaic.compose(&target, "vec", 5, 0.01).unwrap();
assert_eq!(result.pieces.len(), 2);
let found_ids: std::collections::HashSet<_> = result.pieces.iter().map(|p| p.node_id).collect();
assert!(found_ids.contains(&nx));
assert!(found_ids.contains(&ny));
assert!(!found_ids.contains(&nz));
assert!(result.final_residual_magnitude < 0.01);
}
}