use std::cmp::Reverse;
use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque};
use crate::singularity::Singularity;
use csm_core_lib::error::{MemoryError, Result};
const MAX_TRAVERSAL_DEPTH: usize = 32;
const MAX_TRAVERSAL_RESULTS: usize = 10_000;
#[derive(Debug, Clone)]
pub struct TraversalConfig {
pub max_depth: usize,
pub min_strength: f32,
pub max_results: usize,
}
impl Default for TraversalConfig {
fn default() -> Self {
Self {
max_depth: 3,
min_strength: 0.0,
max_results: 100,
}
}
}
impl TraversalConfig {
pub fn validate(&self) -> Result<()> {
if self.max_depth > MAX_TRAVERSAL_DEPTH {
return Err(MemoryError::InvalidInput {
field: "max_depth".to_string(),
reason: format!(
"traversal depth exceeds {} (got {})",
MAX_TRAVERSAL_DEPTH, self.max_depth
),
});
}
if self.max_results > MAX_TRAVERSAL_RESULTS {
return Err(MemoryError::InvalidInput {
field: "max_results".to_string(),
reason: format!(
"traversal results exceed {} (got {})",
MAX_TRAVERSAL_RESULTS, self.max_results
),
});
}
Ok(())
}
}
impl Singularity {
pub fn neighbors(&self, ns: &str, id: &str, min_strength: f32) -> Vec<(String, f32)> {
self.get_associations(ns, id)
.into_iter()
.filter(|(_, strength)| *strength >= min_strength)
.collect()
}
pub fn bfs(
&self,
ns: &str,
start: &str,
config: &TraversalConfig,
) -> Result<Vec<(String, u32)>> {
config.validate()?;
let ns_state = self
.get_namespace(ns)
.ok_or_else(|| MemoryError::NotFound {
entity: "Namespace".to_string(),
id: ns.to_string(),
})?;
if !ns_state.concepts.contains_key(start) {
return Err(MemoryError::NotFound {
entity: "Concept".to_string(),
id: start.to_string(),
});
}
let mut visited: HashSet<String> = HashSet::new();
let mut results: Vec<(String, u32)> = Vec::new();
let mut queue: VecDeque<(String, u32)> = VecDeque::new();
visited.insert(start.to_string());
queue.push_back((start.to_string(), 0));
while let Some((current, depth)) = queue.pop_front() {
if results.len() >= config.max_results {
break;
}
results.push((current.clone(), depth));
if depth as usize >= config.max_depth {
continue;
}
let neighbors = self.neighbors(ns, ¤t, config.min_strength);
for (neighbor, _) in neighbors {
if visited.insert(neighbor.clone()) {
queue.push_back((neighbor, depth + 1));
}
}
}
Ok(results)
}
pub fn shortest_path(
&self,
ns: &str,
from: &str,
to: &str,
config: &TraversalConfig,
) -> Result<Option<Vec<String>>> {
config.validate()?;
let ns_state = self
.get_namespace(ns)
.ok_or_else(|| MemoryError::NotFound {
entity: "Namespace".to_string(),
id: ns.to_string(),
})?;
if !ns_state.concepts.contains_key(from) {
return Err(MemoryError::NotFound {
entity: "Concept".to_string(),
id: from.to_string(),
});
}
if !ns_state.concepts.contains_key(to) {
return Err(MemoryError::NotFound {
entity: "Concept".to_string(),
id: to.to_string(),
});
}
if from == to {
return Ok(Some(vec![from.to_string()]));
}
let mut dist: HashMap<String, f32> = HashMap::new();
let mut parent: HashMap<String, String> = HashMap::new();
let mut heap: BinaryHeap<Reverse<(u32, u32, String)>> = BinaryHeap::new();
dist.insert(from.to_string(), 0.0);
heap.push(Reverse((0u32, 0u32, from.to_string())));
while let Some(Reverse((cost_bits, depth, current))) = heap.pop() {
if current == to {
let mut path = vec![to.to_string()];
let mut node = to;
while let Some(p) = parent.get(node) {
path.push(p.clone());
node = p;
if node == from {
break;
}
}
path.reverse();
return Ok(Some(path));
}
let current_cost = f32::from_bits(cost_bits);
if let Some(&best) = dist.get(¤t) {
if current_cost > best {
continue; }
}
if depth as usize >= config.max_depth {
continue;
}
let neighbors = self.neighbors(ns, ¤t, config.min_strength);
for (neighbor, strength) in neighbors {
let edge_cost = if strength > 0.0 {
-strength.ln()
} else {
f32::MAX / 2.0
};
let new_cost = current_cost + edge_cost;
let best = dist.get(&neighbor).copied().unwrap_or(f32::MAX);
if new_cost < best {
dist.insert(neighbor.clone(), new_cost);
parent.insert(neighbor.clone(), current.clone());
heap.push(Reverse((new_cost.to_bits(), depth + 1, neighbor)));
}
}
}
Ok(None)
}
pub fn shortest_path_hops(
&self,
ns: &str,
from: &str,
to: &str,
config: &TraversalConfig,
) -> Result<Option<Vec<String>>> {
config.validate()?;
let ns_state = self
.get_namespace(ns)
.ok_or_else(|| MemoryError::NotFound {
entity: "Namespace".to_string(),
id: ns.to_string(),
})?;
if !ns_state.concepts.contains_key(from) {
return Err(MemoryError::NotFound {
entity: "Concept".to_string(),
id: from.to_string(),
});
}
if !ns_state.concepts.contains_key(to) {
return Err(MemoryError::NotFound {
entity: "Concept".to_string(),
id: to.to_string(),
});
}
if from == to {
return Ok(Some(vec![from.to_string()]));
}
let mut visited: HashSet<String> = HashSet::new();
let mut parent: HashMap<String, String> = HashMap::new();
let mut queue: VecDeque<(String, u32)> = VecDeque::new();
visited.insert(from.to_string());
queue.push_back((from.to_string(), 0));
while let Some((current, depth)) = queue.pop_front() {
if depth as usize >= config.max_depth {
continue;
}
let neighbors = self.neighbors(ns, ¤t, config.min_strength);
for (neighbor, _) in neighbors {
if visited.insert(neighbor.clone()) {
parent.insert(neighbor.clone(), current.clone());
if neighbor == to {
let mut path = vec![to.to_string()];
let mut node = to;
while let Some(p) = parent.get(node) {
path.push(p.clone());
node = p;
if node == from {
break;
}
}
path.reverse();
return Ok(Some(path));
}
queue.push_back((neighbor, depth + 1));
}
}
}
Ok(None)
}
}
#[cfg(test)]
#[path = "graph_traversal_tests.rs"]
mod tests;