diff --git a/rustworkx-core/src/connectivity/conn_components.rs b/rustworkx-core/src/connectivity/conn_components.rs index 5bdf199004..746972f05f 100644 --- a/rustworkx-core/src/connectivity/conn_components.rs +++ b/rustworkx-core/src/connectivity/conn_components.rs @@ -14,8 +14,10 @@ use hashbrown::HashSet; use std::collections::VecDeque; use std::hash::Hash; -use petgraph::visit::{GraphProp, IntoNeighborsDirected, IntoNodeIdentifiers, VisitMap, Visitable}; -use petgraph::{Incoming, Outgoing}; +use petgraph::visit::{ + GraphProp, IntoNeighborsDirected, IntoNodeIdentifiers, NodeCount, VisitMap, Visitable, +}; +use petgraph::{Directed, Incoming, Outgoing}; /// Given an graph, a node in the graph, and a visit_map, /// return the set of nodes connected to the given node @@ -165,6 +167,101 @@ where num_components } +/// Given a graph, return the list of weakly connected components of the graph. +/// A weakly connected component is a maximal subgraph where all nodes are +/// reachable from all other nodes in the subgraph. +/// This function is only defined for directed graphs. +/// For undirected graphs use the :func:`connected_components` function. +/// +/// Arguments: +/// +/// * `graph` - The graph object to run the algorithm on +/// +/// # Example +/// ```rust +/// use std::iter::FromIterator; +/// use hashbrown::HashSet; +/// use petgraph::graph::Graph; +/// use petgraph::graph::NodeIndex; +/// use petgraph::{Directed}; +/// use petgraph::graph::node_index as ndx; +/// use rustworkx_core::connectivity::weakly_connected_components; +/// +/// let graph = Graph::<(), (), Directed>::from_edges(&[ +/// (0, 1), +/// (1, 2), +/// (2, 3), +/// (3, 0), +/// (4, 5), +/// (5, 6), +/// (6, 7), +/// (7, 4), +/// ]); +/// let components = weakly_connected_components(&graph); +/// let exp1 = HashSet::from_iter([ndx(0), ndx(1), ndx(3), ndx(2)]); +/// let exp2 = HashSet::from_iter([ndx(7), ndx(5), ndx(4), ndx(6)]); +/// let expected = vec![exp1, exp2]; +/// assert_eq!(expected, components); +/// ``` +pub fn weakly_connected_components(graph: G) -> Vec> +where + G: GraphProp + IntoNeighborsDirected + Visitable + IntoNodeIdentifiers, + G::NodeId: Eq + Hash, +{ + connected_components(graph) +} + +/// Given a graph, return the number of weakly connected components of the graph. +/// +/// Arguments: +/// +/// * `graph` - The graph object to run the algorithm on +/// +/// # Example +/// ```rust +/// use rustworkx_core::petgraph::{Graph, Directed}; +/// use rustworkx_core::connectivity::number_weakly_connected_components; +/// +/// let graph = Graph::<(), (), Directed>::from_edges([(0, 1), (1, 2), (3, 4)]); +/// assert_eq!(number_weakly_connected_components(&graph), 2); +/// ``` +pub fn number_weakly_connected_components(graph: G) -> usize +where + G: GraphProp + IntoNeighborsDirected + Visitable + IntoNodeIdentifiers, + G::NodeId: Eq + Hash, +{ + number_connected_components(graph) +} + +/// Given a graph, check if the graph is weakly connected. +/// +/// This function is only defined for directed graphs. +/// For undirected graphs use the :func:`is_connected` function. +/// +/// Arguments: +/// +/// * `graph` - The graph object to run the algorithm on +/// +/// # Example +/// ```rust +/// use rustworkx_core::petgraph::{Graph, Directed}; +/// use rustworkx_core::connectivity::is_weakly_connected; +/// +/// let graph = Graph::<(), (), Directed>::from_edges([(0, 1), (1, 2), (3, 4)]); +/// assert_eq!(is_weakly_connected(&graph), false); +/// ``` +pub fn is_weakly_connected(graph: G) -> bool +where + G: GraphProp + + IntoNeighborsDirected + + Visitable + + IntoNodeIdentifiers + + NodeCount, + G::NodeId: Eq + Hash, +{ + weakly_connected_components(graph)[0].len() == graph.node_count() +} + #[cfg(test)] mod test_conn_components { use hashbrown::HashSet; @@ -174,7 +271,7 @@ mod test_conn_components { use petgraph::{Directed, Undirected}; use std::iter::FromIterator; - use crate::connectivity::{bfs_undirected, connected_components, number_connected_components}; + use super::*; #[test] fn test_number_connected() { @@ -231,4 +328,38 @@ mod test_conn_components { let expected = HashSet::from_iter([ndx(0), ndx(1), ndx(3), ndx(2)]); assert_eq!(expected, component); } + + #[test] + fn test_weakly_connected() { + let graph = Graph::<(), (), Directed>::from_edges(&[ + (1, 2), + (2, 3), + (2, 8), + (3, 4), + (3, 7), + (4, 5), + (5, 3), + (5, 6), + (7, 4), + (7, 6), + (8, 1), + (8, 7), + ]); + let weak_components = weakly_connected_components(&graph); + let exp1 = HashSet::from_iter([ndx(0)]); + let exp2 = HashSet::from_iter([ + ndx(1), + ndx(2), + ndx(3), + ndx(4), + ndx(5), + ndx(6), + ndx(7), + ndx(8), + ]); + let expected = vec![exp1, exp2]; + assert_eq!(expected, weak_components); + assert_eq!(number_weakly_connected_components(&graph), 2); + assert_eq!(is_weakly_connected(&graph), false); + } } diff --git a/rustworkx-core/src/connectivity/mod.rs b/rustworkx-core/src/connectivity/mod.rs index 65e8d44d4b..c7e9d54b07 100644 --- a/rustworkx-core/src/connectivity/mod.rs +++ b/rustworkx-core/src/connectivity/mod.rs @@ -23,5 +23,8 @@ pub use biconnected::articulation_points; pub use chain::chain_decomposition; pub use conn_components::bfs_undirected; pub use conn_components::connected_components; +pub use conn_components::is_weakly_connected; pub use conn_components::number_connected_components; +pub use conn_components::number_weakly_connected_components; +pub use conn_components::weakly_connected_components; pub use min_cut::stoer_wagner_min_cut; diff --git a/src/connectivity/mod.rs b/src/connectivity/mod.rs index ddc08eb3d6..e80c88b028 100644 --- a/src/connectivity/mod.rs +++ b/src/connectivity/mod.rs @@ -29,8 +29,7 @@ use pyo3::Python; use petgraph::algo; use petgraph::stable_graph::NodeIndex; -use petgraph::unionfind::UnionFind; -use petgraph::visit::{EdgeRef, IntoEdgeReferences, NodeCount, NodeIndexable, Visitable}; +use petgraph::visit::{NodeCount, Visitable}; use ndarray::prelude::*; use numpy::IntoPyArray; @@ -328,16 +327,7 @@ pub fn is_connected(graph: &graph::PyGraph) -> PyResult { #[pyfunction] #[pyo3(text_signature = "(graph, /)")] pub fn number_weakly_connected_components(graph: &digraph::PyDiGraph) -> usize { - let mut weak_components = graph.node_count(); - let mut vertex_sets = UnionFind::new(graph.graph.node_bound()); - for edge in graph.graph.edge_references() { - let (a, b) = (edge.source(), edge.target()); - // union the two vertices of the edge - if vertex_sets.union(a.index(), b.index()) { - weak_components -= 1 - }; - } - weak_components + connectivity::number_weakly_connected_components(&graph.graph) } /// Find the weakly connected components in a directed graph @@ -351,7 +341,7 @@ pub fn number_weakly_connected_components(graph: &digraph::PyDiGraph) -> usize { #[pyfunction] #[pyo3(text_signature = "(graph, /)")] pub fn weakly_connected_components(graph: &digraph::PyDiGraph) -> Vec> { - connectivity::connected_components(&graph.graph) + connectivity::weakly_connected_components(&graph.graph) .into_iter() .map(|res_map| res_map.into_iter().map(|x| x.index()).collect()) .collect() @@ -371,7 +361,7 @@ pub fn is_weakly_connected(graph: &digraph::PyDiGraph) -> PyResult { if graph.graph.node_count() == 0 { return Err(NullGraph::new_err("Invalid operation on a NullGraph")); } - Ok(weakly_connected_components(graph)[0].len() == graph.graph.node_count()) + Ok(connectivity::is_weakly_connected(&graph.graph)) } /// Return the adjacency matrix for a PyDiGraph object