1use crate::error::Result;
2use crate::streaming::incremental::{IncrementalGraphProcessor, UpdateResult};
3use arrow::array::Array;
4use std::collections::HashMap;
5
6pub trait StreamingAlgorithm<T> {
9 fn initialize(&mut self, processor: &IncrementalGraphProcessor) -> Result<()>;
11
12 fn update(&mut self, processor: &IncrementalGraphProcessor, changes: &UpdateResult) -> Result<()>;
14
15 fn get_result(&self) -> &T;
17
18 fn recompute(&mut self, processor: &IncrementalGraphProcessor) -> Result<()>;
20
21 fn needs_recomputation(&self, changes: &UpdateResult) -> bool;
23}
24
25#[derive(Debug, Clone)]
27pub struct StreamingPageRank {
28 scores: HashMap<String, f64>,
29 damping_factor: f64,
30 max_iterations: usize,
31 tolerance: f64,
32 iteration_count: usize,
33 converged: bool,
34}
35
36impl StreamingPageRank {
37 pub fn new(damping_factor: f64, max_iterations: usize, tolerance: f64) -> Self {
38 Self {
39 scores: HashMap::new(),
40 damping_factor,
41 max_iterations,
42 tolerance,
43 iteration_count: 0,
44 converged: false,
45 }
46 }
47
48 pub fn default() -> Self {
50 Self::new(0.85, 50, 1e-6)
51 }
52
53 fn iterate(&mut self, adjacency: &HashMap<String, Vec<(String, f64)>>, nodes: &[String]) -> Result<bool> {
55 let node_count = nodes.len() as f64;
56 let base_score = (1.0 - self.damping_factor) / node_count;
57
58 let mut new_scores = HashMap::new();
59
60 for node in nodes {
62 new_scores.insert(node.clone(), base_score);
63 }
64
65 for (source, targets) in adjacency {
67 let source_score = self.scores.get(source).copied().unwrap_or(1.0 / node_count);
68 let out_degree = targets.len() as f64;
69
70 if out_degree > 0.0 {
71 let contribution_per_link = self.damping_factor * source_score / out_degree;
72
73 for (target, _weight) in targets {
74 *new_scores.entry(target.clone()).or_insert(base_score) += contribution_per_link;
75 }
76 }
77 }
78
79 let mut max_change: f64 = 0.0;
81 for (node, new_score) in &new_scores {
82 let old_score = self.scores.get(node).copied().unwrap_or(1.0 / node_count);
83 let change = (new_score - old_score).abs();
84 max_change = max_change.max(change);
85 }
86
87 self.scores = new_scores;
88 self.iteration_count += 1;
89
90 let converged = max_change < self.tolerance;
91 self.converged = converged;
92
93 Ok(converged)
94 }
95
96 pub fn top_nodes(&self, k: usize) -> Vec<(String, f64)> {
98 let mut node_scores: Vec<(String, f64)> = self.scores.iter()
99 .map(|(node, score)| (node.clone(), *score))
100 .collect();
101
102 node_scores.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
103 node_scores.truncate(k);
104 node_scores
105 }
106
107 pub fn node_score(&self, node_id: &str) -> Option<f64> {
109 self.scores.get(node_id).copied()
110 }
111}
112
113impl StreamingAlgorithm<HashMap<String, f64>> for StreamingPageRank {
114 fn initialize(&mut self, processor: &IncrementalGraphProcessor) -> Result<()> {
115 let graph = processor.graph();
117 let nodes_batch = &graph.nodes;
118 let edges_batch = &graph.edges;
119
120 let mut nodes = Vec::new();
122 if nodes_batch.num_rows() > 0 {
123 let node_ids = nodes_batch.column(0)
124 .as_any()
125 .downcast_ref::<arrow::array::StringArray>()
126 .ok_or_else(|| crate::error::GraphError::graph_construction("Expected string array for node IDs"))?;
127
128 for i in 0..node_ids.len() {
129 nodes.push(node_ids.value(i).to_string());
130 }
131 }
132
133 let mut adjacency: HashMap<String, Vec<(String, f64)>> = HashMap::new();
135 if edges_batch.num_rows() > 0 {
136 let source_ids = edges_batch.column(0)
137 .as_any()
138 .downcast_ref::<arrow::array::StringArray>()
139 .ok_or_else(|| crate::error::GraphError::graph_construction("Expected string array for source IDs"))?;
140 let target_ids = edges_batch.column(1)
141 .as_any()
142 .downcast_ref::<arrow::array::StringArray>()
143 .ok_or_else(|| crate::error::GraphError::graph_construction("Expected string array for target IDs"))?;
144 let weights = edges_batch.column(2)
145 .as_any()
146 .downcast_ref::<arrow::array::Float64Array>()
147 .ok_or_else(|| crate::error::GraphError::graph_construction("Expected float64 array for weights"))?;
148
149 for i in 0..source_ids.len() {
150 let source = source_ids.value(i).to_string();
151 let target = target_ids.value(i).to_string();
152 let weight = weights.value(i);
153
154 adjacency.entry(source).or_insert_with(Vec::new).push((target, weight));
155 }
156 }
157
158 let node_count = nodes.len() as f64;
160 if node_count > 0.0 {
161 let initial_score = 1.0 / node_count;
162 for node in &nodes {
163 self.scores.insert(node.clone(), initial_score);
164 }
165
166 for _ in 0..self.max_iterations {
168 if self.iterate(&adjacency, &nodes)? {
169 break;
170 }
171 }
172 }
173
174 Ok(())
175 }
176
177 fn update(&mut self, processor: &IncrementalGraphProcessor, changes: &UpdateResult) -> Result<()> {
178 if self.needs_recomputation(changes) {
180 return self.recompute(processor);
181 }
182
183 let graph = processor.graph();
186 let nodes_batch = &graph.nodes;
187 let edges_batch = &graph.edges;
188
189 if changes.vertices_added > 0 {
191 let node_count = processor.graph().node_count() as f64;
192 let initial_score = 1.0 / node_count;
193
194 for score in self.scores.values_mut() {
196 *score *= (node_count - changes.vertices_added as f64) / node_count;
197 }
198
199 if nodes_batch.num_rows() > 0 {
201 let node_ids = nodes_batch.column(0)
202 .as_any()
203 .downcast_ref::<arrow::array::StringArray>()
204 .ok_or_else(|| crate::error::GraphError::graph_construction("Expected string array for node IDs"))?;
205
206 for i in 0..node_ids.len() {
207 let node = node_ids.value(i).to_string();
208 self.scores.entry(node).or_insert(initial_score);
209 }
210 }
211 }
212
213 if changes.vertices_removed > 0 {
215 let mut valid_nodes = std::collections::HashSet::new();
218 if nodes_batch.num_rows() > 0 {
219 let node_ids = nodes_batch.column(0)
220 .as_any()
221 .downcast_ref::<arrow::array::StringArray>()
222 .ok_or_else(|| crate::error::GraphError::graph_construction("Expected string array for node IDs"))?;
223
224 for i in 0..node_ids.len() {
225 valid_nodes.insert(node_ids.value(i).to_string());
226 }
227 }
228
229 self.scores.retain(|node, _| valid_nodes.contains(node));
230 }
231
232 if changes.edges_added > 0 || changes.edges_removed > 0 {
234 let mut adjacency: HashMap<String, Vec<(String, f64)>> = HashMap::new();
236 if edges_batch.num_rows() > 0 {
237 let source_ids = edges_batch.column(0)
238 .as_any()
239 .downcast_ref::<arrow::array::StringArray>()
240 .ok_or_else(|| crate::error::GraphError::graph_construction("Expected string array for source IDs"))?;
241 let target_ids = edges_batch.column(1)
242 .as_any()
243 .downcast_ref::<arrow::array::StringArray>()
244 .ok_or_else(|| crate::error::GraphError::graph_construction("Expected string array for target IDs"))?;
245 let weights = edges_batch.column(2)
246 .as_any()
247 .downcast_ref::<arrow::array::Float64Array>()
248 .ok_or_else(|| crate::error::GraphError::graph_construction("Expected float64 array for weights"))?;
249
250 for i in 0..source_ids.len() {
251 let source = source_ids.value(i).to_string();
252 let target = target_ids.value(i).to_string();
253 let weight = weights.value(i);
254
255 adjacency.entry(source).or_insert_with(Vec::new).push((target, weight));
256 }
257 }
258
259 let nodes: Vec<String> = self.scores.keys().cloned().collect();
260
261 let update_iterations = std::cmp::min(10, self.max_iterations);
263 for _ in 0..update_iterations {
264 if self.iterate(&adjacency, &nodes)? {
265 break;
266 }
267 }
268 }
269
270 Ok(())
271 }
272
273 fn get_result(&self) -> &HashMap<String, f64> {
274 &self.scores
275 }
276
277 fn recompute(&mut self, processor: &IncrementalGraphProcessor) -> Result<()> {
278 self.scores.clear();
279 self.iteration_count = 0;
280 self.converged = false;
281 self.initialize(processor)
282 }
283
284 fn needs_recomputation(&self, changes: &UpdateResult) -> bool {
285 let total_changes = changes.vertices_added + changes.vertices_removed +
287 changes.edges_added + changes.edges_removed;
288
289 total_changes > 10 || !self.converged
290 }
291}
292
293#[derive(Debug, Clone)]
295pub struct StreamingConnectedComponents {
296 components: HashMap<String, String>, component_sizes: HashMap<String, usize>, }
299
300impl StreamingConnectedComponents {
301 pub fn new() -> Self {
302 Self {
303 components: HashMap::new(),
304 component_sizes: HashMap::new(),
305 }
306 }
307
308 pub fn component_of(&self, node_id: &str) -> Option<&String> {
310 self.components.get(node_id)
311 }
312
313 pub fn component_size(&self, node_id: &str) -> Option<usize> {
315 self.components.get(node_id)
316 .and_then(|comp_id| self.component_sizes.get(comp_id))
317 .copied()
318 }
319
320 pub fn all_components(&self) -> Vec<(String, usize)> {
322 self.component_sizes.iter()
323 .map(|(id, size)| (id.clone(), *size))
324 .collect()
325 }
326
327 pub fn component_count(&self) -> usize {
329 self.component_sizes.len()
330 }
331
332 fn find_root(&self, mut node: String, temp_parents: &mut HashMap<String, String>) -> String {
334 let mut path = Vec::new();
335
336 while let Some(parent) = temp_parents.get(&node).or_else(|| self.components.get(&node)) {
338 if parent == &node {
339 break; }
341 path.push(node.clone());
342 node = parent.clone();
343 }
344
345 for path_node in path {
347 temp_parents.insert(path_node, node.clone());
348 }
349
350 node
351 }
352
353 fn union_components(&mut self, node1: &str, node2: &str) {
355 let comp1 = self.components.get(node1).cloned().unwrap_or_else(|| node1.to_string());
356 let comp2 = self.components.get(node2).cloned().unwrap_or_else(|| node2.to_string());
357
358 if comp1 == comp2 {
359 return; }
361
362 let size1 = self.component_sizes.get(&comp1).copied().unwrap_or(1);
364 let size2 = self.component_sizes.get(&comp2).copied().unwrap_or(1);
365
366 let (smaller, larger, new_size) = if size1 <= size2 {
367 (comp1, comp2, size1 + size2)
368 } else {
369 (comp2, comp1, size1 + size2)
370 };
371
372 let nodes_to_update: Vec<String> = self.components.iter()
374 .filter(|(_, comp)| *comp == &smaller)
375 .map(|(node, _)| node.clone())
376 .collect();
377
378 for node in nodes_to_update {
379 self.components.insert(node, larger.clone());
380 }
381
382 self.component_sizes.insert(larger, new_size);
384 self.component_sizes.remove(&smaller);
385 }
386}
387
388impl Default for StreamingConnectedComponents {
389 fn default() -> Self {
390 Self::new()
391 }
392}
393
394impl StreamingAlgorithm<HashMap<String, String>> for StreamingConnectedComponents {
395 fn initialize(&mut self, processor: &IncrementalGraphProcessor) -> Result<()> {
396 let graph = processor.graph();
397 let nodes_batch = &graph.nodes;
398 let edges_batch = &graph.edges;
399
400 self.components.clear();
401 self.component_sizes.clear();
402
403 if nodes_batch.num_rows() > 0 {
405 let node_ids = nodes_batch.column(0)
406 .as_any()
407 .downcast_ref::<arrow::array::StringArray>()
408 .ok_or_else(|| crate::error::GraphError::graph_construction("Expected string array for node IDs"))?;
409
410 for i in 0..node_ids.len() {
411 let node = node_ids.value(i).to_string();
412 self.components.insert(node.clone(), node.clone());
413 self.component_sizes.insert(node, 1);
414 }
415 }
416
417 if edges_batch.num_rows() > 0 {
419 let source_ids = edges_batch.column(0)
420 .as_any()
421 .downcast_ref::<arrow::array::StringArray>()
422 .ok_or_else(|| crate::error::GraphError::graph_construction("Expected string array for source IDs"))?;
423 let target_ids = edges_batch.column(1)
424 .as_any()
425 .downcast_ref::<arrow::array::StringArray>()
426 .ok_or_else(|| crate::error::GraphError::graph_construction("Expected string array for target IDs"))?;
427
428 for i in 0..source_ids.len() {
429 let source = source_ids.value(i);
430 let target = target_ids.value(i);
431 self.union_components(source, target);
432 }
433 }
434
435 Ok(())
436 }
437
438 fn update(&mut self, processor: &IncrementalGraphProcessor, changes: &UpdateResult) -> Result<()> {
439 if self.needs_recomputation(changes) {
441 return self.recompute(processor);
442 }
443
444 let graph = processor.graph();
445 let nodes_batch = &graph.nodes;
446 let edges_batch = &graph.edges;
447
448 if changes.vertices_added > 0 {
450 if nodes_batch.num_rows() > 0 {
451 let node_ids = nodes_batch.column(0)
452 .as_any()
453 .downcast_ref::<arrow::array::StringArray>()
454 .ok_or_else(|| crate::error::GraphError::graph_construction("Expected string array for node IDs"))?;
455
456 for i in 0..node_ids.len() {
457 let node = node_ids.value(i).to_string();
458 if !self.components.contains_key(&node) {
459 self.components.insert(node.clone(), node.clone());
460 self.component_sizes.insert(node, 1);
461 }
462 }
463 }
464 }
465
466 if changes.vertices_removed > 0 {
468 return self.recompute(processor);
471 }
472
473 if changes.edges_added > 0 {
475 if edges_batch.num_rows() > 0 {
476 let source_ids = edges_batch.column(0)
477 .as_any()
478 .downcast_ref::<arrow::array::StringArray>()
479 .ok_or_else(|| crate::error::GraphError::graph_construction("Expected string array for source IDs"))?;
480 let target_ids = edges_batch.column(1)
481 .as_any()
482 .downcast_ref::<arrow::array::StringArray>()
483 .ok_or_else(|| crate::error::GraphError::graph_construction("Expected string array for target IDs"))?;
484
485 for i in 0..source_ids.len() {
486 let source = source_ids.value(i);
487 let target = target_ids.value(i);
488
489 if !self.components.contains_key(source) {
491 self.components.insert(source.to_string(), source.to_string());
492 self.component_sizes.insert(source.to_string(), 1);
493 }
494 if !self.components.contains_key(target) {
495 self.components.insert(target.to_string(), target.to_string());
496 self.component_sizes.insert(target.to_string(), 1);
497 }
498
499 self.union_components(source, target);
500 }
501 }
502 }
503
504 if changes.edges_removed > 0 {
506 return self.recompute(processor);
509 }
510
511 Ok(())
512 }
513
514 fn get_result(&self) -> &HashMap<String, String> {
515 &self.components
516 }
517
518 fn recompute(&mut self, processor: &IncrementalGraphProcessor) -> Result<()> {
519 self.initialize(processor)
520 }
521
522 fn needs_recomputation(&self, changes: &UpdateResult) -> bool {
523 let total_changes = changes.vertices_added + changes.vertices_removed +
526 changes.edges_added + changes.edges_removed;
527
528 changes.vertices_removed > 0 || changes.edges_removed > 0 || total_changes > 20
529 }
530}
531
532#[cfg(test)]
533mod tests {
534 use super::*;
535 use crate::graph::ArrowGraph;
536 use arrow::array::{StringArray, Float64Array};
537 use arrow::record_batch::RecordBatch;
538 use arrow::datatypes::{Schema, Field, DataType};
539 use std::sync::Arc;
540
541 fn create_test_graph() -> Result<ArrowGraph> {
542 let nodes_schema = Arc::new(Schema::new(vec![
544 Field::new("id", DataType::Utf8, false),
545 ]));
546 let node_ids = StringArray::from(vec!["A", "B", "C", "D"]);
547 let nodes_batch = RecordBatch::try_new(
548 nodes_schema,
549 vec![Arc::new(node_ids)],
550 )?;
551
552 let edges_schema = Arc::new(Schema::new(vec![
554 Field::new("source", DataType::Utf8, false),
555 Field::new("target", DataType::Utf8, false),
556 Field::new("weight", DataType::Float64, false),
557 ]));
558 let sources = StringArray::from(vec!["A", "B"]);
559 let targets = StringArray::from(vec!["B", "C"]);
560 let weights = Float64Array::from(vec![1.0, 1.0]);
561 let edges_batch = RecordBatch::try_new(
562 edges_schema,
563 vec![Arc::new(sources), Arc::new(targets), Arc::new(weights)],
564 )?;
565
566 ArrowGraph::new(nodes_batch, edges_batch)
567 }
568
569 #[test]
570 fn test_streaming_pagerank_initialization() {
571 let graph = create_test_graph().unwrap();
572 let processor = IncrementalGraphProcessor::new(graph).unwrap();
573
574 let mut pagerank = StreamingPageRank::default();
575 pagerank.initialize(&processor).unwrap();
576
577 let scores = pagerank.get_result();
578 assert_eq!(scores.len(), 4); for node in ["A", "B", "C", "D"] {
582 assert!(scores.contains_key(node));
583 assert!(scores[node] > 0.0);
584 }
585
586 assert!(scores["B"] > scores["A"]);
588 assert!(scores["C"] > scores["D"]);
589 }
590
591 #[test]
592 fn test_streaming_pagerank_update() {
593 let graph = create_test_graph().unwrap();
594 let mut processor = IncrementalGraphProcessor::new(graph).unwrap();
595 processor.set_batch_size(1); let mut pagerank = StreamingPageRank::default();
598 pagerank.initialize(&processor).unwrap();
599
600 let initial_scores = pagerank.get_result().clone();
601
602 processor.add_edge("A".to_string(), "D".to_string(), 1.0).unwrap();
604
605 let update_result = crate::streaming::incremental::UpdateResult {
607 vertices_added: 0,
608 vertices_removed: 0,
609 edges_added: 1,
610 edges_removed: 0,
611 affected_components: vec![],
612 recomputation_needed: false,
613 };
614
615 pagerank.update(&processor, &update_result).unwrap();
616
617 let updated_scores = pagerank.get_result();
618
619 assert!(updated_scores["D"] > initial_scores["D"]);
621 }
622
623 #[test]
624 fn test_streaming_connected_components_initialization() {
625 let graph = create_test_graph().unwrap();
626 let processor = IncrementalGraphProcessor::new(graph).unwrap();
627
628 let mut components = StreamingConnectedComponents::new();
629 components.initialize(&processor).unwrap();
630
631 let result = components.get_result();
632 assert_eq!(result.len(), 4); let comp_a = &result["A"];
636 let comp_b = &result["B"];
637 let comp_c = &result["C"];
638 assert_eq!(comp_a, comp_b);
639 assert_eq!(comp_b, comp_c);
640
641 let comp_d = &result["D"];
643 assert_ne!(comp_a, comp_d);
644
645 assert_eq!(components.component_count(), 2);
647 }
648
649 #[test]
650 fn test_streaming_connected_components_update() {
651 let graph = create_test_graph().unwrap();
652 let mut processor = IncrementalGraphProcessor::new(graph).unwrap();
653 processor.set_batch_size(1); let mut components = StreamingConnectedComponents::new();
656 components.initialize(&processor).unwrap();
657
658 assert_eq!(components.component_count(), 2); processor.add_edge("C".to_string(), "D".to_string(), 1.0).unwrap();
662
663 let update_result = crate::streaming::incremental::UpdateResult {
664 vertices_added: 0,
665 vertices_removed: 0,
666 edges_added: 1,
667 edges_removed: 0,
668 affected_components: vec![],
669 recomputation_needed: false,
670 };
671
672 components.update(&processor, &update_result).unwrap();
673
674 assert_eq!(components.component_count(), 1);
676
677 let result = components.get_result();
678 let comp_a = &result["A"];
679 let comp_d = &result["D"];
680 assert_eq!(comp_a, comp_d); }
682
683 #[test]
684 fn test_streaming_algorithm_recomputation() {
685 let graph = create_test_graph().unwrap();
686 let mut processor = IncrementalGraphProcessor::new(graph).unwrap();
687
688 let mut pagerank = StreamingPageRank::default();
689 pagerank.initialize(&processor).unwrap();
690
691 let large_changes = crate::streaming::incremental::UpdateResult {
693 vertices_added: 15,
694 vertices_removed: 5,
695 edges_added: 20,
696 edges_removed: 10,
697 affected_components: vec![],
698 recomputation_needed: true,
699 };
700
701 assert!(pagerank.needs_recomputation(&large_changes));
702
703 pagerank.update(&processor, &large_changes).unwrap();
705
706 let scores = pagerank.get_result();
708 assert_eq!(scores.len(), 4);
709 }
710
711 #[test]
712 fn test_pagerank_top_nodes() {
713 let graph = create_test_graph().unwrap();
714 let processor = IncrementalGraphProcessor::new(graph).unwrap();
715
716 let mut pagerank = StreamingPageRank::default();
717 pagerank.initialize(&processor).unwrap();
718
719 let top_2 = pagerank.top_nodes(2);
720 assert_eq!(top_2.len(), 2);
721
722 assert!(top_2[0].1 >= top_2[1].1);
724
725 assert!(pagerank.node_score("A").is_some());
727 assert!(pagerank.node_score("nonexistent").is_none());
728 }
729
730 #[test]
731 fn test_connected_components_queries() {
732 let graph = create_test_graph().unwrap();
733 let processor = IncrementalGraphProcessor::new(graph).unwrap();
734
735 let mut components = StreamingConnectedComponents::new();
736 components.initialize(&processor).unwrap();
737
738 assert!(components.component_of("A").is_some());
740 assert!(components.component_of("nonexistent").is_none());
741
742 assert!(components.component_size("A").is_some());
743 assert_eq!(components.component_size("A"), Some(3)); assert_eq!(components.component_size("D"), Some(1)); let all_components = components.all_components();
747 assert_eq!(all_components.len(), 2);
748
749 let total_size: usize = all_components.iter().map(|(_, size)| size).sum();
751 assert_eq!(total_size, 4); }
753}