retworkx_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,
20    VisitMap, Visitable,
21};
22use petgraph::Undirected;
23
24use crate::traversal::{depth_first_search, DfsEvent};
25
26fn _build_chain<G, VM: VisitMap<G::NodeId>>(
27    graph: G,
28    parent: &[usize],
29    mut u_id: G::NodeId,
30    mut v_id: G::NodeId,
31    visited: &mut VM,
32) -> Vec<(G::NodeId, G::NodeId)>
33where
34    G: Visitable + NodeIndexable,
35{
36    let mut chain = Vec::new();
37    while visited.visit(v_id) {
38        chain.push((u_id, v_id));
39        u_id = v_id;
40        let u = graph.to_index(u_id);
41        let v = parent[u];
42        v_id = graph.from_index(v);
43    }
44    chain.push((u_id, v_id));
45
46    chain
47}
48
49/// Returns the chain decomposition of a graph.
50///
51/// The *chain decomposition* of a graph with respect to a depth-first
52/// search tree is a set of cycles or paths derived from the set of
53/// fundamental cycles of the tree in the following manner. Consider
54/// each fundamental cycle with respect to the given tree, represented
55/// as a list of edges beginning with the nontree edge oriented away
56/// from the root of the tree. For each fundamental cycle, if it
57/// overlaps with any previous fundamental cycle, just take the initial
58/// non-overlapping segment, which is a path instead of a cycle. Each
59/// cycle or path is called a *chain*. For more information,
60/// see [`Schmidt`](https://doi.org/10.1016/j.ipl.2013.01.016).
61///
62/// The graph should be undirected. If `source` is specified only the chain
63/// decomposition for the connected component containing this node will be returned.
64/// This node indicates the root of the depth-first search tree. If it's not
65/// specified, a source will be chosen arbitrarly and repeated until all components
66/// of the graph are searched.
67///
68/// Returns a list of list of edges where each inner list is a chain.
69///
70/// # Note
71/// The function implicitly assumes that there are no parallel edges
72/// or self loops. It may produce incorrect/unexpected results if the
73/// input graph has self loops or parallel edges.
74///
75/// # Example
76/// ```rust
77/// use retworkx_core::connectivity::chain_decomposition;
78/// use retworkx_core::petgraph::graph::{NodeIndex, UnGraph};
79///
80/// let mut graph : UnGraph<(), ()> = UnGraph::new_undirected();
81/// let a = graph.add_node(()); // node with no weight
82/// let b = graph.add_node(());
83/// let c = graph.add_node(());
84/// let d = graph.add_node(());
85/// let e = graph.add_node(());
86/// let f = graph.add_node(());
87/// let g = graph.add_node(());
88/// let h = graph.add_node(());
89///
90/// graph.extend_with_edges(&[
91///     (a, b),
92///     (b, c),
93///     (c, d),
94///     (d, a),
95///     (e, f),
96///     (b, e),
97///     (f, g),
98///     (g, h),
99///     (h, e)
100/// ]);
101/// // a ---- b ---- e ---- f
102/// // |      |      |      |
103/// // d ---- c      h ---- g
104///
105/// let chains = chain_decomposition(&graph, None);
106/// assert_eq!(
107///     chains,
108///     vec![
109///         vec![(a, d), (d, c), (c, b), (b, a)],
110///         vec![(e, h), (h, g), (g, f), (f, e)]
111///     ]
112/// );
113/// ```
114pub fn chain_decomposition<G>(
115    graph: G,
116    source: Option<G::NodeId>,
117) -> Vec<Vec<(G::NodeId, G::NodeId)>>
118where
119    G: IntoNodeIdentifiers
120        + IntoEdges
121        + Visitable
122        + NodeIndexable
123        + NodeCount
124        + GraphProp<EdgeType = Undirected>,
125    G::NodeId: Eq + Hash,
126{
127    let roots = match source {
128        Some(node) => vec![node],
129        None => graph.node_identifiers().collect(),
130    };
131
132    let mut parent = vec![std::usize::MAX; graph.node_bound()];
133    let mut back_edges: HashMap<G::NodeId, Vec<G::NodeId>> = HashMap::new();
134
135    // depth-first-index (DFI) ordered nodes.
136    let mut nodes = Vec::with_capacity(graph.node_count());
137    depth_first_search(graph, roots, |event| match event {
138        DfsEvent::Discover(u, _) => {
139            nodes.push(u);
140        }
141        DfsEvent::TreeEdge(u, v, _) => {
142            let u = graph.to_index(u);
143            let v = graph.to_index(v);
144            parent[v] = u;
145        }
146        DfsEvent::BackEdge(u_id, v_id, _) => {
147            let u = graph.to_index(u_id);
148            let v = graph.to_index(v_id);
149
150            // do *not* consider ``(u, v)`` as a back edge if ``(v, u)`` is a tree edge.
151            if parent[u] != v {
152                back_edges
153                    .entry(v_id)
154                    .and_modify(|v_edges| v_edges.push(u_id))
155                    .or_insert(vec![u_id]);
156            }
157        }
158        _ => {}
159    });
160
161    let visited = &mut graph.visit_map();
162    nodes
163        .into_iter()
164        .filter_map(|u| {
165            visited.visit(u);
166            back_edges.get(&u).map(|vs| {
167                vs.iter()
168                    .map(|v| _build_chain(graph, &parent, u, *v, visited))
169                    .collect::<Vec<Vec<(G::NodeId, G::NodeId)>>>()
170            })
171        })
172        .flatten()
173        .collect()
174}