scirs2_graph/
algorithms.rs

1//! Graph algorithms
2//!
3//! This module provides various algorithms for graph analysis and manipulation.
4//! The algorithms are organized into submodules by category:
5//!
6//! - `traversal`: BFS, DFS, and other traversal algorithms
7//! - `shortest_path`: Dijkstra, A*, Floyd-Warshall, etc.
8//! - `connectivity`: Connected components, articulation points, bridges
9//! - `flow`: Network flow and cut algorithms
10//! - `matching`: Bipartite matching algorithms
11//! - `coloring`: Graph coloring algorithms
12//! - `paths`: Eulerian and Hamiltonian path algorithms
13//! - `community`: Community detection algorithms
14//! - `decomposition`: Graph decomposition algorithms
15//! - `isomorphism`: Graph isomorphism and subgraph matching
16//! - `motifs`: Motif finding algorithms
17//! - `random_walk`: Random walk and PageRank algorithms
18//! - `similarity`: Node and graph similarity measures
19//! - `properties`: Graph properties like diameter, radius, center
20
21pub mod coloring;
22pub mod community;
23pub mod connectivity;
24pub mod decomposition;
25pub mod flow;
26pub mod hypergraph;
27pub mod isomorphism;
28pub mod matching;
29pub mod motifs;
30pub mod paths;
31pub mod properties;
32pub mod random_walk;
33pub mod shortest_path;
34pub mod similarity;
35pub mod transformations;
36pub mod traversal;
37
38// Re-export all public items for convenience
39pub use coloring::*;
40// Modern community detection APIs - stable for 1.0
41pub use community::{
42    // Standardized community detection algorithms - stable for 1.0
43    fluid_communities_result,
44    greedy_modularity_optimization_result,
45    hierarchical_communities_result,
46    infomap_communities,
47    label_propagation_result,
48    louvain_communities_result,
49    modularity,
50    modularity_optimization_result,
51    // Standardized result types - stable for 1.0
52    CommunityResult,
53    CommunityStructure,
54    InfomapResult,
55};
56
57#[cfg(feature = "parallel")]
58pub use community::{
59    parallel_label_propagation_result, parallel_louvain_communities_result, parallel_modularity,
60};
61
62// Legacy community detection APIs - deprecated
63#[deprecated(
64    since = "0.1.0-beta.2",
65    note = "Use `louvain_communities_result` instead"
66)]
67#[allow(deprecated)]
68pub use community::louvain_communities;
69
70#[deprecated(
71    since = "0.1.0-beta.2",
72    note = "Use `label_propagation_result` instead"
73)]
74#[allow(deprecated)]
75pub use community::label_propagation;
76
77#[deprecated(
78    since = "0.1.0-beta.2",
79    note = "Use `fluid_communities_result` instead"
80)]
81#[allow(deprecated)]
82pub use community::fluid_communities;
83
84#[deprecated(
85    since = "0.1.0-beta.2",
86    note = "Use `hierarchical_communities_result` instead"
87)]
88#[allow(deprecated)]
89pub use community::hierarchical_communities;
90
91#[deprecated(
92    since = "0.1.0-beta.2",
93    note = "Use `modularity_optimization_result` instead"
94)]
95#[allow(deprecated)]
96pub use community::modularity_optimization;
97
98#[deprecated(
99    since = "0.1.0-beta.2",
100    note = "Use `greedy_modularity_optimization_result` instead"
101)]
102#[allow(deprecated)]
103pub use community::greedy_modularity_optimization;
104
105#[deprecated(
106    since = "0.1.0-beta.2",
107    note = "Use `parallel_louvain_communities_result` instead"
108)]
109#[allow(deprecated)]
110pub use community::parallel_louvain_communities;
111pub use connectivity::*;
112pub use decomposition::*;
113pub use flow::{dinic_max_flow, minimum_cut, push_relabel_max_flow};
114pub use hypergraph::*;
115pub use isomorphism::*;
116pub use matching::*;
117pub use motifs::*;
118pub use paths::*;
119pub use properties::*;
120pub use random_walk::*;
121pub use shortest_path::*;
122pub use similarity::*;
123pub use transformations::*;
124pub use traversal::*;
125
126// Additional algorithms that haven't been moved to submodules yet
127
128use crate::base::{DiGraph, EdgeWeight, Graph, IndexType, Node};
129use crate::error::{GraphError, Result};
130use petgraph::algo::toposort as petgraph_toposort;
131use petgraph::visit::EdgeRef;
132use petgraph::Direction;
133use scirs2_core::ndarray::{Array1, Array2};
134use std::cmp::Ordering;
135use std::collections::{HashMap, VecDeque};
136
137/// Kruskal's algorithm for finding minimum spanning tree
138///
139/// Returns a vector of edges that form the minimum spanning tree.
140/// Only works on undirected graphs.
141#[allow(dead_code)]
142pub fn minimum_spanning_tree<N, E, Ix>(
143    graph: &Graph<N, E, Ix>,
144) -> Result<Vec<crate::base::Edge<N, E>>>
145where
146    N: Node + std::fmt::Debug,
147    E: EdgeWeight + Into<f64> + std::cmp::PartialOrd,
148    Ix: petgraph::graph::IndexType,
149{
150    // Get all edges and sort by weight
151    let mut edges: Vec<_> = graph
152        .inner()
153        .edge_references()
154        .map(|e| {
155            let source = graph.inner()[e.source()].clone();
156            let target = graph.inner()[e.target()].clone();
157            let weight = e.weight().clone();
158            (source, target, weight)
159        })
160        .collect();
161
162    edges.sort_by(|a, b| a.2.partial_cmp(&b.2).unwrap_or(Ordering::Equal));
163
164    // Use Union-Find to detect cycles
165    let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
166    let mut parent: HashMap<N, N> = nodes.iter().map(|n| (n.clone(), n.clone())).collect();
167    let mut rank: HashMap<N, usize> = nodes.iter().map(|n| (n.clone(), 0)).collect();
168
169    fn find<N: Node>(parent: &mut HashMap<N, N>, node: &N) -> N {
170        if parent[node] != *node {
171            let root = find(parent, &parent[node].clone());
172            parent.insert(node.clone(), root.clone());
173        }
174        parent[node].clone()
175    }
176
177    fn union<N: Node>(
178        parent: &mut HashMap<N, N>,
179        rank: &mut HashMap<N, usize>,
180        x: &N,
181        y: &N,
182    ) -> bool {
183        let root_x = find(parent, x);
184        let root_y = find(parent, y);
185
186        if root_x == root_y {
187            return false; // Already in same set
188        }
189
190        // Union by rank
191        match rank[&root_x].cmp(&rank[&root_y]) {
192            Ordering::Less => {
193                parent.insert(root_x, root_y);
194            }
195            Ordering::Greater => {
196                parent.insert(root_y, root_x);
197            }
198            Ordering::Equal => {
199                parent.insert(root_y, root_x.clone());
200                *rank.get_mut(&root_x).expect("Operation failed") += 1;
201            }
202        }
203        true
204    }
205
206    let mut mst = Vec::new();
207
208    for (source, target, weight) in edges {
209        if union(&mut parent, &mut rank, &source, &target) {
210            mst.push(crate::base::Edge {
211                source,
212                target,
213                weight,
214            });
215        }
216    }
217
218    Ok(mst)
219}
220
221/// Topological sort for directed acyclic graphs
222///
223/// Returns nodes in topological order if the graph is a DAG,
224/// otherwise returns an error indicating a cycle was found.
225#[allow(dead_code)]
226pub fn topological_sort<N, E, Ix>(graph: &DiGraph<N, E, Ix>) -> Result<Vec<N>>
227where
228    N: Node + std::fmt::Debug,
229    E: EdgeWeight,
230    Ix: IndexType,
231{
232    // Use petgraph's topological sort
233    match petgraph_toposort(graph.inner(), None) {
234        Ok(indices) => {
235            let sorted_nodes = indices
236                .into_iter()
237                .map(|idx| graph.inner()[idx].clone())
238                .collect();
239            Ok(sorted_nodes)
240        }
241        Err(_) => Err(GraphError::CycleDetected {
242            start_node: "unknown".to_string(),
243            cycle_length: 0,
244        }),
245    }
246}
247
248/// PageRank algorithm for computing node importance
249///
250/// Returns a map from nodes to their PageRank scores.
251#[allow(dead_code)]
252pub fn pagerank<N, E, Ix>(
253    graph: &DiGraph<N, E, Ix>,
254    damping_factor: f64,
255    tolerance: f64,
256    max_iterations: usize,
257) -> HashMap<N, f64>
258where
259    N: Node + std::fmt::Debug,
260    E: EdgeWeight,
261    Ix: IndexType,
262{
263    let nodes: Vec<_> = graph.inner().node_indices().collect();
264    let n = nodes.len();
265
266    if n == 0 {
267        return HashMap::new();
268    }
269
270    // Initialize PageRank values
271    let mut pr = vec![1.0 / n as f64; n];
272    let mut new_pr = vec![0.0; n];
273
274    // Create node index mapping
275    let node_to_idx: HashMap<_, _> = nodes.iter().enumerate().map(|(i, &n)| (n, i)).collect();
276
277    // Iterate until convergence
278    for _ in 0..max_iterations {
279        // Reset new PageRank values
280        for pr in new_pr.iter_mut().take(n) {
281            *pr = (1.0 - damping_factor) / n as f64;
282        }
283
284        // Calculate contributions from incoming edges
285        for (i, &node_idx) in nodes.iter().enumerate() {
286            let out_degree = graph
287                .inner()
288                .edges_directed(node_idx, Direction::Outgoing)
289                .count();
290
291            if out_degree > 0 {
292                let contribution = damping_factor * pr[i] / out_degree as f64;
293
294                for edge in graph.inner().edges_directed(node_idx, Direction::Outgoing) {
295                    if let Some(&j) = node_to_idx.get(&edge.target()) {
296                        new_pr[j] += contribution;
297                    }
298                }
299            } else {
300                // Dangling node: distribute equally to all nodes
301                let contribution = damping_factor * pr[i] / n as f64;
302                for pr_val in new_pr.iter_mut().take(n) {
303                    *pr_val += contribution;
304                }
305            }
306        }
307
308        // Check convergence
309        let diff: f64 = pr
310            .iter()
311            .zip(&new_pr)
312            .map(|(old, new)| (old - new).abs())
313            .sum();
314
315        // Swap vectors
316        std::mem::swap(&mut pr, &mut new_pr);
317
318        if diff < tolerance {
319            break;
320        }
321    }
322
323    // Convert to HashMap
324    nodes
325        .iter()
326        .enumerate()
327        .map(|(i, &node_idx)| (graph.inner()[node_idx].clone(), pr[i]))
328        .collect()
329}
330
331/// Betweenness centrality for nodes
332///
333/// Measures the extent to which a node lies on paths between other nodes.
334#[allow(dead_code)]
335pub fn betweenness_centrality<N, E, Ix>(
336    graph: &Graph<N, E, Ix>,
337    normalized: bool,
338) -> HashMap<N, f64>
339where
340    N: Node + std::fmt::Debug,
341    E: EdgeWeight,
342    Ix: IndexType,
343{
344    let node_indices: Vec<_> = graph.inner().node_indices().collect();
345    let nodes: Vec<N> = node_indices
346        .iter()
347        .map(|&idx| graph.inner()[idx].clone())
348        .collect();
349    let n = nodes.len();
350    let mut centrality: HashMap<N, f64> = nodes.iter().map(|n| (n.clone(), 0.0)).collect();
351
352    // For each source node
353    for s in &nodes {
354        // Single-source shortest paths
355        let mut stack = Vec::new();
356        let mut paths: HashMap<N, Vec<N>> = HashMap::new();
357        let mut sigma: HashMap<N, f64> = nodes.iter().map(|n| (n.clone(), 0.0)).collect();
358        let mut dist: HashMap<N, Option<f64>> = nodes.iter().map(|n| (n.clone(), None)).collect();
359
360        sigma.insert(s.clone(), 1.0);
361        dist.insert(s.clone(), Some(0.0));
362
363        let mut queue = VecDeque::new();
364        queue.push_back(s.clone());
365
366        // BFS
367        while let Some(v) = queue.pop_front() {
368            stack.push(v.clone());
369
370            if let Ok(neighbors) = graph.neighbors(&v) {
371                for w in neighbors {
372                    // First time we reach w?
373                    if dist[&w].is_none() {
374                        dist.insert(w.clone(), Some(dist[&v].expect("Operation failed") + 1.0));
375                        queue.push_back(w.clone());
376                    }
377
378                    // Shortest path to w via v?
379                    if dist[&w] == Some(dist[&v].expect("Operation failed") + 1.0) {
380                        *sigma.get_mut(&w).expect("Operation failed") += sigma[&v];
381                        paths.entry(w.clone()).or_default().push(v.clone());
382                    }
383                }
384            }
385        }
386
387        // Accumulation
388        let mut delta: HashMap<N, f64> = nodes.iter().map(|n| (n.clone(), 0.0)).collect();
389
390        while let Some(w) = stack.pop() {
391            if let Some(predecessors) = paths.get(&w) {
392                for v in predecessors {
393                    *delta.get_mut(v).expect("Operation failed") +=
394                        (sigma[v] / sigma[&w]) * (1.0 + delta[&w]);
395                }
396            }
397
398            if w != *s {
399                *centrality.get_mut(&w).expect("Operation failed") += delta[&w];
400            }
401        }
402    }
403
404    // Normalization
405    if normalized && n > 2 {
406        let scale = 1.0 / ((n - 1) * (n - 2)) as f64;
407        for value in centrality.values_mut() {
408            *value *= scale;
409        }
410    }
411
412    centrality
413}
414
415/// Closeness centrality for nodes
416///
417/// Measures how close a node is to all other nodes in the graph.
418#[allow(dead_code)]
419pub fn closeness_centrality<N, E, Ix>(graph: &Graph<N, E, Ix>, normalized: bool) -> HashMap<N, f64>
420where
421    N: Node + std::fmt::Debug,
422    E: EdgeWeight
423        + Into<f64>
424        + scirs2_core::numeric::Zero
425        + scirs2_core::numeric::One
426        + std::ops::Add<Output = E>
427        + PartialOrd
428        + std::marker::Copy
429        + std::fmt::Debug
430        + std::default::Default,
431    Ix: IndexType,
432{
433    let node_indices: Vec<_> = graph.inner().node_indices().collect();
434    let nodes: Vec<N> = node_indices
435        .iter()
436        .map(|&idx| graph.inner()[idx].clone())
437        .collect();
438    let n = nodes.len();
439    let mut centrality = HashMap::new();
440
441    for node in &nodes {
442        let mut total_distance = 0.0;
443        let mut reachable_count = 0;
444
445        // Calculate shortest paths to all other nodes
446        for other in &nodes {
447            if node != other {
448                if let Ok(Some(path)) = dijkstra_path(graph, node, other) {
449                    let distance: f64 = path.total_weight.into();
450                    total_distance += distance;
451                    reachable_count += 1;
452                }
453            }
454        }
455
456        if reachable_count > 0 {
457            let closeness = reachable_count as f64 / total_distance;
458            let value = if normalized && n > 1 {
459                closeness * (reachable_count as f64 / (n - 1) as f64)
460            } else {
461                closeness
462            };
463            centrality.insert(node.clone(), value);
464        } else {
465            centrality.insert(node.clone(), 0.0);
466        }
467    }
468
469    centrality
470}
471
472/// Eigenvector centrality
473///
474/// Computes the eigenvector centrality of nodes using power iteration.
475#[allow(dead_code)]
476pub fn eigenvector_centrality<N, E, Ix>(
477    graph: &Graph<N, E, Ix>,
478    max_iter: usize,
479    tolerance: f64,
480) -> Result<HashMap<N, f64>>
481where
482    N: Node + std::fmt::Debug,
483    E: EdgeWeight + Into<f64>,
484    Ix: IndexType,
485{
486    let node_indices: Vec<_> = graph.inner().node_indices().collect();
487    let nodes: Vec<N> = node_indices
488        .iter()
489        .map(|&idx| graph.inner()[idx].clone())
490        .collect();
491    let n = nodes.len();
492
493    if n == 0 {
494        return Ok(HashMap::new());
495    }
496
497    // Create adjacency matrix
498    let mut adj_matrix = Array2::<f64>::zeros((n, n));
499    for (i, node_i) in nodes.iter().enumerate() {
500        for (j, node_j) in nodes.iter().enumerate() {
501            if let Ok(weight) = graph.edge_weight(node_i, node_j) {
502                adj_matrix[[i, j]] = weight.into();
503            }
504        }
505    }
506
507    // Initialize eigenvector
508    let mut x = Array1::<f64>::from_elem(n, 1.0 / (n as f64).sqrt());
509    let mut converged = false;
510
511    // Power iteration
512    for _ in 0..max_iter {
513        let x_new = adj_matrix.dot(&x);
514
515        // Normalize
516        let norm = x_new.dot(&x_new).sqrt();
517        if norm == 0.0 {
518            return Err(GraphError::ComputationError(
519                "Eigenvector computation failed".to_string(),
520            ));
521        }
522
523        let x_normalized = x_new / norm;
524
525        // Check convergence
526        let diff = (&x_normalized - &x).mapv(f64::abs).sum();
527        if diff < tolerance {
528            converged = true;
529            x = x_normalized;
530            break;
531        }
532
533        x = x_normalized;
534    }
535
536    if !converged {
537        return Err(GraphError::ComputationError(
538            "Eigenvector centrality did not converge".to_string(),
539        ));
540    }
541
542    // Convert to HashMap
543    Ok(nodes
544        .into_iter()
545        .enumerate()
546        .map(|(i, node)| (node, x[i]))
547        .collect())
548}
549
550#[cfg(test)]
551mod tests {
552    use super::*;
553    use crate::generators::create_graph;
554
555    #[test]
556    fn test_minimum_spanning_tree() {
557        let mut graph = create_graph::<&str, f64>();
558        graph.add_edge("A", "B", 1.0).expect("Operation failed");
559        graph.add_edge("B", "C", 2.0).expect("Operation failed");
560        graph.add_edge("A", "C", 3.0).expect("Operation failed");
561        graph.add_edge("C", "D", 1.0).expect("Operation failed");
562
563        let mst = minimum_spanning_tree(&graph).expect("Operation failed");
564
565        // MST should have n-1 edges
566        assert_eq!(mst.len(), 3);
567
568        // Total weight should be 4.0 (AB: 1, BC: 2, CD: 1)
569        let total_weight: f64 = mst.iter().map(|e| e.weight).sum();
570        assert_eq!(total_weight, 4.0);
571    }
572
573    #[test]
574    fn test_topological_sort() {
575        let mut graph = crate::generators::create_digraph::<&str, ()>();
576        graph.add_edge("A", "B", ()).expect("Operation failed");
577        graph.add_edge("A", "C", ()).expect("Operation failed");
578        graph.add_edge("B", "D", ()).expect("Operation failed");
579        graph.add_edge("C", "D", ()).expect("Operation failed");
580
581        let sorted = topological_sort(&graph).expect("Operation failed");
582
583        // A should come before B and C
584        let a_pos = sorted
585            .iter()
586            .position(|n| n == &"A")
587            .expect("Operation failed");
588        let b_pos = sorted
589            .iter()
590            .position(|n| n == &"B")
591            .expect("Operation failed");
592        let c_pos = sorted
593            .iter()
594            .position(|n| n == &"C")
595            .expect("Operation failed");
596        let d_pos = sorted
597            .iter()
598            .position(|n| n == &"D")
599            .expect("Operation failed");
600
601        assert!(a_pos < b_pos);
602        assert!(a_pos < c_pos);
603        assert!(b_pos < d_pos);
604        assert!(c_pos < d_pos);
605    }
606
607    #[test]
608    fn test_pagerank() {
609        let mut graph = crate::generators::create_digraph::<&str, ()>();
610        graph.add_edge("A", "B", ()).expect("Operation failed");
611        graph.add_edge("A", "C", ()).expect("Operation failed");
612        graph.add_edge("B", "C", ()).expect("Operation failed");
613        graph.add_edge("C", "A", ()).expect("Operation failed");
614
615        let pr = pagerank(&graph, 0.85, 1e-6, 100);
616
617        // All nodes should have positive PageRank
618        assert!(pr.values().all(|&v| v > 0.0));
619
620        // Sum should be approximately 1.0
621        let sum: f64 = pr.values().sum();
622        assert!((sum - 1.0).abs() < 0.01);
623    }
624}