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}