1#[cfg(test)]
2mod tests {
3 use crate::algorithms::pathfinding::{ShortestPath, AllPaths};
4 use crate::algorithms::centrality::PageRank;
5 use crate::algorithms::components::{WeaklyConnectedComponents, StronglyConnectedComponents};
6 use crate::algorithms::community::LeidenCommunityDetection;
7 use crate::algorithms::aggregation::{TriangleCount, ClusteringCoefficient};
8 use crate::{GraphAlgorithm, AlgorithmParams};
9 use crate::ArrowGraph;
10 use arrow::record_batch::RecordBatch;
11 use arrow::array::{StringArray, Float64Array, UInt32Array, UInt64Array};
12 use arrow::datatypes::{DataType, Field, Schema};
13 use std::sync::Arc;
14
15 fn create_test_graph() -> ArrowGraph {
16 let schema = Arc::new(Schema::new(vec![
17 Field::new("source", DataType::Utf8, false),
18 Field::new("target", DataType::Utf8, false),
19 Field::new("weight", DataType::Float64, true),
20 ]));
21
22 let source_array = StringArray::from(vec!["A", "A", "B", "C", "D"]);
23 let target_array = StringArray::from(vec!["B", "C", "C", "D", "E"]);
24 let weight_array = Float64Array::from(vec![
25 Some(1.0),
26 Some(4.0),
27 Some(2.0),
28 Some(1.0),
29 Some(1.0)
30 ]);
31
32 let edges = RecordBatch::try_new(
33 schema,
34 vec![
35 Arc::new(source_array) as Arc<dyn arrow::array::Array>,
36 Arc::new(target_array) as Arc<dyn arrow::array::Array>,
37 Arc::new(weight_array) as Arc<dyn arrow::array::Array>
38 ],
39 ).unwrap();
40
41 ArrowGraph::from_edges(edges).unwrap()
42 }
43
44 fn create_disconnected_graph() -> ArrowGraph {
45 let schema = Arc::new(Schema::new(vec![
46 Field::new("source", DataType::Utf8, false),
47 Field::new("target", DataType::Utf8, false),
48 Field::new("weight", DataType::Float64, true),
49 ]));
50
51 let source_array = StringArray::from(vec!["A", "C"]);
53 let target_array = StringArray::from(vec!["B", "D"]);
54 let weight_array = Float64Array::from(vec![Some(1.0), Some(1.0)]);
55
56 let edges = RecordBatch::try_new(
57 schema,
58 vec![
59 Arc::new(source_array) as Arc<dyn arrow::array::Array>,
60 Arc::new(target_array) as Arc<dyn arrow::array::Array>,
61 Arc::new(weight_array) as Arc<dyn arrow::array::Array>
62 ],
63 ).unwrap();
64
65 ArrowGraph::from_edges(edges).unwrap()
66 }
67
68 #[test]
69 fn test_shortest_path_single_target() {
70 let graph = create_test_graph();
71 let algorithm = ShortestPath;
72
73 let params = AlgorithmParams::new()
74 .with_param("source", "A")
75 .with_param("target", "D");
76
77 let result = algorithm.execute(&graph, ¶ms).unwrap();
78
79 assert_eq!(result.num_rows(), 1);
80 assert_eq!(result.num_columns(), 4);
81
82 let source_col = result.column(0).as_any().downcast_ref::<StringArray>().unwrap();
84 let target_col = result.column(1).as_any().downcast_ref::<StringArray>().unwrap();
85 let distance_col = result.column(2).as_any().downcast_ref::<Float64Array>().unwrap();
86
87 assert_eq!(source_col.value(0), "A");
88 assert_eq!(target_col.value(0), "D");
89 assert_eq!(distance_col.value(0), 4.0); }
91
92 #[test]
93 fn test_shortest_path_all_targets() {
94 let graph = create_test_graph();
95 let algorithm = ShortestPath;
96
97 let params = AlgorithmParams::new()
98 .with_param("source", "A");
99
100 let result = algorithm.execute(&graph, ¶ms).unwrap();
101
102 assert!(result.num_rows() > 0);
104 assert_eq!(result.num_columns(), 3);
105
106 let source_col = result.column(0).as_any().downcast_ref::<StringArray>().unwrap();
107
108 for i in 0..result.num_rows() {
110 assert_eq!(source_col.value(i), "A");
111 }
112 }
113
114 #[test]
115 fn test_all_paths() {
116 let graph = create_test_graph();
117 let algorithm = AllPaths;
118
119 let params = AlgorithmParams::new()
120 .with_param("source", "A")
121 .with_param("target", "D")
122 .with_param("max_hops", 5);
123
124 let result = algorithm.execute(&graph, ¶ms).unwrap();
125
126 assert!(result.num_rows() >= 1);
128 assert_eq!(result.num_columns(), 4);
129
130 let source_col = result.column(0).as_any().downcast_ref::<StringArray>().unwrap();
131 let target_col = result.column(1).as_any().downcast_ref::<StringArray>().unwrap();
132
133 for i in 0..result.num_rows() {
134 assert_eq!(source_col.value(i), "A");
135 assert_eq!(target_col.value(i), "D");
136 }
137 }
138
139 #[test]
140 fn test_shortest_path_no_path() {
141 let graph = create_test_graph();
142 let algorithm = ShortestPath;
143
144 let params = AlgorithmParams::new()
145 .with_param("source", "E")
146 .with_param("target", "A");
147
148 let result = algorithm.execute(&graph, ¶ms);
149
150 assert!(result.is_err());
152 }
153
154 #[test]
155 fn test_pagerank() {
156 let graph = create_test_graph();
157 let algorithm = PageRank;
158
159 let params = AlgorithmParams::new()
160 .with_param("damping_factor", 0.85)
161 .with_param("max_iterations", 100)
162 .with_param("tolerance", 1e-6);
163
164 let result = algorithm.execute(&graph, ¶ms).unwrap();
165
166 assert_eq!(result.num_rows(), graph.node_count());
168 assert_eq!(result.num_columns(), 2);
169
170 let node_col = result.column(0).as_any().downcast_ref::<StringArray>().unwrap();
171 let score_col = result.column(1).as_any().downcast_ref::<Float64Array>().unwrap();
172
173 let mut found_nodes = std::collections::HashSet::new();
175 let mut total_score = 0.0;
176
177 for i in 0..result.num_rows() {
178 let node_id = node_col.value(i);
179 let score = score_col.value(i);
180
181 found_nodes.insert(node_id);
182 total_score += score;
183
184 assert!(score > 0.0);
186 }
187
188 assert!(found_nodes.contains("A"));
190 assert!(found_nodes.contains("B"));
191 assert!(found_nodes.contains("C"));
192 assert!(found_nodes.contains("D"));
193 assert!(found_nodes.contains("E"));
194
195 assert!((total_score - 1.0).abs() < 1e-10);
197 }
198
199 #[test]
200 fn test_pagerank_with_custom_params() {
201 let graph = create_test_graph();
202 let algorithm = PageRank;
203
204 let params = AlgorithmParams::new()
205 .with_param("damping_factor", 0.5)
206 .with_param("max_iterations", 10)
207 .with_param("tolerance", 1e-3);
208
209 let result = algorithm.execute(&graph, ¶ms).unwrap();
210
211 assert!(result.num_rows() > 0);
212
213 let score_col = result.column(1).as_any().downcast_ref::<Float64Array>().unwrap();
214 let total_score: f64 = (0..result.num_rows()).map(|i| score_col.value(i)).sum();
215
216 assert!((total_score - 1.0).abs() < 1e-2);
218 }
219
220 #[test]
221 fn test_pagerank_invalid_params() {
222 let graph = create_test_graph();
223 let algorithm = PageRank;
224
225 let params = AlgorithmParams::new().with_param("damping_factor", 1.5);
227 let result = algorithm.execute(&graph, ¶ms);
228 assert!(result.is_err());
229
230 let params = AlgorithmParams::new().with_param("max_iterations", 0);
232 let result = algorithm.execute(&graph, ¶ms);
233 assert!(result.is_err());
234
235 let params = AlgorithmParams::new().with_param("tolerance", -1.0);
237 let result = algorithm.execute(&graph, ¶ms);
238 assert!(result.is_err());
239 }
240
241 #[test]
242 fn test_weakly_connected_components() {
243 let graph = create_test_graph();
244 let algorithm = WeaklyConnectedComponents;
245
246 let params = AlgorithmParams::new();
247 let result = algorithm.execute(&graph, ¶ms).unwrap();
248
249 assert_eq!(result.num_rows(), graph.node_count());
251 assert_eq!(result.num_columns(), 2);
252
253 let node_col = result.column(0).as_any().downcast_ref::<StringArray>().unwrap();
254 let component_col = result.column(1).as_any().downcast_ref::<UInt32Array>().unwrap();
255
256 let mut found_nodes = std::collections::HashSet::new();
258 for i in 0..result.num_rows() {
259 let node_id = node_col.value(i);
260 let component_id = component_col.value(i);
261
262 found_nodes.insert(node_id);
263 assert_eq!(component_id, 0); }
265
266 assert!(found_nodes.contains("A"));
268 assert!(found_nodes.contains("B"));
269 assert!(found_nodes.contains("C"));
270 assert!(found_nodes.contains("D"));
271 assert!(found_nodes.contains("E"));
272 }
273
274 #[test]
275 fn test_weakly_connected_components_disconnected() {
276 let graph = create_disconnected_graph();
277 let algorithm = WeaklyConnectedComponents;
278
279 let params = AlgorithmParams::new();
280 let result = algorithm.execute(&graph, ¶ms).unwrap();
281
282 assert_eq!(result.num_rows(), 4);
284 assert_eq!(result.num_columns(), 2);
285
286 let node_col = result.column(0).as_any().downcast_ref::<StringArray>().unwrap();
287 let component_col = result.column(1).as_any().downcast_ref::<UInt32Array>().unwrap();
288
289 let mut component_map: std::collections::HashMap<String, u32> = std::collections::HashMap::new();
290 for i in 0..result.num_rows() {
291 let node_id = node_col.value(i).to_string();
292 let component_id = component_col.value(i);
293 component_map.insert(node_id, component_id);
294 }
295
296 assert_eq!(component_map.get("A"), component_map.get("B"));
298
299 assert_eq!(component_map.get("C"), component_map.get("D"));
301
302 assert_ne!(component_map.get("A"), component_map.get("C"));
304 }
305
306 #[test]
307 fn test_strongly_connected_components() {
308 let graph = create_test_graph();
309 let algorithm = StronglyConnectedComponents;
310
311 let params = AlgorithmParams::new();
312 let result = algorithm.execute(&graph, ¶ms).unwrap();
313
314 assert_eq!(result.num_rows(), graph.node_count());
316 assert_eq!(result.num_columns(), 2);
317
318 let node_col = result.column(0).as_any().downcast_ref::<StringArray>().unwrap();
319 let component_col = result.column(1).as_any().downcast_ref::<UInt32Array>().unwrap();
320
321 let mut component_ids = std::collections::HashSet::new();
323 for i in 0..result.num_rows() {
324 let component_id = component_col.value(i);
325 component_ids.insert(component_id);
326 }
327
328 assert_eq!(component_ids.len(), graph.node_count());
330 }
331
332 fn create_strongly_connected_graph() -> ArrowGraph {
333 let schema = Arc::new(Schema::new(vec![
334 Field::new("source", DataType::Utf8, false),
335 Field::new("target", DataType::Utf8, false),
336 Field::new("weight", DataType::Float64, true),
337 ]));
338
339 let source_array = StringArray::from(vec!["A", "B", "C", "D", "E"]);
341 let target_array = StringArray::from(vec!["B", "C", "A", "E", "D"]);
342 let weight_array = Float64Array::from(vec![
343 Some(1.0),
344 Some(1.0),
345 Some(1.0),
346 Some(1.0),
347 Some(1.0)
348 ]);
349
350 let edges = RecordBatch::try_new(
351 schema,
352 vec![
353 Arc::new(source_array) as Arc<dyn arrow::array::Array>,
354 Arc::new(target_array) as Arc<dyn arrow::array::Array>,
355 Arc::new(weight_array) as Arc<dyn arrow::array::Array>
356 ],
357 ).unwrap();
358
359 ArrowGraph::from_edges(edges).unwrap()
360 }
361
362 #[test]
363 fn test_strongly_connected_components_with_cycles() {
364 let graph = create_strongly_connected_graph();
365 let algorithm = StronglyConnectedComponents;
366
367 let params = AlgorithmParams::new();
368 let result = algorithm.execute(&graph, ¶ms).unwrap();
369
370 assert_eq!(result.num_rows(), 5);
371 assert_eq!(result.num_columns(), 2);
372
373 let node_col = result.column(0).as_any().downcast_ref::<StringArray>().unwrap();
374 let component_col = result.column(1).as_any().downcast_ref::<UInt32Array>().unwrap();
375
376 let mut component_map: std::collections::HashMap<String, u32> = std::collections::HashMap::new();
377 for i in 0..result.num_rows() {
378 let node_id = node_col.value(i).to_string();
379 let component_id = component_col.value(i);
380 component_map.insert(node_id, component_id);
381 }
382
383 assert_eq!(component_map.get("A"), component_map.get("B"));
385 assert_eq!(component_map.get("B"), component_map.get("C"));
386
387 assert_eq!(component_map.get("D"), component_map.get("E"));
389
390 assert_ne!(component_map.get("A"), component_map.get("D"));
392 }
393
394 fn create_community_graph() -> ArrowGraph {
395 let schema = Arc::new(Schema::new(vec![
396 Field::new("source", DataType::Utf8, false),
397 Field::new("target", DataType::Utf8, false),
398 Field::new("weight", DataType::Float64, true),
399 ]));
400
401 let source_array = StringArray::from(vec!["A", "B", "C", "A", "C", "D", "E", "F", "D", "E"]);
406 let target_array = StringArray::from(vec!["B", "C", "A", "C", "D", "E", "F", "D", "F", "F"]);
407 let weight_array = Float64Array::from(vec![
408 Some(1.0), Some(1.0), Some(1.0), Some(1.0), Some(0.5), Some(1.0), Some(1.0), Some(1.0), Some(1.0), Some(1.0) ]);
411
412 let edges = RecordBatch::try_new(
413 schema,
414 vec![
415 Arc::new(source_array) as Arc<dyn arrow::array::Array>,
416 Arc::new(target_array) as Arc<dyn arrow::array::Array>,
417 Arc::new(weight_array) as Arc<dyn arrow::array::Array>
418 ],
419 ).unwrap();
420
421 ArrowGraph::from_edges(edges).unwrap()
422 }
423
424 #[test]
425 fn test_leiden_community_detection() {
426 let graph = create_community_graph();
427 let algorithm = LeidenCommunityDetection;
428
429 let params = AlgorithmParams::new()
430 .with_param("resolution", 1.0)
431 .with_param("max_iterations", 1);
432
433 let result = algorithm.execute(&graph, ¶ms).unwrap();
434
435 assert_eq!(result.num_rows(), 6); assert_eq!(result.num_columns(), 2);
438
439 let node_col = result.column(0).as_any().downcast_ref::<StringArray>().unwrap();
440 let community_col = result.column(1).as_any().downcast_ref::<UInt32Array>().unwrap();
441
442 let mut community_map: std::collections::HashMap<String, u32> = std::collections::HashMap::new();
443 for i in 0..result.num_rows() {
444 let node_id = node_col.value(i).to_string();
445 let community_id = community_col.value(i);
446 community_map.insert(node_id, community_id);
447 }
448
449 let unique_communities: std::collections::HashSet<u32> = community_map.values().cloned().collect();
451 assert!(unique_communities.len() >= 2);
452
453 assert!(community_map.contains_key("A"));
455 assert!(community_map.contains_key("B"));
456 assert!(community_map.contains_key("C"));
457 assert!(community_map.contains_key("D"));
458 assert!(community_map.contains_key("E"));
459 assert!(community_map.contains_key("F"));
460 }
461
462 #[test]
463 fn test_leiden_with_custom_resolution() {
464 let graph = create_test_graph();
465 let algorithm = LeidenCommunityDetection;
466
467 let params = AlgorithmParams::new()
468 .with_param("resolution", 0.5)
469 .with_param("max_iterations", 10);
470
471 let result = algorithm.execute(&graph, ¶ms).unwrap();
472
473 assert!(result.num_rows() > 0);
474 assert_eq!(result.num_columns(), 2);
475 }
476
477 #[test]
478 fn test_leiden_invalid_params() {
479 let graph = create_test_graph();
480 let algorithm = LeidenCommunityDetection;
481
482 let params = AlgorithmParams::new().with_param("resolution", -1.0);
484 let result = algorithm.execute(&graph, ¶ms);
485 assert!(result.is_err());
486
487 let params = AlgorithmParams::new().with_param("max_iterations", 0);
489 let result = algorithm.execute(&graph, ¶ms);
490 assert!(result.is_err());
491 }
492
493
494 fn create_triangle_graph() -> ArrowGraph {
495 let schema = Arc::new(Schema::new(vec![
496 Field::new("source", DataType::Utf8, false),
497 Field::new("target", DataType::Utf8, false),
498 Field::new("weight", DataType::Float64, true),
499 ]));
500
501 let source_array = StringArray::from(vec!["A", "B", "C", "B"]);
503 let target_array = StringArray::from(vec!["B", "C", "A", "D"]);
504 let weight_array = Float64Array::from(vec![
505 Some(1.0), Some(1.0), Some(1.0), Some(1.0)
506 ]);
507
508 let edges = RecordBatch::try_new(
509 schema,
510 vec![
511 Arc::new(source_array) as Arc<dyn arrow::array::Array>,
512 Arc::new(target_array) as Arc<dyn arrow::array::Array>,
513 Arc::new(weight_array) as Arc<dyn arrow::array::Array>
514 ],
515 ).unwrap();
516
517 ArrowGraph::from_edges(edges).unwrap()
518 }
519
520 #[test]
521 fn test_triangle_count() {
522 let graph = create_triangle_graph();
523 let algorithm = TriangleCount;
524
525 let params = AlgorithmParams::new();
526 let result = algorithm.execute(&graph, ¶ms).unwrap();
527
528 assert_eq!(result.num_rows(), 1);
529 assert_eq!(result.num_columns(), 2);
530
531 let metric_col = result.column(0).as_any().downcast_ref::<StringArray>().unwrap();
532 let value_col = result.column(1).as_any().downcast_ref::<UInt64Array>().unwrap();
533
534 assert_eq!(metric_col.value(0), "triangle_count");
535 assert_eq!(value_col.value(0), 1); }
537
538 #[test]
539 fn test_clustering_coefficient_local() {
540 let graph = create_triangle_graph();
541 let algorithm = ClusteringCoefficient;
542
543 let params = AlgorithmParams::new().with_param("mode", "local");
544 let result = algorithm.execute(&graph, ¶ms).unwrap();
545
546 assert_eq!(result.num_rows(), 4); assert_eq!(result.num_columns(), 2);
548
549 let node_col = result.column(0).as_any().downcast_ref::<StringArray>().unwrap();
550 let coeff_col = result.column(1).as_any().downcast_ref::<Float64Array>().unwrap();
551
552 let mut coeff_map = std::collections::HashMap::new();
554 for i in 0..result.num_rows() {
555 let node_id = node_col.value(i);
556 let coefficient = coeff_col.value(i);
557 coeff_map.insert(node_id, coefficient);
558 }
559
560 for (node_id, coefficient) in &coeff_map {
563 assert!(*coefficient >= 0.0, "Node {} has negative coefficient: {}", node_id, coefficient);
564 assert!(*coefficient <= 1.0, "Node {} has coefficient > 1.0: {}", node_id, coefficient);
565 }
566
567 assert_eq!(coeff_map.get("D").unwrap(), &0.0);
569 }
570
571 #[test]
572 fn test_clustering_coefficient_global() {
573 let graph = create_triangle_graph();
574 let algorithm = ClusteringCoefficient;
575
576 let params = AlgorithmParams::new().with_param("mode", "global");
577 let result = algorithm.execute(&graph, ¶ms).unwrap();
578
579 assert_eq!(result.num_rows(), 1);
580 assert_eq!(result.num_columns(), 2);
581
582 let metric_col = result.column(0).as_any().downcast_ref::<StringArray>().unwrap();
583 let value_col = result.column(1).as_any().downcast_ref::<Float64Array>().unwrap();
584
585 assert_eq!(metric_col.value(0), "global_clustering_coefficient");
586
587 let global_coefficient = value_col.value(0);
590 assert!(global_coefficient >= 0.0, "Global coefficient should be non-negative: {}", global_coefficient);
591 assert!(global_coefficient <= 1.0, "Global coefficient should be <= 1.0: {}", global_coefficient);
592 }
593
594 #[test]
595 fn test_clustering_coefficient_invalid_mode() {
596 let graph = create_test_graph();
597 let algorithm = ClusteringCoefficient;
598
599 let params = AlgorithmParams::new().with_param("mode", "invalid");
600 let result = algorithm.execute(&graph, ¶ms);
601 assert!(result.is_err());
602 }
603
604 #[test]
605 fn test_triangle_count_no_triangles() {
606 let graph = create_test_graph(); let algorithm = TriangleCount;
608
609 let params = AlgorithmParams::new();
610 let result = algorithm.execute(&graph, ¶ms).unwrap();
611
612 let value_col = result.column(1).as_any().downcast_ref::<UInt64Array>().unwrap();
613 assert!(value_col.value(0) >= 0);
616 }
617
618 #[test]
619 fn test_algorithm_names_with_aggregation() {
620 let shortest_path = ShortestPath;
621 let all_paths = AllPaths;
622 let pagerank = PageRank;
623 let weakly_connected = WeaklyConnectedComponents;
624 let strongly_connected = StronglyConnectedComponents;
625 let leiden = LeidenCommunityDetection;
626 let triangle_count = TriangleCount;
627 let clustering = ClusteringCoefficient;
628
629 assert_eq!(shortest_path.name(), "shortest_path");
630 assert_eq!(all_paths.name(), "all_paths");
631 assert_eq!(pagerank.name(), "pagerank");
632 assert_eq!(weakly_connected.name(), "weakly_connected_components");
633 assert_eq!(strongly_connected.name(), "strongly_connected_components");
634 assert_eq!(leiden.name(), "leiden");
635 assert_eq!(triangle_count.name(), "triangle_count");
636 assert_eq!(clustering.name(), "clustering_coefficient");
637 }
638}