From eabb624c16e06e12922e1c459ab8518a36e65874 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Wed, 25 Jan 2023 07:53:28 -0800 Subject: [PATCH 1/3] Initial cycle basis --- .../src/connectivity/cycle_basis.rs | 466 ++++++++++++++++++ 1 file changed, 466 insertions(+) create mode 100644 rustworkx-core/src/connectivity/cycle_basis.rs diff --git a/rustworkx-core/src/connectivity/cycle_basis.rs b/rustworkx-core/src/connectivity/cycle_basis.rs new file mode 100644 index 000000000..a825256be --- /dev/null +++ b/rustworkx-core/src/connectivity/cycle_basis.rs @@ -0,0 +1,466 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +// This module was forked from petgraph: +// +// https://github.com/petgraph/petgraph/blob/9ff688872b467d3e1b5adef19f5c52f519d3279c/src/algo/simple_paths.rs +// +// to add support for returning all simple paths to a list of targets instead +// of just between a single node pair. + +use hashbrown::HashSet; +use indexmap::map::Entry; +use indexmap::IndexSet; +use petgraph::visit::{IntoNeighborsDirected, NodeCount}; +use petgraph::Direction::Outgoing; +use std::iter; +use std::{hash::Hash, iter::FromIterator}; + +use crate::dictmap::*; + +/// Return a list of cycles which form a basis for cycles of a given PyGraph +/// +/// A basis for cycles of a graph is a minimal collection of +/// cycles such that any cycle in the graph can be written +/// as a sum of cycles in the basis. Here summation of cycles +/// is defined as the exclusive or of the edges. +/// +/// This is adapted from algorithm CACM 491 [1]_. +/// +/// .. note:: +/// +/// The function implicitly assumes that there are no parallel edges. +/// It may produce incorrect/unexpected results if the input graph has +/// parallel edges. +/// +/// :param PyGraph graph: The graph to find the cycle basis in +/// :param int root: Optional index for starting node for basis +/// +/// :returns: A list of cycle lists. Each list is a list of node ids which +/// forms a cycle (loop) in the input graph +/// :rtype: list +/// +/// .. [1] Paton, K. An algorithm for finding a fundamental set of +/// cycles of a graph. Comm. ACM 12, 9 (Sept 1969), 514-518. + +/// Returns a dictionary with all simple paths from `from` node to all nodes in `to`, which contains at least `min_intermediate_nodes` nodes +/// and at most `max_intermediate_nodes`, if given, or limited by the graph's order otherwise. The simple path is a path without repetitions. +/// +/// This algorithm is adapted from . +/// +/// # Example +/// ``` +/// use petgraph::prelude::*; +/// use hashbrown::HashSet; +/// use rustworkx_core::connectivity::all_simple_paths_multiple_targets; +/// +/// let mut graph = DiGraph::<&str, i32>::new(); +/// +/// let a = graph.add_node("a"); +/// let b = graph.add_node("b"); +/// let c = graph.add_node("c"); +/// let d = graph.add_node("d"); +/// +/// graph.extend_with_edges(&[(a, b, 1), (b, c, 1), (c, d, 1), (a, b, 1), (b, d, 1)]); +/// +/// let mut to_set = HashSet::new(); +/// to_set.insert(d); +/// +/// let ways = all_simple_paths_multiple_targets(&graph, a, &to_set, 0, None); +/// +/// let d_path = ways.get(&d).unwrap(); +/// assert_eq!(4, d_path.len()); +/// ``` +pub fn cycle_basis( + graph: G, + root: Option(G::NodeId), +) -> Vec> +where + G: NodeCount, + G: IntoNeighbors, + G::NodeId: Eq + Hash, +{ + let mut root_node = root; + let mut graph_nodes: HashSet = graph.graph.node_indices().collect(); + let mut cycles: Vec> = Vec::new(); + while !graph_nodes.is_empty() { + let temp_value: NodeIndex; + // If root_node is not set get an arbitrary node from the set of graph + // nodes we've not "examined" + let root_index = match root_node { + Some(root_value) => NodeIndex::new(root_value), + None => { + temp_value = *graph_nodes.iter().next().unwrap(); + graph_nodes.remove(&temp_value); + temp_value + } + }; + // Stack (ie "pushdown list") of vertices already in the spanning tree + let mut stack: Vec = vec![root_index]; + // Map of node index to predecessor node index + let mut pred: HashMap = HashMap::new(); + pred.insert(root_index, root_index); + // Set of examined nodes during this iteration + let mut used: HashMap> = HashMap::new(); + used.insert(root_index, HashSet::new()); + // Walk the spanning tree + while !stack.is_empty() { + // Use the last element added so that cycles are easier to find + let z = stack.pop().unwrap(); + for neighbor in graph.graph.neighbors(z) { + // A new node was encountered: + if !used.contains_key(&neighbor) { + pred.insert(neighbor, z); + stack.push(neighbor); + let mut temp_set: HashSet = HashSet::new(); + temp_set.insert(z); + used.insert(neighbor, temp_set); + // A self loop: + } else if z == neighbor { + let cycle: Vec = vec![z.index()]; + cycles.push(cycle); + // A cycle was found: + } else if !used.get(&z).unwrap().contains(&neighbor) { + let pn = used.get(&neighbor).unwrap(); + let mut cycle: Vec = vec![neighbor, z]; + let mut p = pred.get(&z).unwrap(); + while !pn.contains(p) { + cycle.push(*p); + p = pred.get(p).unwrap(); + } + cycle.push(*p); + cycles.push(cycle.iter().map(|x| x.index()).collect()); + let neighbor_set = used.get_mut(&neighbor).unwrap(); + neighbor_set.insert(z); + } + } + } + let mut temp_hashset: HashSet = HashSet::new(); + for key in pred.keys() { + temp_hashset.insert(*key); + } + graph_nodes = graph_nodes.difference(&temp_hashset).copied().collect(); + root_node = None; + } + cycles +} +#[cfg(test)] +mod tests { + use crate::connectivity::all_simple_paths_multiple_targets; + use hashbrown::HashSet; + use petgraph::prelude::*; + + #[test] + fn test_all_simple_paths() { + // create a path graph + let mut graph = Graph::new_undirected(); + let a = graph.add_node(0); + let b = graph.add_node(1); + let c = graph.add_node(2); + let d = graph.add_node(3); + let e = graph.add_node(4); + + graph.extend_with_edges(&[(a, b, 1), (b, c, 1), (c, d, 1), (d, e, 1)]); + + let mut to_set = HashSet::new(); + to_set.insert(d); + + let paths = all_simple_paths_multiple_targets(&graph, a, &to_set, 0, None); + + assert_eq!(paths.get(&d).unwrap(), &vec![vec![a, b, c, d]]); + } + + #[test] + fn test_all_simple_paths_with_two_targets_emits_two_paths() { + // create a path graph + let mut graph = Graph::new_undirected(); + let a = graph.add_node(0); + let b = graph.add_node(1); + let c = graph.add_node(2); + let d = graph.add_node(3); + let e = graph.add_node(4); + + graph.extend_with_edges(&[(a, b, 1), (b, c, 1), (c, d, 1), (d, e, 1), (c, e, 1)]); + + let mut to_set = HashSet::new(); + to_set.insert(d); + to_set.insert(e); + + let paths = all_simple_paths_multiple_targets(&graph, a, &to_set, 0, None); + + assert_eq!( + paths.get(&d).unwrap(), + &vec![vec![a, b, c, e, d], vec![a, b, c, d]] + ); + assert_eq!( + paths.get(&e).unwrap(), + &vec![vec![a, b, c, e], vec![a, b, c, d, e]] + ); + } + + #[test] + fn test_digraph_all_simple_paths_with_two_targets_emits_two_paths() { + // create a path graph + let mut graph = Graph::new(); + let a = graph.add_node(0); + let b = graph.add_node(1); + let c = graph.add_node(2); + let d = graph.add_node(3); + let e = graph.add_node(4); + + graph.extend_with_edges(&[(a, b, 1), (b, c, 1), (c, d, 1), (d, e, 1), (c, e, 1)]); + + let mut to_set = HashSet::new(); + to_set.insert(d); + to_set.insert(e); + + let paths = all_simple_paths_multiple_targets(&graph, a, &to_set, 0, None); + + assert_eq!(paths.get(&d).unwrap(), &vec![vec![a, b, c, d]]); + assert_eq!( + paths.get(&e).unwrap(), + &vec![vec![a, b, c, e], vec![a, b, c, d, e]] + ); + } + + #[test] + fn test_all_simple_paths_max_nodes() { + // create a complete graph + let mut graph = Graph::new_undirected(); + let a = graph.add_node(0); + let b = graph.add_node(1); + let c = graph.add_node(2); + let d = graph.add_node(3); + let e = graph.add_node(4); + + graph.extend_with_edges(&[ + (a, b, 1), + (a, c, 1), + (a, d, 1), + (a, e, 1), + (b, c, 1), + (b, d, 1), + (b, e, 1), + (c, d, 1), + (c, e, 1), + (d, e, 1), + ]); + + let mut to_set = HashSet::new(); + to_set.insert(b); + + let paths = all_simple_paths_multiple_targets(&graph, a, &to_set, 0, Some(0)); + + assert_eq!(paths.get(&b).unwrap(), &vec![vec![a, b]]); + + let paths = all_simple_paths_multiple_targets(&graph, a, &to_set, 0, Some(1)); + + assert_eq!( + paths.get(&b).unwrap(), + &vec![vec![a, e, b], vec![a, d, b], vec![a, c, b], vec![a, b],] + ); + } + + #[test] + fn test_all_simple_paths_with_two_targets_max_nodes() { + // create a path graph + let mut graph = Graph::new_undirected(); + let a = graph.add_node(0); + let b = graph.add_node(1); + let c = graph.add_node(2); + let d = graph.add_node(3); + let e = graph.add_node(4); + + graph.extend_with_edges(&[(a, b, 1), (b, c, 1), (c, d, 1), (d, e, 1), (c, e, 1)]); + + let mut to_set = HashSet::new(); + to_set.insert(d); + to_set.insert(e); + + let paths = all_simple_paths_multiple_targets(&graph, a, &to_set, 0, Some(2)); + + assert_eq!(paths.get(&d).unwrap(), &vec![vec![a, b, c, d]]); + assert_eq!(paths.get(&e).unwrap(), &vec![vec![a, b, c, e]]); + } + + #[test] + fn test_digraph_all_simple_paths_with_two_targets_max_nodes() { + // create a path graph + let mut graph = Graph::new(); + let a = graph.add_node(0); + let b = graph.add_node(1); + let c = graph.add_node(2); + let d = graph.add_node(3); + let e = graph.add_node(4); + + graph.extend_with_edges(&[(a, b, 1), (b, c, 1), (c, d, 1), (d, e, 1), (c, e, 1)]); + + let mut to_set = HashSet::new(); + to_set.insert(d); + to_set.insert(e); + + let paths = all_simple_paths_multiple_targets(&graph, a, &to_set, 0, Some(2)); + + assert_eq!(paths.get(&d).unwrap(), &vec![vec![a, b, c, d]]); + assert_eq!(paths.get(&e).unwrap(), &vec![vec![a, b, c, e]]); + } + + #[test] + fn test_all_simple_paths_with_two_targets_in_line_emits_two_paths() { + // create a path graph + let mut graph = Graph::new_undirected(); + let a = graph.add_node(0); + let b = graph.add_node(1); + let c = graph.add_node(2); + let d = graph.add_node(3); + let e = graph.add_node(4); + + graph.extend_with_edges(&[(a, b, 1), (b, c, 1), (c, d, 1), (d, e, 1)]); + + let mut to_set = HashSet::new(); + to_set.insert(c); + to_set.insert(d); + + let paths = all_simple_paths_multiple_targets(&graph, a, &to_set, 0, None); + + assert_eq!(paths.get(&c).unwrap(), &vec![vec![a, b, c]]); + assert_eq!(paths.get(&d).unwrap(), &vec![vec![a, b, c, d]]); + } + + #[test] + fn test_all_simple_paths_min_nodes() { + // create a cycle graph + let mut graph = Graph::new(); + let a = graph.add_node(0); + let b = graph.add_node(1); + let c = graph.add_node(2); + let d = graph.add_node(3); + + graph.extend_with_edges(&[(a, b, 1), (b, c, 1), (c, d, 1), (d, a, 1), (b, d, 1)]); + + let mut to_set = HashSet::new(); + to_set.insert(d); + + let paths = all_simple_paths_multiple_targets(&graph, a, &to_set, 2, None); + + assert_eq!(paths.get(&d).unwrap(), &vec![vec![a, b, c, d]]); + } + + #[test] + fn test_all_simple_paths_with_two_targets_min_nodes() { + // create a cycle graph + let mut graph = Graph::new(); + let a = graph.add_node(0); + let b = graph.add_node(1); + let c = graph.add_node(2); + let d = graph.add_node(3); + + graph.extend_with_edges(&[(a, b, 1), (b, c, 1), (c, d, 1), (d, a, 1), (b, d, 1)]); + + let mut to_set = HashSet::new(); + to_set.insert(c); + to_set.insert(d); + + let paths = all_simple_paths_multiple_targets(&graph, a, &to_set, 2, None); + + assert_eq!(paths.get(&c), None); + assert_eq!(paths.get(&d).unwrap(), &vec![vec![a, b, c, d]]); + } + + #[test] + fn test_all_simple_paths_source_target() { + // create a path graph + let mut graph = Graph::new_undirected(); + let a = graph.add_node(0); + let b = graph.add_node(1); + let c = graph.add_node(2); + let d = graph.add_node(3); + let e = graph.add_node(4); + + graph.extend_with_edges(&[(a, b, 1), (b, c, 1), (c, d, 1), (d, e, 1)]); + + let mut to_set = HashSet::new(); + to_set.insert(a); + + let paths = all_simple_paths_multiple_targets(&graph, a, &to_set, 0, None); + + assert_eq!(paths.get(&a), None); + } + + #[test] + fn test_all_simple_paths_on_non_trivial_graph() { + // create a path graph + let mut graph = Graph::new(); + let a = graph.add_node(0); + let b = graph.add_node(1); + let c = graph.add_node(2); + let d = graph.add_node(3); + let e = graph.add_node(4); + let f = graph.add_node(5); + + graph.extend_with_edges(&[ + (a, b, 1), + (b, c, 1), + (c, d, 1), + (d, e, 1), + (e, f, 1), + (a, f, 1), + (b, f, 1), + (b, d, 1), + (f, e, 1), + (e, c, 1), + (e, d, 1), + ]); + + let mut to_set = HashSet::new(); + to_set.insert(c); + to_set.insert(d); + + let paths = all_simple_paths_multiple_targets(&graph, b, &to_set, 0, None); + + assert_eq!( + paths.get(&c).unwrap(), + &vec![vec![b, d, e, c], vec![b, f, e, c], vec![b, c]] + ); + assert_eq!( + paths.get(&d).unwrap(), + &vec![ + vec![b, d], + vec![b, f, e, d], + vec![b, f, e, c, d], + vec![b, c, d] + ] + ); + + let paths = all_simple_paths_multiple_targets(&graph, b, &to_set, 1, None); + + assert_eq!( + paths.get(&c).unwrap(), + &vec![vec![b, d, e, c], vec![b, f, e, c]] + ); + assert_eq!( + paths.get(&d).unwrap(), + &vec![vec![b, f, e, d], vec![b, f, e, c, d], vec![b, c, d]] + ); + + let paths = all_simple_paths_multiple_targets(&graph, b, &to_set, 0, Some(2)); + + assert_eq!( + paths.get(&c).unwrap(), + &vec![vec![b, d, e, c], vec![b, f, e, c], vec![b, c]] + ); + assert_eq!( + paths.get(&d).unwrap(), + &vec![vec![b, d], vec![b, f, e, d], vec![b, c, d]] + ); + } +} From 2fa1f6f1f3fb08fa7c8c2c0c4baaf674cf2cca25 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Thu, 26 Jan 2023 08:28:41 -0800 Subject: [PATCH 2/3] Finish cycle_basis tests etc --- .../src/connectivity/cycle_basis.rs | 463 ++++-------------- rustworkx-core/src/connectivity/mod.rs | 2 + src/connectivity/mod.rs | 67 +-- 3 files changed, 100 insertions(+), 432 deletions(-) diff --git a/rustworkx-core/src/connectivity/cycle_basis.rs b/rustworkx-core/src/connectivity/cycle_basis.rs index a825256be..49b8ca663 100644 --- a/rustworkx-core/src/connectivity/cycle_basis.rs +++ b/rustworkx-core/src/connectivity/cycle_basis.rs @@ -10,94 +10,57 @@ // License for the specific language governing permissions and limitations // under the License. -// This module was forked from petgraph: -// -// https://github.com/petgraph/petgraph/blob/9ff688872b467d3e1b5adef19f5c52f519d3279c/src/algo/simple_paths.rs -// -// to add support for returning all simple paths to a list of targets instead -// of just between a single node pair. - -use hashbrown::HashSet; -use indexmap::map::Entry; -use indexmap::IndexSet; -use petgraph::visit::{IntoNeighborsDirected, NodeCount}; -use petgraph::Direction::Outgoing; -use std::iter; -use std::{hash::Hash, iter::FromIterator}; - -use crate::dictmap::*; +use hashbrown::{HashMap, HashSet}; +use petgraph::visit::{IntoNeighbors, IntoNodeIdentifiers, NodeCount}; +use std::hash::Hash; -/// Return a list of cycles which form a basis for cycles of a given PyGraph +/// Return a list of cycles which form a basis for cycles of a given graph. /// /// A basis for cycles of a graph is a minimal collection of /// cycles such that any cycle in the graph can be written /// as a sum of cycles in the basis. Here summation of cycles -/// is defined as the exclusive or of the edges. +/// is defined as the exclusive-or of the edges. /// -/// This is adapted from algorithm CACM 491 [1]_. -/// -/// .. note:: -/// -/// The function implicitly assumes that there are no parallel edges. -/// It may produce incorrect/unexpected results if the input graph has -/// parallel edges. +/// This is adapted from +/// Paton, K. An algorithm for finding a fundamental set of +/// cycles of a graph. Comm. ACM 12, 9 (Sept 1969), 514-518. /// -/// :param PyGraph graph: The graph to find the cycle basis in -/// :param int root: Optional index for starting node for basis +/// The function implicitly assumes that there are no parallel edges. +/// It may produce incorrect/unexpected results if the input graph has +/// parallel edges. /// -/// :returns: A list of cycle lists. Each list is a list of node ids which -/// forms a cycle (loop) in the input graph -/// :rtype: list /// -/// .. [1] Paton, K. An algorithm for finding a fundamental set of -/// cycles of a graph. Comm. ACM 12, 9 (Sept 1969), 514-518. - -/// Returns a dictionary with all simple paths from `from` node to all nodes in `to`, which contains at least `min_intermediate_nodes` nodes -/// and at most `max_intermediate_nodes`, if given, or limited by the graph's order otherwise. The simple path is a path without repetitions. +/// Arguments: /// -/// This algorithm is adapted from . +/// * `graph` - The graph in which to find the basis. +/// * `root` - Optional node index for starting the basis search. If not +/// specified, an arbitrary node is chosen. /// /// # Example -/// ``` +/// ```rust /// use petgraph::prelude::*; -/// use hashbrown::HashSet; -/// use rustworkx_core::connectivity::all_simple_paths_multiple_targets; -/// -/// let mut graph = DiGraph::<&str, i32>::new(); -/// -/// let a = graph.add_node("a"); -/// let b = graph.add_node("b"); -/// let c = graph.add_node("c"); -/// let d = graph.add_node("d"); -/// -/// graph.extend_with_edges(&[(a, b, 1), (b, c, 1), (c, d, 1), (a, b, 1), (b, d, 1)]); -/// -/// let mut to_set = HashSet::new(); -/// to_set.insert(d); +/// use rustworkx_core::connectivity::cycle_basis; /// -/// let ways = all_simple_paths_multiple_targets(&graph, a, &to_set, 0, None); -/// -/// let d_path = ways.get(&d).unwrap(); -/// assert_eq!(4, d_path.len()); +/// let edge_list = [(0, 1), (0, 3), (0, 5), (1, 2), (2, 3), (3, 4), (4, 5)]; +/// let graph = UnGraph::::from_edges(&edge_list); +/// let mut res: Vec> = cycle_basis(&graph, Some(NodeIndex::new(0))); /// ``` -pub fn cycle_basis( - graph: G, - root: Option(G::NodeId), -) -> Vec> +pub fn cycle_basis(graph: G, root: Option) -> Vec> where G: NodeCount, G: IntoNeighbors, + G: IntoNodeIdentifiers, G::NodeId: Eq + Hash, { let mut root_node = root; - let mut graph_nodes: HashSet = graph.graph.node_indices().collect(); - let mut cycles: Vec> = Vec::new(); + let mut graph_nodes: HashSet = graph.node_identifiers().collect(); + let mut cycles: Vec> = Vec::new(); while !graph_nodes.is_empty() { - let temp_value: NodeIndex; + let temp_value: G::NodeId; // If root_node is not set get an arbitrary node from the set of graph // nodes we've not "examined" let root_index = match root_node { - Some(root_value) => NodeIndex::new(root_value), + Some(root_node) => root_node, None => { temp_value = *graph_nodes.iter().next().unwrap(); graph_nodes.remove(&temp_value); @@ -105,46 +68,46 @@ where } }; // Stack (ie "pushdown list") of vertices already in the spanning tree - let mut stack: Vec = vec![root_index]; + let mut stack: Vec = vec![root_index]; // Map of node index to predecessor node index - let mut pred: HashMap = HashMap::new(); + let mut pred: HashMap = HashMap::new(); pred.insert(root_index, root_index); // Set of examined nodes during this iteration - let mut used: HashMap> = HashMap::new(); + let mut used: HashMap> = HashMap::new(); used.insert(root_index, HashSet::new()); // Walk the spanning tree while !stack.is_empty() { // Use the last element added so that cycles are easier to find let z = stack.pop().unwrap(); - for neighbor in graph.graph.neighbors(z) { + for neighbor in graph.neighbors(z) { // A new node was encountered: if !used.contains_key(&neighbor) { pred.insert(neighbor, z); stack.push(neighbor); - let mut temp_set: HashSet = HashSet::new(); + let mut temp_set: HashSet = HashSet::new(); temp_set.insert(z); used.insert(neighbor, temp_set); // A self loop: } else if z == neighbor { - let cycle: Vec = vec![z.index()]; + let cycle: Vec = vec![z]; cycles.push(cycle); // A cycle was found: } else if !used.get(&z).unwrap().contains(&neighbor) { let pn = used.get(&neighbor).unwrap(); - let mut cycle: Vec = vec![neighbor, z]; + let mut cycle: Vec = vec![neighbor, z]; let mut p = pred.get(&z).unwrap(); while !pn.contains(p) { cycle.push(*p); p = pred.get(p).unwrap(); } cycle.push(*p); - cycles.push(cycle.iter().map(|x| x.index()).collect()); + cycles.push(cycle); let neighbor_set = used.get_mut(&neighbor).unwrap(); neighbor_set.insert(z); } } } - let mut temp_hashset: HashSet = HashSet::new(); + let mut temp_hashset: HashSet = HashSet::new(); for key in pred.keys() { temp_hashset.insert(*key); } @@ -152,315 +115,77 @@ where root_node = None; } cycles -} +} + #[cfg(test)] mod tests { - use crate::connectivity::all_simple_paths_multiple_targets; - use hashbrown::HashSet; + use crate::connectivity::cycle_basis; use petgraph::prelude::*; - #[test] - fn test_all_simple_paths() { - // create a path graph - let mut graph = Graph::new_undirected(); - let a = graph.add_node(0); - let b = graph.add_node(1); - let c = graph.add_node(2); - let d = graph.add_node(3); - let e = graph.add_node(4); - - graph.extend_with_edges(&[(a, b, 1), (b, c, 1), (c, d, 1), (d, e, 1)]); - - let mut to_set = HashSet::new(); - to_set.insert(d); - - let paths = all_simple_paths_multiple_targets(&graph, a, &to_set, 0, None); - - assert_eq!(paths.get(&d).unwrap(), &vec![vec![a, b, c, d]]); - } - - #[test] - fn test_all_simple_paths_with_two_targets_emits_two_paths() { - // create a path graph - let mut graph = Graph::new_undirected(); - let a = graph.add_node(0); - let b = graph.add_node(1); - let c = graph.add_node(2); - let d = graph.add_node(3); - let e = graph.add_node(4); - - graph.extend_with_edges(&[(a, b, 1), (b, c, 1), (c, d, 1), (d, e, 1), (c, e, 1)]); - - let mut to_set = HashSet::new(); - to_set.insert(d); - to_set.insert(e); - - let paths = all_simple_paths_multiple_targets(&graph, a, &to_set, 0, None); - - assert_eq!( - paths.get(&d).unwrap(), - &vec![vec![a, b, c, e, d], vec![a, b, c, d]] - ); - assert_eq!( - paths.get(&e).unwrap(), - &vec![vec![a, b, c, e], vec![a, b, c, d, e]] - ); - } - - #[test] - fn test_digraph_all_simple_paths_with_two_targets_emits_two_paths() { - // create a path graph - let mut graph = Graph::new(); - let a = graph.add_node(0); - let b = graph.add_node(1); - let c = graph.add_node(2); - let d = graph.add_node(3); - let e = graph.add_node(4); - - graph.extend_with_edges(&[(a, b, 1), (b, c, 1), (c, d, 1), (d, e, 1), (c, e, 1)]); - - let mut to_set = HashSet::new(); - to_set.insert(d); - to_set.insert(e); - - let paths = all_simple_paths_multiple_targets(&graph, a, &to_set, 0, None); - - assert_eq!(paths.get(&d).unwrap(), &vec![vec![a, b, c, d]]); - assert_eq!( - paths.get(&e).unwrap(), - &vec![vec![a, b, c, e], vec![a, b, c, d, e]] - ); - } - - #[test] - fn test_all_simple_paths_max_nodes() { - // create a complete graph - let mut graph = Graph::new_undirected(); - let a = graph.add_node(0); - let b = graph.add_node(1); - let c = graph.add_node(2); - let d = graph.add_node(3); - let e = graph.add_node(4); - - graph.extend_with_edges(&[ - (a, b, 1), - (a, c, 1), - (a, d, 1), - (a, e, 1), - (b, c, 1), - (b, d, 1), - (b, e, 1), - (c, d, 1), - (c, e, 1), - (d, e, 1), - ]); - - let mut to_set = HashSet::new(); - to_set.insert(b); - - let paths = all_simple_paths_multiple_targets(&graph, a, &to_set, 0, Some(0)); - - assert_eq!(paths.get(&b).unwrap(), &vec![vec![a, b]]); - - let paths = all_simple_paths_multiple_targets(&graph, a, &to_set, 0, Some(1)); - - assert_eq!( - paths.get(&b).unwrap(), - &vec![vec![a, e, b], vec![a, d, b], vec![a, c, b], vec![a, b],] - ); - } - - #[test] - fn test_all_simple_paths_with_two_targets_max_nodes() { - // create a path graph - let mut graph = Graph::new_undirected(); - let a = graph.add_node(0); - let b = graph.add_node(1); - let c = graph.add_node(2); - let d = graph.add_node(3); - let e = graph.add_node(4); - - graph.extend_with_edges(&[(a, b, 1), (b, c, 1), (c, d, 1), (d, e, 1), (c, e, 1)]); - - let mut to_set = HashSet::new(); - to_set.insert(d); - to_set.insert(e); - - let paths = all_simple_paths_multiple_targets(&graph, a, &to_set, 0, Some(2)); - - assert_eq!(paths.get(&d).unwrap(), &vec![vec![a, b, c, d]]); - assert_eq!(paths.get(&e).unwrap(), &vec![vec![a, b, c, e]]); - } - - #[test] - fn test_digraph_all_simple_paths_with_two_targets_max_nodes() { - // create a path graph - let mut graph = Graph::new(); - let a = graph.add_node(0); - let b = graph.add_node(1); - let c = graph.add_node(2); - let d = graph.add_node(3); - let e = graph.add_node(4); - - graph.extend_with_edges(&[(a, b, 1), (b, c, 1), (c, d, 1), (d, e, 1), (c, e, 1)]); - - let mut to_set = HashSet::new(); - to_set.insert(d); - to_set.insert(e); - - let paths = all_simple_paths_multiple_targets(&graph, a, &to_set, 0, Some(2)); - - assert_eq!(paths.get(&d).unwrap(), &vec![vec![a, b, c, d]]); - assert_eq!(paths.get(&e).unwrap(), &vec![vec![a, b, c, e]]); - } - - #[test] - fn test_all_simple_paths_with_two_targets_in_line_emits_two_paths() { - // create a path graph - let mut graph = Graph::new_undirected(); - let a = graph.add_node(0); - let b = graph.add_node(1); - let c = graph.add_node(2); - let d = graph.add_node(3); - let e = graph.add_node(4); - - graph.extend_with_edges(&[(a, b, 1), (b, c, 1), (c, d, 1), (d, e, 1)]); - - let mut to_set = HashSet::new(); - to_set.insert(c); - to_set.insert(d); - - let paths = all_simple_paths_multiple_targets(&graph, a, &to_set, 0, None); - - assert_eq!(paths.get(&c).unwrap(), &vec![vec![a, b, c]]); - assert_eq!(paths.get(&d).unwrap(), &vec![vec![a, b, c, d]]); - } - - #[test] - fn test_all_simple_paths_min_nodes() { - // create a cycle graph - let mut graph = Graph::new(); - let a = graph.add_node(0); - let b = graph.add_node(1); - let c = graph.add_node(2); - let d = graph.add_node(3); - - graph.extend_with_edges(&[(a, b, 1), (b, c, 1), (c, d, 1), (d, a, 1), (b, d, 1)]); - - let mut to_set = HashSet::new(); - to_set.insert(d); - - let paths = all_simple_paths_multiple_targets(&graph, a, &to_set, 2, None); - - assert_eq!(paths.get(&d).unwrap(), &vec![vec![a, b, c, d]]); - } - - #[test] - fn test_all_simple_paths_with_two_targets_min_nodes() { - // create a cycle graph - let mut graph = Graph::new(); - let a = graph.add_node(0); - let b = graph.add_node(1); - let c = graph.add_node(2); - let d = graph.add_node(3); - - graph.extend_with_edges(&[(a, b, 1), (b, c, 1), (c, d, 1), (d, a, 1), (b, d, 1)]); - - let mut to_set = HashSet::new(); - to_set.insert(c); - to_set.insert(d); - - let paths = all_simple_paths_multiple_targets(&graph, a, &to_set, 2, None); - - assert_eq!(paths.get(&c), None); - assert_eq!(paths.get(&d).unwrap(), &vec![vec![a, b, c, d]]); + fn sorted_cycle(cycles: Vec>) -> Vec> { + let mut sorted_cycles: Vec> = vec![]; + for cycle in cycles { + let mut cycle: Vec = cycle.iter().map(|x| x.index()).collect(); + cycle.sort(); + sorted_cycles.push(cycle); + } + sorted_cycles.sort(); + sorted_cycles } #[test] - fn test_all_simple_paths_source_target() { - // create a path graph - let mut graph = Graph::new_undirected(); - let a = graph.add_node(0); - let b = graph.add_node(1); - let c = graph.add_node(2); - let d = graph.add_node(3); - let e = graph.add_node(4); - - graph.extend_with_edges(&[(a, b, 1), (b, c, 1), (c, d, 1), (d, e, 1)]); - - let mut to_set = HashSet::new(); - to_set.insert(a); - - let paths = all_simple_paths_multiple_targets(&graph, a, &to_set, 0, None); - - assert_eq!(paths.get(&a), None); + fn test_cycle_basis_source() { + let edge_list = vec![ + (0, 1), + (0, 3), + (0, 5), + (0, 8), + (1, 2), + (1, 6), + (2, 3), + (3, 4), + (4, 5), + (6, 7), + (7, 8), + (8, 9), + ]; + let graph = UnGraph::::from_edges(&edge_list); + let expected = vec![vec![0, 1, 2, 3], vec![0, 1, 6, 7, 8], vec![0, 3, 4, 5]]; + let res_0 = cycle_basis(&graph, Some(NodeIndex::new(0))); + assert_eq!(sorted_cycle(res_0), expected); + let res_1 = cycle_basis(&graph, Some(NodeIndex::new(1))); + assert_eq!(sorted_cycle(res_1), expected); + let res_9 = cycle_basis(&graph, Some(NodeIndex::new(9))); + assert_eq!(sorted_cycle(res_9), expected); } #[test] - fn test_all_simple_paths_on_non_trivial_graph() { - // create a path graph - let mut graph = Graph::new(); - let a = graph.add_node(0); - let b = graph.add_node(1); - let c = graph.add_node(2); - let d = graph.add_node(3); - let e = graph.add_node(4); - let f = graph.add_node(5); - - graph.extend_with_edges(&[ - (a, b, 1), - (b, c, 1), - (c, d, 1), - (d, e, 1), - (e, f, 1), - (a, f, 1), - (b, f, 1), - (b, d, 1), - (f, e, 1), - (e, c, 1), - (e, d, 1), - ]); - - let mut to_set = HashSet::new(); - to_set.insert(c); - to_set.insert(d); - - let paths = all_simple_paths_multiple_targets(&graph, b, &to_set, 0, None); - + fn test_self_loop() { + let edge_list = vec![ + (0, 1), + (0, 3), + (0, 5), + (0, 8), + (1, 2), + (1, 6), + (2, 3), + (3, 4), + (4, 5), + (6, 7), + (7, 8), + (8, 9), + ]; + let mut graph = UnGraph::::from_edges(&edge_list); + graph.add_edge(NodeIndex::new(1), NodeIndex::new(1), 0); + let res_0 = cycle_basis(&graph, Some(NodeIndex::new(0))); assert_eq!( - paths.get(&c).unwrap(), - &vec![vec![b, d, e, c], vec![b, f, e, c], vec![b, c]] - ); - assert_eq!( - paths.get(&d).unwrap(), - &vec![ - vec![b, d], - vec![b, f, e, d], - vec![b, f, e, c, d], - vec![b, c, d] + sorted_cycle(res_0), + vec![ + vec![0, 1, 2, 3], + vec![0, 1, 6, 7, 8], + vec![0, 3, 4, 5], + vec![1] ] ); - - let paths = all_simple_paths_multiple_targets(&graph, b, &to_set, 1, None); - - assert_eq!( - paths.get(&c).unwrap(), - &vec![vec![b, d, e, c], vec![b, f, e, c]] - ); - assert_eq!( - paths.get(&d).unwrap(), - &vec![vec![b, f, e, d], vec![b, f, e, c, d], vec![b, c, d]] - ); - - let paths = all_simple_paths_multiple_targets(&graph, b, &to_set, 0, Some(2)); - - assert_eq!( - paths.get(&c).unwrap(), - &vec![vec![b, d, e, c], vec![b, f, e, c], vec![b, c]] - ); - assert_eq!( - paths.get(&d).unwrap(), - &vec![vec![b, d], vec![b, f, e, d], vec![b, c, d]] - ); } } diff --git a/rustworkx-core/src/connectivity/mod.rs b/rustworkx-core/src/connectivity/mod.rs index 65e8d44d4..5978b0b0c 100644 --- a/rustworkx-core/src/connectivity/mod.rs +++ b/rustworkx-core/src/connectivity/mod.rs @@ -16,6 +16,7 @@ mod all_simple_paths; mod biconnected; mod chain; mod conn_components; +mod cycle_basis; mod min_cut; pub use all_simple_paths::all_simple_paths_multiple_targets; @@ -24,4 +25,5 @@ pub use chain::chain_decomposition; pub use conn_components::bfs_undirected; pub use conn_components::connected_components; pub use conn_components::number_connected_components; +pub use cycle_basis::cycle_basis; pub use min_cut::stoer_wagner_min_cut; diff --git a/src/connectivity/mod.rs b/src/connectivity/mod.rs index ddc08eb3d..c907d5fcd 100644 --- a/src/connectivity/mod.rs +++ b/src/connectivity/mod.rs @@ -65,69 +65,10 @@ use rustworkx_core::connectivity; #[pyfunction] #[pyo3(text_signature = "(graph, /, root=None)")] pub fn cycle_basis(graph: &graph::PyGraph, root: Option) -> Vec> { - let mut root_node = root; - let mut graph_nodes: HashSet = graph.graph.node_indices().collect(); - let mut cycles: Vec> = Vec::new(); - while !graph_nodes.is_empty() { - let temp_value: NodeIndex; - // If root_node is not set get an arbitrary node from the set of graph - // nodes we've not "examined" - let root_index = match root_node { - Some(root_value) => NodeIndex::new(root_value), - None => { - temp_value = *graph_nodes.iter().next().unwrap(); - graph_nodes.remove(&temp_value); - temp_value - } - }; - // Stack (ie "pushdown list") of vertices already in the spanning tree - let mut stack: Vec = vec![root_index]; - // Map of node index to predecessor node index - let mut pred: HashMap = HashMap::new(); - pred.insert(root_index, root_index); - // Set of examined nodes during this iteration - let mut used: HashMap> = HashMap::new(); - used.insert(root_index, HashSet::new()); - // Walk the spanning tree - while !stack.is_empty() { - // Use the last element added so that cycles are easier to find - let z = stack.pop().unwrap(); - for neighbor in graph.graph.neighbors(z) { - // A new node was encountered: - if !used.contains_key(&neighbor) { - pred.insert(neighbor, z); - stack.push(neighbor); - let mut temp_set: HashSet = HashSet::new(); - temp_set.insert(z); - used.insert(neighbor, temp_set); - // A self loop: - } else if z == neighbor { - let cycle: Vec = vec![z.index()]; - cycles.push(cycle); - // A cycle was found: - } else if !used.get(&z).unwrap().contains(&neighbor) { - let pn = used.get(&neighbor).unwrap(); - let mut cycle: Vec = vec![neighbor, z]; - let mut p = pred.get(&z).unwrap(); - while !pn.contains(p) { - cycle.push(*p); - p = pred.get(p).unwrap(); - } - cycle.push(*p); - cycles.push(cycle.iter().map(|x| x.index()).collect()); - let neighbor_set = used.get_mut(&neighbor).unwrap(); - neighbor_set.insert(z); - } - } - } - let mut temp_hashset: HashSet = HashSet::new(); - for key in pred.keys() { - temp_hashset.insert(*key); - } - graph_nodes = graph_nodes.difference(&temp_hashset).copied().collect(); - root_node = None; - } - cycles + connectivity::cycle_basis(&graph.graph, root.map(NodeIndex::new)) + .into_iter() + .map(|res_map| res_map.into_iter().map(|x| x.index()).collect()) + .collect() } /// Find all simple cycles of a :class:`~.PyDiGraph` From 77637ff9af8bbb9beef2e09b8af4b97c4db2fc38 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Thu, 26 Jan 2023 09:51:05 -0800 Subject: [PATCH 3/3] Added reno --- .../add-find-cycle-and-cycle-basis-399d487def06239d.yaml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 releasenotes/notes/add-find-cycle-and-cycle-basis-399d487def06239d.yaml diff --git a/releasenotes/notes/add-find-cycle-and-cycle-basis-399d487def06239d.yaml b/releasenotes/notes/add-find-cycle-and-cycle-basis-399d487def06239d.yaml new file mode 100644 index 000000000..1ee368588 --- /dev/null +++ b/releasenotes/notes/add-find-cycle-and-cycle-basis-399d487def06239d.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Two new functions, ``find_cycle`` and ``cycle_basis``, have been added + to the ``rustworkx-core`` crate in the ``connectivity`` module. These + functions can be used to find a cycle in a petgraph graph or to find + the cycle basis of a graph.