petgraph/algo/
tred.rs

1//! Compute the transitive reduction and closure of a directed acyclic graph
2//!
3//! ## Transitive reduction and closure
4//! The *transitive closure* of a graph **G = (V, E)** is the graph **Gc = (V, Ec)**
5//! such that **(i, j)** belongs to **Ec** if and only if there is a path connecting
6//! **i** to **j** in **G**. The *transitive reduction* of **G** is the graph **Gr
7//! = (V, Er)** such that **Er** is minimal wrt. inclusion in **E** and the transitive
8//! closure of **Gr** is the same as that of **G**.
9//! The transitive reduction is well-defined for acyclic graphs only.
10
11use alloc::{vec, vec::Vec};
12
13use fixedbitset::FixedBitSet;
14
15use crate::adj::{List, UnweightedList};
16use crate::graph::IndexType;
17use crate::visit::{
18    GraphBase, IntoNeighbors, IntoNeighborsDirected, NodeCompactIndexable, NodeCount,
19};
20use crate::Direction;
21
22/// Creates a representation of the same graph respecting topological order for use in `tred::dag_transitive_reduction_closure`.
23///
24/// `toposort` must be a topological order on the node indices of `g` (for example obtained
25/// from [`toposort`]).
26///
27/// [`toposort`]: ../fn.toposort.html
28///
29/// Returns a pair of a graph `res` and the reciprocal of the topological sort `revmap`.
30///
31/// `res` is the same graph as `g` with the following differences:
32/// * Node and edge weights are stripped,
33/// * Node indices are replaced by the corresponding rank in `toposort`,
34/// * Iterating on the neighbors of a node respects topological order.
35///
36/// `revmap` is handy to get back to map indices in `g` to indices in `res`.
37/// ```
38/// use petgraph::prelude::*;
39/// use petgraph::graph::DefaultIx;
40/// use petgraph::visit::IntoNeighbors;
41/// use petgraph::algo::tred::dag_to_toposorted_adjacency_list;
42///
43/// let mut g = Graph::<&str, (), Directed, DefaultIx>::new();
44/// let second = g.add_node("second child");
45/// let top = g.add_node("top");
46/// let first = g.add_node("first child");
47/// g.extend_with_edges(&[(top, second), (top, first), (first, second)]);
48///
49/// let toposort = vec![top, first, second];
50///
51/// let (res, revmap) = dag_to_toposorted_adjacency_list(&g, &toposort);
52///
53/// // let's compute the children of top in topological order
54/// let children: Vec<NodeIndex> = res
55///     .neighbors(revmap[top.index()])
56///     .map(|ix: NodeIndex| toposort[ix.index()])
57///     .collect();
58/// assert_eq!(children, vec![first, second])
59/// ```
60///
61/// Runtime: **O(|V| + |E|)**.
62///
63/// Space complexity: **O(|V| + |E|)**.
64pub fn dag_to_toposorted_adjacency_list<G, Ix: IndexType>(
65    g: G,
66    toposort: &[G::NodeId],
67) -> (UnweightedList<Ix>, Vec<Ix>)
68where
69    G: GraphBase + IntoNeighborsDirected + NodeCompactIndexable + NodeCount,
70    G::NodeId: IndexType,
71{
72    let mut res = List::with_capacity(g.node_count());
73    // map from old node index to rank in toposort
74    let mut revmap = vec![Ix::default(); g.node_bound()];
75    for (ix, &old_ix) in toposort.iter().enumerate() {
76        let ix = Ix::new(ix);
77        revmap[old_ix.index()] = ix;
78        let iter = g.neighbors_directed(old_ix, Direction::Incoming);
79        let new_ix: Ix = res.add_node_with_capacity(iter.size_hint().0);
80        debug_assert_eq!(new_ix.index(), ix.index());
81        for old_pre in iter {
82            let pre: Ix = revmap[old_pre.index()];
83            res.add_edge(pre, ix, ());
84        }
85    }
86    (res, revmap)
87}
88
89/// Computes the transitive reduction and closure of a DAG.
90///
91/// The algorithm implemented here comes from [On the calculation of
92/// transitive reduction-closure of
93/// orders](https://www.sciencedirect.com/science/article/pii/0012365X9390164O) by Habib, Morvan
94/// and Rampon.
95///
96/// The input graph must be in a very specific format: an adjacency
97/// list such that:
98/// * Node indices are a toposort, and
99/// * The neighbors of all nodes are stored in topological order.
100///
101/// To get such a representation, use the function [`dag_to_toposorted_adjacency_list`].
102///
103/// [`dag_to_toposorted_adjacency_list`]: ./fn.dag_to_toposorted_adjacency_list.html
104///
105/// The output is the pair of the transitive reduction and the transitive closure.
106///
107/// Runtime complexity: **O(|V| + \sum_{(x, y) \in Er} d(y))** where **d(y)**
108/// denotes the outgoing degree of **y** in the transitive closure of **G**.
109/// This is still **O(|V|³)** in the worst case like the naive algorithm but
110/// should perform better for some classes of graphs.
111///
112/// Space complexity: **O(|E|)**.
113pub fn dag_transitive_reduction_closure<E, Ix: IndexType>(
114    g: &List<E, Ix>,
115) -> (UnweightedList<Ix>, UnweightedList<Ix>) {
116    let mut tred = List::with_capacity(g.node_count());
117    let mut tclos = List::with_capacity(g.node_count());
118    let mut mark = FixedBitSet::with_capacity(g.node_count());
119    for i in g.node_indices() {
120        tred.add_node();
121        tclos.add_node_with_capacity(g.neighbors(i).len());
122    }
123    // the algorithm relies on this iterator being toposorted
124    for i in g.node_indices().rev() {
125        // the algorighm relies on this iterator being toposorted
126        for x in g.neighbors(i) {
127            if !mark[x.index()] {
128                tred.add_edge(i, x, ());
129                tclos.add_edge(i, x, ());
130                for e in tclos.edge_indices_from(x) {
131                    let y = tclos.edge_endpoints(e).unwrap().1;
132                    if !mark[y.index()] {
133                        mark.insert(y.index());
134                        tclos.add_edge(i, y, ());
135                    }
136                }
137            }
138        }
139        for y in tclos.neighbors(i) {
140            mark.set(y.index(), false);
141        }
142    }
143    (tred, tclos)
144}
145
146#[cfg(test)]
147#[test]
148fn test_easy_tred() {
149    let mut input = List::new();
150    let a: u8 = input.add_node();
151    let b = input.add_node();
152    let c = input.add_node();
153    input.add_edge(a, b, ());
154    input.add_edge(a, c, ());
155    input.add_edge(b, c, ());
156    let (tred, tclos) = dag_transitive_reduction_closure(&input);
157    assert_eq!(tred.node_count(), 3);
158    assert_eq!(tclos.node_count(), 3);
159    assert!(tred.find_edge(a, b).is_some());
160    assert!(tred.find_edge(b, c).is_some());
161    assert!(tred.find_edge(a, c).is_none());
162    assert!(tclos.find_edge(a, b).is_some());
163    assert!(tclos.find_edge(b, c).is_some());
164    assert!(tclos.find_edge(a, c).is_some());
165}