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