graph_advanced/
condensation.rs

1use crate::tarjan_scc;
2use graph_core::{Graph, NodeId};
3use std::collections::HashMap;
4
5/// The result of condensing a directed graph into its SCC DAG.
6///
7/// Each node in the condensed graph represents one SCC of the original graph.
8/// The condensed graph is always a DAG (directed acyclic graph).
9#[derive(Debug, Clone)]
10pub struct CondensedGraph {
11    /// `components[i]` is the list of original [`NodeId`]s that belong to
12    /// super-node `i` in the condensed graph.
13    pub components: Vec<Vec<NodeId>>,
14    /// Edges of the condensed DAG.
15    ///
16    /// `edges[i]` is the list of super-node indices that super-node `i` has
17    /// a directed edge to. Self-loops (SCC to itself) are not included.
18    pub edges: Vec<Vec<usize>>,
19}
20
21impl CondensedGraph {
22    /// Returns the number of super-nodes (SCCs) in the condensed graph.
23    #[inline]
24    pub fn node_count(&self) -> usize {
25        self.components.len()
26    }
27}
28
29/// Condenses a directed graph into its **SCC DAG**.
30///
31/// Each strongly connected component (SCC) is collapsed into a single
32/// super-node. The resulting graph is always a DAG — any cycle that existed
33/// in the original graph is now internal to a single super-node.
34///
35/// Uses [`tarjan_scc()`] internally to compute the SCCs.
36///
37/// # Returns
38///
39/// A [`CondensedGraph`] where:
40/// - `components[i]` lists the original nodes that form super-node `i`.
41/// - `edges[i]` lists which super-nodes super-node `i` has edges to.
42///
43/// # Complexity
44///
45/// O(V + E) — dominated by the SCC computation.
46///
47/// # Examples
48///
49/// ```
50/// use graph_core::{AdjacencyList, Graph};
51/// use graph_advanced::condensation;
52///
53/// // 0 ↔ 1  (one SCC),  2 is a singleton,  edge 0→2.
54/// let mut g: AdjacencyList<()> = AdjacencyList::directed();
55/// let n: Vec<_> = (0..3).map(|_| g.add_node(())).collect();
56/// g.add_edge(n[0], n[1], 1.0).unwrap();
57/// g.add_edge(n[1], n[0], 1.0).unwrap();
58/// g.add_edge(n[0], n[2], 1.0).unwrap();
59///
60/// let cg = condensation(&g);
61/// assert_eq!(cg.node_count(), 2); // two SCCs
62/// // There must be exactly one inter-SCC edge.
63/// let total_edges: usize = cg.edges.iter().map(|e| e.len()).sum();
64/// assert_eq!(total_edges, 1);
65/// ```
66pub fn condensation<G>(graph: &G) -> CondensedGraph
67where
68    G: Graph<Weight = f64>,
69{
70    let sccs = tarjan_scc(graph);
71
72    // Map every original NodeId to its SCC index.
73    let mut node_to_scc: HashMap<NodeId, usize> = HashMap::new();
74    for (scc_idx, scc) in sccs.iter().enumerate() {
75        for &node in scc {
76            node_to_scc.insert(node, scc_idx);
77        }
78    }
79
80    let num_sccs = sccs.len();
81    let mut edge_set: Vec<std::collections::HashSet<usize>> =
82        vec![std::collections::HashSet::new(); num_sccs];
83
84    for node in graph.nodes() {
85        let from_scc = node_to_scc[&node];
86        for (neighbour, _) in graph.neighbors(node) {
87            let to_scc = node_to_scc[&neighbour];
88            if from_scc != to_scc {
89                edge_set[from_scc].insert(to_scc);
90            }
91        }
92    }
93
94    let edges: Vec<Vec<usize>> = edge_set
95        .into_iter()
96        .map(|s| s.into_iter().collect())
97        .collect();
98
99    CondensedGraph {
100        components: sccs,
101        edges,
102    }
103}