rustworkx_core/connectivity/chain.rs
1// Licensed under the Apache License, Version 2.0 (the "License"); you may
2// not use this file except in compliance with the License. You may obtain
3// a copy of the License at
4//
5// http://www.apache.org/licenses/LICENSE-2.0
6//
7// Unless required by applicable law or agreed to in writing, software
8// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10// License for the specific language governing permissions and limitations
11// under the License.
12
13use std::cmp::Eq;
14use std::hash::Hash;
15
16use hashbrown::HashMap;
17
18use petgraph::visit::{
19 GraphProp, IntoEdges, IntoNodeIdentifiers, NodeCount, NodeIndexable, VisitMap, Visitable,
20};
21use petgraph::Undirected;
22
23use crate::traversal::{depth_first_search, DfsEvent};
24
25fn _build_chain<G, VM: VisitMap<G::NodeId>>(
26 graph: G,
27 parent: &[usize],
28 mut u_id: G::NodeId,
29 mut v_id: G::NodeId,
30 visited: &mut VM,
31) -> Vec<(G::NodeId, G::NodeId)>
32where
33 G: Visitable + NodeIndexable,
34{
35 let mut chain = Vec::new();
36 while visited.visit(v_id) {
37 chain.push((u_id, v_id));
38 u_id = v_id;
39 let u = graph.to_index(u_id);
40 let v = parent[u];
41 v_id = graph.from_index(v);
42 }
43 chain.push((u_id, v_id));
44
45 chain
46}
47
48/// Returns the chain decomposition of a graph.
49///
50/// The *chain decomposition* of a graph with respect to a depth-first
51/// search tree is a set of cycles or paths derived from the set of
52/// fundamental cycles of the tree in the following manner. Consider
53/// each fundamental cycle with respect to the given tree, represented
54/// as a list of edges beginning with the nontree edge oriented away
55/// from the root of the tree. For each fundamental cycle, if it
56/// overlaps with any previous fundamental cycle, just take the initial
57/// non-overlapping segment, which is a path instead of a cycle. Each
58/// cycle or path is called a *chain*. For more information,
59/// see [`Schmidt`](https://doi.org/10.1016/j.ipl.2013.01.016).
60///
61/// The graph should be undirected. If `source` is specified only the chain
62/// decomposition for the connected component containing this node will be returned.
63/// This node indicates the root of the depth-first search tree. If it's not
64/// specified, a source will be chosen arbitrarily and repeated until all components
65/// of the graph are searched.
66///
67/// Returns a list of list of edges where each inner list is a chain.
68///
69/// # Note
70/// The function implicitly assumes that there are no parallel edges
71/// or self loops. It may produce incorrect/unexpected results if the
72/// input graph has self loops or parallel edges.
73///
74/// # Example
75/// ```rust
76/// use rustworkx_core::connectivity::chain_decomposition;
77/// use rustworkx_core::petgraph::graph::{NodeIndex, UnGraph};
78///
79/// let mut graph : UnGraph<(), ()> = UnGraph::new_undirected();
80/// let a = graph.add_node(()); // node with no weight
81/// let b = graph.add_node(());
82/// let c = graph.add_node(());
83/// let d = graph.add_node(());
84/// let e = graph.add_node(());
85/// let f = graph.add_node(());
86/// let g = graph.add_node(());
87/// let h = graph.add_node(());
88///
89/// graph.extend_with_edges(&[
90/// (a, b),
91/// (b, c),
92/// (c, d),
93/// (d, a),
94/// (e, f),
95/// (b, e),
96/// (f, g),
97/// (g, h),
98/// (h, e)
99/// ]);
100/// // a ---- b ---- e ---- f
101/// // | | | |
102/// // d ---- c h ---- g
103///
104/// let chains = chain_decomposition(&graph, None);
105/// assert_eq!(
106/// chains,
107/// vec![
108/// vec![(a, d), (d, c), (c, b), (b, a)],
109/// vec![(e, h), (h, g), (g, f), (f, e)]
110/// ]
111/// );
112/// ```
113pub fn chain_decomposition<G>(
114 graph: G,
115 source: Option<G::NodeId>,
116) -> Vec<Vec<(G::NodeId, G::NodeId)>>
117where
118 G: IntoNodeIdentifiers
119 + IntoEdges
120 + Visitable
121 + NodeIndexable
122 + NodeCount
123 + GraphProp<EdgeType = Undirected>,
124 G::NodeId: Eq + Hash,
125{
126 let roots = match source {
127 Some(node) => vec![node],
128 None => graph.node_identifiers().collect(),
129 };
130
131 let mut parent = vec![usize::MAX; graph.node_bound()];
132 let mut back_edges: HashMap<G::NodeId, Vec<G::NodeId>> = HashMap::new();
133
134 // depth-first-index (DFI) ordered nodes.
135 let mut nodes = Vec::with_capacity(graph.node_count());
136 depth_first_search(graph, roots, |event| match event {
137 DfsEvent::Discover(u, _) => {
138 nodes.push(u);
139 }
140 DfsEvent::TreeEdge(u, v, _) => {
141 let u = graph.to_index(u);
142 let v = graph.to_index(v);
143 parent[v] = u;
144 }
145 DfsEvent::BackEdge(u_id, v_id, _) => {
146 let u = graph.to_index(u_id);
147 let v = graph.to_index(v_id);
148
149 // do *not* consider ``(u, v)`` as a back edge if ``(v, u)`` is a tree edge.
150 if parent[u] != v {
151 back_edges
152 .entry(v_id)
153 .and_modify(|v_edges| v_edges.push(u_id))
154 .or_insert(vec![u_id]);
155 }
156 }
157 _ => {}
158 });
159
160 let visited = &mut graph.visit_map();
161 nodes
162 .into_iter()
163 .filter_map(|u| {
164 visited.visit(u);
165 back_edges.get(&u).map(|vs| {
166 vs.iter()
167 .map(|v| _build_chain(graph, &parent, u, *v, visited))
168 .collect::<Vec<Vec<(G::NodeId, G::NodeId)>>>()
169 })
170 })
171 .flatten()
172 .collect()
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178 use petgraph::graph::node_index as ni;
179 use petgraph::prelude::*;
180
181 #[test]
182 fn test_decomposition() {
183 let graph = UnGraph::<(), ()>::from_edges([
184 // DFS tree edges.
185 (1, 2),
186 (2, 3),
187 (3, 4),
188 (3, 5),
189 (5, 6),
190 (6, 7),
191 (7, 8),
192 (5, 9),
193 (9, 10),
194 // Nontree edges.
195 (1, 3),
196 (1, 4),
197 (2, 5),
198 (5, 10),
199 (6, 8),
200 ]);
201
202 let chains = chain_decomposition(&graph, Some(NodeIndex::new(1)));
203
204 let expected: Vec<Vec<(NodeIndex<usize>, NodeIndex<usize>)>> = vec![
205 vec![(ni(1), ni(3)), (ni(3), ni(2)), (ni(2), ni(1))],
206 vec![(ni(1), ni(4)), (ni(4), ni(3))],
207 vec![(ni(2), ni(5)), (ni(5), ni(3))],
208 vec![(ni(5), ni(10)), (ni(10), ni(9)), (ni(9), ni(5))],
209 vec![(ni(6), ni(8)), (ni(8), ni(7)), (ni(7), ni(6))],
210 ];
211 assert_eq!(chains.len(), expected.len());
212 }
213}