1use std::cmp::Reverse;
14use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque};
15
16use crate::singularity::Singularity;
17use csm_core_lib::error::{MemoryError, Result};
18
19const MAX_TRAVERSAL_DEPTH: usize = 32;
21const MAX_TRAVERSAL_RESULTS: usize = 10_000;
23
24#[derive(Debug, Clone)]
26pub struct TraversalConfig {
27 pub max_depth: usize,
29 pub min_strength: f32,
31 pub max_results: usize,
33}
34
35impl Default for TraversalConfig {
36 fn default() -> Self {
37 Self {
38 max_depth: 3,
39 min_strength: 0.0,
40 max_results: 100,
41 }
42 }
43}
44
45impl TraversalConfig {
46 pub fn validate(&self) -> Result<()> {
48 if self.max_depth > MAX_TRAVERSAL_DEPTH {
49 return Err(MemoryError::InvalidInput {
50 field: "max_depth".to_string(),
51 reason: format!(
52 "traversal depth exceeds {} (got {})",
53 MAX_TRAVERSAL_DEPTH, self.max_depth
54 ),
55 });
56 }
57 if self.max_results > MAX_TRAVERSAL_RESULTS {
58 return Err(MemoryError::InvalidInput {
59 field: "max_results".to_string(),
60 reason: format!(
61 "traversal results exceed {} (got {})",
62 MAX_TRAVERSAL_RESULTS, self.max_results
63 ),
64 });
65 }
66 Ok(())
67 }
68}
69
70impl Singularity {
71 pub fn neighbors(&self, ns: &str, id: &str, min_strength: f32) -> Vec<(String, f32)> {
75 self.get_associations(ns, id)
76 .into_iter()
77 .filter(|(_, strength)| *strength >= min_strength)
78 .collect()
79 }
80
81 pub fn bfs(
89 &self,
90 ns: &str,
91 start: &str,
92 config: &TraversalConfig,
93 ) -> Result<Vec<(String, u32)>> {
94 config.validate()?;
95 let ns_state = self
96 .get_namespace(ns)
97 .ok_or_else(|| MemoryError::NotFound {
98 entity: "Namespace".to_string(),
99 id: ns.to_string(),
100 })?;
101 if !ns_state.concepts.contains_key(start) {
102 return Err(MemoryError::NotFound {
103 entity: "Concept".to_string(),
104 id: start.to_string(),
105 });
106 }
107
108 let mut visited: HashSet<String> = HashSet::new();
109 let mut results: Vec<(String, u32)> = Vec::new();
110 let mut queue: VecDeque<(String, u32)> = VecDeque::new();
111
112 visited.insert(start.to_string());
113 queue.push_back((start.to_string(), 0));
114
115 while let Some((current, depth)) = queue.pop_front() {
116 if results.len() >= config.max_results {
117 break;
118 }
119
120 results.push((current.clone(), depth));
121
122 if depth as usize >= config.max_depth {
123 continue;
124 }
125
126 let neighbors = self.neighbors(ns, ¤t, config.min_strength);
127 for (neighbor, _) in neighbors {
128 if visited.insert(neighbor.clone()) {
129 queue.push_back((neighbor, depth + 1));
130 }
131 }
132 }
133
134 Ok(results)
135 }
136
137 pub fn shortest_path(
146 &self,
147 ns: &str,
148 from: &str,
149 to: &str,
150 config: &TraversalConfig,
151 ) -> Result<Option<Vec<String>>> {
152 config.validate()?;
153 let ns_state = self
154 .get_namespace(ns)
155 .ok_or_else(|| MemoryError::NotFound {
156 entity: "Namespace".to_string(),
157 id: ns.to_string(),
158 })?;
159 if !ns_state.concepts.contains_key(from) {
160 return Err(MemoryError::NotFound {
161 entity: "Concept".to_string(),
162 id: from.to_string(),
163 });
164 }
165 if !ns_state.concepts.contains_key(to) {
166 return Err(MemoryError::NotFound {
167 entity: "Concept".to_string(),
168 id: to.to_string(),
169 });
170 }
171
172 if from == to {
173 return Ok(Some(vec![from.to_string()]));
174 }
175
176 let mut dist: HashMap<String, f32> = HashMap::new();
179 let mut parent: HashMap<String, String> = HashMap::new();
180 let mut heap: BinaryHeap<Reverse<(u32, u32, String)>> = BinaryHeap::new();
182
183 dist.insert(from.to_string(), 0.0);
184 heap.push(Reverse((0u32, 0u32, from.to_string())));
185
186 while let Some(Reverse((cost_bits, depth, current))) = heap.pop() {
187 if current == to {
188 let mut path = vec![to.to_string()];
190 let mut node = to;
191 while let Some(p) = parent.get(node) {
192 path.push(p.clone());
193 node = p;
194 if node == from {
195 break;
196 }
197 }
198 path.reverse();
199 return Ok(Some(path));
200 }
201
202 let current_cost = f32::from_bits(cost_bits);
203 if let Some(&best) = dist.get(¤t) {
204 if current_cost > best {
205 continue; }
207 }
208
209 if depth as usize >= config.max_depth {
210 continue;
211 }
212
213 let neighbors = self.neighbors(ns, ¤t, config.min_strength);
214 for (neighbor, strength) in neighbors {
215 let edge_cost = if strength > 0.0 {
217 -strength.ln()
218 } else {
219 f32::MAX / 2.0
220 };
221 let new_cost = current_cost + edge_cost;
222 let best = dist.get(&neighbor).copied().unwrap_or(f32::MAX);
223 if new_cost < best {
224 dist.insert(neighbor.clone(), new_cost);
225 parent.insert(neighbor.clone(), current.clone());
226 heap.push(Reverse((new_cost.to_bits(), depth + 1, neighbor)));
227 }
228 }
229 }
230
231 Ok(None)
232 }
233
234 pub fn shortest_path_hops(
241 &self,
242 ns: &str,
243 from: &str,
244 to: &str,
245 config: &TraversalConfig,
246 ) -> Result<Option<Vec<String>>> {
247 config.validate()?;
248 let ns_state = self
249 .get_namespace(ns)
250 .ok_or_else(|| MemoryError::NotFound {
251 entity: "Namespace".to_string(),
252 id: ns.to_string(),
253 })?;
254 if !ns_state.concepts.contains_key(from) {
255 return Err(MemoryError::NotFound {
256 entity: "Concept".to_string(),
257 id: from.to_string(),
258 });
259 }
260 if !ns_state.concepts.contains_key(to) {
261 return Err(MemoryError::NotFound {
262 entity: "Concept".to_string(),
263 id: to.to_string(),
264 });
265 }
266
267 if from == to {
268 return Ok(Some(vec![from.to_string()]));
269 }
270
271 let mut visited: HashSet<String> = HashSet::new();
272 let mut parent: HashMap<String, String> = HashMap::new();
273 let mut queue: VecDeque<(String, u32)> = VecDeque::new();
274
275 visited.insert(from.to_string());
276 queue.push_back((from.to_string(), 0));
277
278 while let Some((current, depth)) = queue.pop_front() {
279 if depth as usize >= config.max_depth {
280 continue;
281 }
282
283 let neighbors = self.neighbors(ns, ¤t, config.min_strength);
284 for (neighbor, _) in neighbors {
285 if visited.insert(neighbor.clone()) {
286 parent.insert(neighbor.clone(), current.clone());
287 if neighbor == to {
288 let mut path = vec![to.to_string()];
290 let mut node = to;
291 while let Some(p) = parent.get(node) {
292 path.push(p.clone());
293 node = p;
294 if node == from {
295 break;
296 }
297 }
298 path.reverse();
299 return Ok(Some(path));
300 }
301 queue.push_back((neighbor, depth + 1));
302 }
303 }
304 }
305
306 Ok(None)
307 }
308}
309
310#[cfg(test)]
311#[path = "graph_traversal_tests.rs"]
312mod tests;