#[cfg(feature = "hgraph")]
use crate::hgraph::h_graph::HeterogeneousGraph;
#[cfg(feature = "hgraph")]
use std::collections::{HashSet, VecDeque};
#[cfg(feature = "hgraph")]
use std::fmt::Debug;
#[cfg(feature = "hgraph")]
use std::hash::Hash;
#[derive(Debug)]
pub enum ConnectivityError {
InvalidNodeReference(usize),
}
impl std::fmt::Display for ConnectivityError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ConnectivityError::InvalidNodeReference(id) => {
write!(f, "Invalid node reference: node ID {} not found", id)
}
}
}
}
impl std::error::Error for ConnectivityError {}
pub type Result<T> = std::result::Result<T, ConnectivityError>;
#[cfg(feature = "hgraph")]
pub trait HeteroConnectivity<W, N, E> {
fn find_weakly_connected_components(&self) -> Result<Vec<Vec<usize>>>;
fn find_strongly_connected_components(&self) -> Result<Vec<Vec<usize>>>;
fn find_connected_components(&self) -> Result<Vec<Vec<usize>>> {
if self.is_directed() {
self.find_strongly_connected_components()
} else {
self.find_weakly_connected_components()
}
}
fn is_weakly_connected(&self) -> Result<bool>;
fn is_strongly_connected(&self) -> Result<bool>;
fn is_connected(&self) -> Result<bool> {
if self.is_directed() {
self.is_strongly_connected()
} else {
self.is_weakly_connected()
}
}
fn find_weakly_connected_components_by_types(
&self,
allowed_edge_types: &[E],
) -> Result<Vec<Vec<usize>>>;
fn find_strongly_connected_components_by_types(
&self,
allowed_edge_types: &[E],
) -> Result<Vec<Vec<usize>>>;
fn find_connected_components_by_types(
&self,
allowed_edge_types: &[E],
) -> Result<Vec<Vec<usize>>> {
if self.is_directed() {
self.find_strongly_connected_components_by_types(allowed_edge_types)
} else {
self.find_weakly_connected_components_by_types(allowed_edge_types)
}
}
fn is_weakly_connected_by_types(&self, allowed_edge_types: &[E]) -> Result<bool>;
fn is_strongly_connected_by_types(&self, allowed_edge_types: &[E]) -> Result<bool>;
fn is_connected_by_types(&self, allowed_edge_types: &[E]) -> Result<bool> {
if self.is_directed() {
self.is_strongly_connected_by_types(allowed_edge_types)
} else {
self.is_weakly_connected_by_types(allowed_edge_types)
}
}
fn is_directed(&self) -> bool;
}
#[cfg(feature = "hgraph")]
impl<W, N, E> HeteroConnectivity<W, N, E> for HeterogeneousGraph<W, N, E>
where
W: Copy + Default + PartialEq + Debug,
N: Clone + Eq + Hash + Debug + crate::hgraph::h_node::NodeType,
E: Clone + Default + Debug + crate::hgraph::h_edge::EdgeType + PartialEq,
{
fn find_weakly_connected_components(&self) -> Result<Vec<Vec<usize>>> {
self.find_weakly_connected_components_by_types(&[])
}
fn find_strongly_connected_components(&self) -> Result<Vec<Vec<usize>>> {
self.find_strongly_connected_components_by_types(&[])
}
fn is_weakly_connected(&self) -> Result<bool> {
Ok(self.find_weakly_connected_components()?.len() <= 1)
}
fn is_strongly_connected(&self) -> Result<bool> {
if self.nodes.is_empty() {
return Ok(true);
}
let start_node = self.nodes.iter().next().unwrap().0;
Ok(self.strong_connectivity_check_by_types(start_node, &[]))
}
fn find_weakly_connected_components_by_types(
&self,
allowed_edge_types: &[E],
) -> Result<Vec<Vec<usize>>> {
let mut visited = HashSet::new();
let mut components = Vec::new();
for node in self.nodes.iter().map(|(id, _)| id) {
if !visited.contains(&node) {
let mut component = Vec::new();
let mut queue = VecDeque::new();
queue.push_back(node);
visited.insert(node);
while let Some(current) = queue.pop_front() {
component.push(current);
let mut neighbors = self
.get_neighbors_by_types(current, allowed_edge_types)
.into_iter()
.map(|(n, _)| n)
.collect::<HashSet<_>>();
if self.is_directed() {
neighbors
.extend(self.get_predecessors_by_types(current, allowed_edge_types));
}
for neighbor in neighbors {
if !self.nodes.contains(neighbor) {
return Err(ConnectivityError::InvalidNodeReference(neighbor));
}
if !visited.contains(&neighbor) {
visited.insert(neighbor);
queue.push_back(neighbor);
}
}
}
components.push(component);
}
}
Ok(components)
}
fn find_strongly_connected_components_by_types(
&self,
allowed_edge_types: &[E],
) -> Result<Vec<Vec<usize>>> {
let mut visited = HashSet::new();
let mut order = Vec::with_capacity(self.nodes.len());
for node in self.nodes.iter().map(|(id, _)| id) {
if !visited.contains(&node) {
self.dfs_order_by_types(node, allowed_edge_types, &mut visited, &mut order);
}
}
let transposed = self.transpose_by_types(allowed_edge_types);
let mut visited = HashSet::new();
let mut components = Vec::new();
for &node in order.iter().rev() {
if !self.nodes.contains(node) {
return Err(ConnectivityError::InvalidNodeReference(node));
}
if !visited.contains(&node) {
let mut component = Vec::new();
transposed.dfs_collect_by_types(
node,
allowed_edge_types,
&mut visited,
&mut component,
);
component.sort();
components.push(component);
}
}
Ok(components)
}
fn is_weakly_connected_by_types(&self, allowed_edge_types: &[E]) -> Result<bool> {
Ok(self
.find_weakly_connected_components_by_types(allowed_edge_types)?
.len()
<= 1)
}
fn is_strongly_connected_by_types(&self, allowed_edge_types: &[E]) -> Result<bool> {
if self.nodes.is_empty() {
return Ok(true);
}
let start_node = self.nodes.iter().next().unwrap().0;
Ok(self.strong_connectivity_check_by_types(start_node, allowed_edge_types))
}
fn is_directed(&self) -> bool {
self.directed
}
}
#[cfg(feature = "hgraph")]
impl<W, N, E> HeterogeneousGraph<W, N, E>
where
W: Copy + Default + PartialEq + Debug,
N: Clone + Eq + Hash + Debug + crate::hgraph::h_node::NodeType,
E: Clone + Default + Debug + crate::hgraph::h_edge::EdgeType + PartialEq,
{
fn dfs_order_by_types(
&self,
node: usize,
allowed_edge_types: &[E],
visited: &mut HashSet<usize>,
order: &mut Vec<usize>,
) {
visited.insert(node);
for (neighbor, _edge) in self.get_neighbors_by_types(node, allowed_edge_types) {
if !visited.contains(&neighbor) {
self.dfs_order_by_types(neighbor, allowed_edge_types, visited, order);
}
}
order.push(node);
}
fn dfs_collect_by_types(
&self,
node: usize,
allowed_edge_types: &[E],
visited: &mut HashSet<usize>,
component: &mut Vec<usize>,
) {
visited.insert(node);
component.push(node);
for (neighbor, _) in self.get_neighbors_by_types(node, allowed_edge_types) {
if !visited.contains(&neighbor) {
self.dfs_collect_by_types(neighbor, allowed_edge_types, visited, component);
}
}
}
fn strong_connectivity_check_by_types(&self, start: usize, allowed_edge_types: &[E]) -> bool {
let mut forward_visited = HashSet::new();
self.dfs_collect_by_types(start, allowed_edge_types, &mut forward_visited, &mut vec![]);
if forward_visited.len() != self.nodes.len() {
return false;
}
let transposed = self.transpose_by_types(allowed_edge_types);
let mut backward_visited = HashSet::new();
transposed.dfs_collect_by_types(
start,
allowed_edge_types,
&mut backward_visited,
&mut vec![],
);
backward_visited.len() == self.nodes.len()
}
fn get_neighbors_by_types(&self, node: usize, allowed_edge_types: &[E]) -> Vec<(usize, &E)> {
self.edges
.iter()
.filter(|(_, e)| {
e.from == node
&& (allowed_edge_types.is_empty() || allowed_edge_types.contains(&e.data))
})
.map(|(_, e)| (e.to, &e.data))
.collect()
}
fn get_predecessors_by_types(&self, node: usize, allowed_edge_types: &[E]) -> Vec<usize> {
self.edges
.iter()
.filter(|(_, e)| {
e.to == node
&& (allowed_edge_types.is_empty() || allowed_edge_types.contains(&e.data))
})
.map(|(_, e)| e.from)
.collect()
}
fn transpose_by_types(&self, allowed_edge_types: &[E]) -> Self {
let mut transposed = HeterogeneousGraph::new(self.directed);
let mut node_map = std::collections::HashMap::new();
for (id, node) in self.nodes.iter() {
let new_id = transposed.add_node(node.data.clone());
node_map.insert(id, new_id);
for (key, value) in &node.attributes {
transposed.set_node_attribute(new_id, key.clone(), value.clone());
}
}
for (_edge_id, edge) in self.edges.iter() {
if allowed_edge_types.is_empty() || allowed_edge_types.contains(&edge.data) {
let new_from = *node_map.get(&edge.to).unwrap();
let new_to = *node_map.get(&edge.from).unwrap();
let new_edge_id = transposed
.add_edge(new_from, new_to, edge.weight, edge.data.clone())
.expect("Invalid edge in transpose");
for (key, value) in &edge.attributes {
transposed.set_edge_attribute(new_edge_id, key.clone(), value.clone());
}
}
}
transposed
}
}
#[cfg(test)]
#[cfg(feature = "hgraph")]
mod tests {
use super::*;
use crate::hgraph::h_edge::EdgeType;
use crate::hgraph::h_node::NodeType;
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
struct TestNode(String);
impl NodeType for TestNode {
fn as_string(&self) -> String {
self.0.clone()
}
}
#[derive(Clone, Debug, Default, PartialEq)]
struct TestEdge(String);
impl EdgeType for TestEdge {
fn as_string(&self) -> String {
self.0.clone()
}
}
#[test]
fn test_weakly_connected_components() {
let mut graph = HeterogeneousGraph::<f64, TestNode, TestEdge>::new(false);
let n0 = graph.add_node(TestNode("A".to_string()));
let n1 = graph.add_node(TestNode("B".to_string()));
let n2 = graph.add_node(TestNode("C".to_string()));
graph
.add_edge(n0, n1, 1.0, TestEdge("road".to_string()))
.unwrap();
graph
.add_edge(n1, n2, 1.0, TestEdge("bridge".to_string()))
.unwrap();
let wcc = graph.find_weakly_connected_components().unwrap();
assert_eq!(wcc.len(), 1);
assert_eq!(wcc[0], vec![n0, n1, n2]);
}
#[test]
fn test_strongly_connected_components() {
let mut graph = HeterogeneousGraph::<f64, TestNode, TestEdge>::new(true);
let n0 = graph.add_node(TestNode("A".to_string()));
let n1 = graph.add_node(TestNode("B".to_string()));
let n2 = graph.add_node(TestNode("C".to_string()));
graph
.add_edge(n0, n1, 1.0, TestEdge("road".to_string()))
.unwrap();
graph
.add_edge(n1, n2, 1.0, TestEdge("bridge".to_string()))
.unwrap();
graph
.add_edge(n2, n0, 1.0, TestEdge("path".to_string()))
.unwrap();
let scc = graph.find_strongly_connected_components().unwrap();
assert_eq!(scc.len(), 1);
assert_eq!(scc[0], vec![n0, n1, n2]);
}
#[test]
fn test_weakly_connected_components_by_types() {
let mut graph = HeterogeneousGraph::<f64, TestNode, TestEdge>::new(false);
let n0 = graph.add_node(TestNode("A".to_string()));
let n1 = graph.add_node(TestNode("B".to_string()));
let n2 = graph.add_node(TestNode("C".to_string()));
graph
.add_edge(n0, n1, 1.0, TestEdge("road".to_string()))
.unwrap();
graph
.add_edge(n1, n2, 1.0, TestEdge("bridge".to_string()))
.unwrap();
let allowed_edge_types = vec![TestEdge("road".to_string())];
let wcc = graph
.find_weakly_connected_components_by_types(&allowed_edge_types)
.unwrap();
assert_eq!(wcc.len(), 2);
assert_eq!(wcc[0], vec![n0, n1]);
assert_eq!(wcc[1], vec![n2]);
}
#[test]
fn test_strongly_connected_components_by_types() {
let mut graph = HeterogeneousGraph::<f64, TestNode, TestEdge>::new(true);
let n0 = graph.add_node(TestNode("A".to_string()));
let n1 = graph.add_node(TestNode("B".to_string()));
let n2 = graph.add_node(TestNode("C".to_string()));
graph
.add_edge(n0, n1, 1.0, TestEdge("road".to_string()))
.unwrap();
graph
.add_edge(n1, n0, 1.0, TestEdge("path".to_string()))
.unwrap();
graph
.add_edge(n0, n2, 1.0, TestEdge("path".to_string()))
.unwrap();
graph
.add_edge(n2, n0, 1.0, TestEdge("path".to_string()))
.unwrap();
let allowed_edge_types = vec![TestEdge("road".to_string()), TestEdge("path".to_string())];
let scc = graph
.find_strongly_connected_components_by_types(&allowed_edge_types)
.unwrap();
assert_eq!(scc.len(), 1);
assert_eq!(scc[0], vec![n0, n1, n2]);
}
#[test]
fn test_is_weakly_connected() {
let mut graph = HeterogeneousGraph::<f64, TestNode, TestEdge>::new(false);
let n0 = graph.add_node(TestNode("A".to_string()));
let n1 = graph.add_node(TestNode("B".to_string()));
graph
.add_edge(n0, n1, 1.0, TestEdge("road".to_string()))
.unwrap();
assert!(graph.is_weakly_connected().unwrap());
}
#[test]
fn test_is_strongly_connected() {
let mut graph = HeterogeneousGraph::<f64, TestNode, TestEdge>::new(true);
let n0 = graph.add_node(TestNode("A".to_string()));
let n1 = graph.add_node(TestNode("B".to_string()));
let n2 = graph.add_node(TestNode("C".to_string()));
graph
.add_edge(n0, n1, 1.0, TestEdge("road".to_string()))
.unwrap();
graph
.add_edge(n1, n2, 1.0, TestEdge("bridge".to_string()))
.unwrap();
graph
.add_edge(n2, n0, 1.0, TestEdge("path".to_string()))
.unwrap();
assert!(graph.is_strongly_connected().unwrap());
}
}