use crate::graph::traits::{GraphBase, GraphQuery};
use crate::graph::Graph;
use crate::node::NodeIndex;
use std::collections::{HashMap, VecDeque};
pub fn connected_components<T>(graph: &Graph<T, impl Clone>) -> Vec<Vec<NodeIndex>> {
let node_indices: Vec<NodeIndex> = graph.nodes().map(|n| n.index()).collect();
let index_to_node: std::collections::HashMap<usize, NodeIndex> =
node_indices.iter().map(|&ni| (ni.index(), ni)).collect();
let n = graph.node_count();
let mut visited = vec![false; n];
let mut components = Vec::new();
for &node in &node_indices {
if !visited[node.index()] {
let mut component = Vec::new();
bfs_component(graph, node, &mut visited, &mut component, &index_to_node);
components.push(component);
}
}
components
}
fn bfs_component<T>(
graph: &Graph<T, impl Clone>,
start: NodeIndex,
visited: &mut [bool],
component: &mut Vec<NodeIndex>,
index_to_node: &std::collections::HashMap<usize, NodeIndex>,
) {
let mut queue = VecDeque::new();
queue.push_back(start);
visited[start.index()] = true;
while let Some(node) = queue.pop_front() {
component.push(node);
for neighbor in graph.neighbors(node) {
if !visited[neighbor.index()] {
visited[neighbor.index()] = true;
if let Some(&neighbor_ni) = index_to_node.get(&neighbor.index()) {
queue.push_back(neighbor_ni);
}
}
}
}
}
pub fn strongly_connected_components<T>(graph: &Graph<T, impl Clone>) -> Vec<Vec<NodeIndex>> {
let node_indices: Vec<NodeIndex> = graph.nodes().map(|n| n.index()).collect();
let index_to_node: std::collections::HashMap<usize, NodeIndex> =
node_indices.iter().map(|&ni| (ni.index(), ni)).collect();
let n = graph.node_count();
let mut visited = vec![false; n];
let mut finish_order = Vec::with_capacity(n);
for &node in &node_indices {
if !visited[node.index()] {
dfs_finish_order(graph, node, &mut visited, &mut finish_order);
}
}
let mut visited = vec![false; n];
let mut components = Vec::new();
for &node in finish_order.iter().rev() {
if !visited[node.index()] {
let mut component = Vec::new();
dfs_reverse(graph, node, &mut visited, &mut component, &index_to_node);
components.push(component);
}
}
components
}
fn dfs_finish_order<T>(
graph: &Graph<T, impl Clone>,
node: NodeIndex,
visited: &mut [bool],
finish_order: &mut Vec<NodeIndex>,
) {
visited[node.index()] = true;
for neighbor in graph.neighbors(node) {
if !visited[neighbor.index()] {
dfs_finish_order(graph, neighbor, visited, finish_order);
}
}
finish_order.push(node);
}
fn dfs_reverse<T>(
graph: &Graph<T, impl Clone>,
node: NodeIndex,
visited: &mut [bool],
component: &mut Vec<NodeIndex>,
index_to_node: &std::collections::HashMap<usize, NodeIndex>,
) {
component.push(node);
visited[node.index()] = true;
for potential_source in graph.nodes() {
if graph.has_edge(potential_source.index(), node) {
let source_idx = potential_source.index();
if !visited[source_idx.index()] {
if let Some(&source_ni) = index_to_node.get(&source_idx.index()) {
dfs_reverse(graph, source_ni, visited, component, index_to_node);
}
}
}
}
}
pub fn label_propagation<T>(
graph: &Graph<T, impl Clone>,
max_iterations: usize,
) -> HashMap<NodeIndex, usize> {
let node_indices: Vec<NodeIndex> = graph.nodes().map(|n| n.index()).collect();
let n = node_indices.len();
if n == 0 {
return HashMap::new();
}
let mut labels: HashMap<NodeIndex, usize> = node_indices
.iter()
.enumerate()
.map(|(i, &ni)| (ni, i))
.collect();
for _ in 0..max_iterations {
let mut changed = false;
for &node in &node_indices {
let mut label_counts: HashMap<usize, usize> = HashMap::new();
for neighbor in graph.neighbors(node) {
if let Some(&label) = labels.get(&neighbor) {
*label_counts.entry(label).or_insert(0) += 1;
}
}
if let Some((&max_label, _)) = label_counts.iter().max_by_key(|&(_, count)| count) {
let current_label = labels.get(&node).copied().unwrap_or(usize::MAX);
if max_label != current_label {
labels.insert(node, max_label);
changed = true;
}
}
}
if !changed {
break;
}
}
labels
}
pub fn louvain<T>(graph: &Graph<T, impl Clone>, resolution: f64) -> HashMap<NodeIndex, usize>
where
T: Clone,
{
let node_indices: Vec<NodeIndex> = graph.nodes().map(|n| n.index()).collect();
let n = node_indices.len();
if n == 0 {
return HashMap::new();
}
let mut communities: HashMap<NodeIndex, usize> = node_indices
.iter()
.enumerate()
.map(|(i, &ni)| (ni, i))
.collect();
let total_edges = graph.edge_count() as f64;
if total_edges == 0.0 {
return communities;
}
let mut degrees: HashMap<NodeIndex, usize> = HashMap::new();
for &node in &node_indices {
degrees.insert(node, graph.out_degree(node).unwrap_or(0));
}
let mut improved = true;
while improved {
improved = false;
for &node in &node_indices {
let current_comm = *communities.get(&node).unwrap();
let node_degree = *degrees.get(&node).unwrap();
let mut comm_delta_q: HashMap<usize, f64> = HashMap::new();
for neighbor in graph.neighbors(node) {
let neighbor_comm = *communities.get(&neighbor).unwrap();
if neighbor_comm != current_comm {
let delta_q = 1.0 / (2.0 * total_edges)
- resolution
* (node_degree as f64
* degrees.get(&neighbor).copied().unwrap_or(0) as f64)
/ (4.0 * total_edges * total_edges);
*comm_delta_q.entry(neighbor_comm).or_insert(0.0) += delta_q;
}
}
if let Some((&best_comm, &max_delta)) = comm_delta_q
.iter()
.max_by(|a, b| a.1.partial_cmp(b.1).unwrap_or(std::cmp::Ordering::Equal))
{
if max_delta > 0.0 {
communities.insert(node, best_comm);
improved = true;
}
}
}
}
let mut comm_remap: HashMap<usize, usize> = HashMap::new();
let mut next_comm = 0usize;
for comm in communities.values_mut() {
if !comm_remap.contains_key(comm) {
comm_remap.insert(*comm, next_comm);
next_comm += 1;
}
*comm = comm_remap[comm];
}
communities
}
#[cfg(test)]
mod tests {
use super::*;
use crate::graph::builders::GraphBuilder;
#[test]
fn test_connected_components() {
let graph = GraphBuilder::undirected()
.with_nodes(vec![1, 2, 3, 4, 5, 6])
.with_edges(vec![(0, 1, 1.0), (1, 2, 1.0), (3, 4, 1.0)])
.build()
.unwrap();
let components = connected_components(&graph);
assert_eq!(components.len(), 3); }
#[test]
fn test_connected_components_empty_graph() {
let graph: Graph<i32, f64> = GraphBuilder::undirected()
.with_nodes(vec![1, 2, 3])
.build()
.unwrap();
let components = connected_components(&graph);
assert_eq!(components.len(), 3); assert!(components.iter().all(|c| c.len() == 1));
}
#[test]
fn test_connected_components_single_node() {
let graph: Graph<i32, f64> = GraphBuilder::undirected()
.with_nodes(vec![1])
.build()
.unwrap();
let components = connected_components(&graph);
assert_eq!(components.len(), 1);
assert_eq!(components[0].len(), 1);
}
#[test]
fn test_connected_components_fully_connected() {
let graph: Graph<i32, f64> = GraphBuilder::undirected()
.with_nodes(vec![1, 2, 3, 4])
.with_edges(vec![
(0, 1, 1.0),
(0, 2, 1.0),
(0, 3, 1.0),
(1, 2, 1.0),
(1, 3, 1.0),
(2, 3, 1.0),
])
.build()
.unwrap();
let components = connected_components(&graph);
assert_eq!(components.len(), 1);
assert_eq!(components[0].len(), 4);
}
#[test]
fn test_strongly_connected_components() {
let graph = GraphBuilder::directed()
.with_nodes(vec![1, 2, 3, 4])
.with_edges(vec![(0, 1, 1.0), (1, 2, 1.0), (2, 0, 1.0), (2, 3, 1.0)])
.build()
.unwrap();
let components = strongly_connected_components(&graph);
assert!(!components.is_empty());
}
#[test]
fn test_strongly_connected_single_node() {
let graph: Graph<i32, f64> = GraphBuilder::directed()
.with_nodes(vec![1])
.build()
.unwrap();
let components = strongly_connected_components(&graph);
assert_eq!(components.len(), 1);
assert_eq!(components[0].len(), 1);
}
#[test]
fn test_strongly_connected_dag() {
let graph: Graph<i32, f64> = GraphBuilder::directed()
.with_nodes(vec![1, 2, 3, 4])
.with_edges(vec![(0, 1, 1.0), (1, 2, 1.0), (2, 3, 1.0)])
.build()
.unwrap();
let components = strongly_connected_components(&graph);
assert_eq!(components.len(), 4); }
#[test]
fn test_label_propagation() {
let graph = GraphBuilder::undirected()
.with_nodes(vec![1, 2, 3, 4])
.with_edges(vec![(0, 1, 1.0), (1, 2, 1.0), (2, 3, 1.0)])
.build()
.unwrap();
let labels = label_propagation(&graph, 10);
assert_eq!(labels.len(), 4);
}
#[test]
fn test_label_propagation_empty_graph() {
let graph: Graph<i32, f64> = GraphBuilder::undirected()
.with_nodes(vec![1, 2, 3])
.build()
.unwrap();
let labels = label_propagation(&graph, 10);
assert_eq!(labels.len(), 3);
}
#[test]
fn test_label_propagation_single_node() {
let graph: Graph<i32, f64> = GraphBuilder::undirected()
.with_nodes(vec![1])
.build()
.unwrap();
let labels = label_propagation(&graph, 10);
assert_eq!(labels.len(), 1);
}
#[test]
fn test_louvain_basic() {
let graph: Graph<i32, f64> = GraphBuilder::undirected()
.with_nodes(vec![1, 2, 3, 4])
.with_edges(vec![(0, 1, 1.0), (1, 2, 1.0), (2, 3, 1.0)])
.build()
.unwrap();
let communities = louvain(&graph, 1.0);
assert!(!communities.is_empty());
assert_eq!(communities.len(), 4);
}
#[test]
fn test_louvain_empty_graph() {
let graph: Graph<i32, f64> = GraphBuilder::undirected()
.with_nodes(vec![1, 2, 3])
.build()
.unwrap();
let communities = louvain(&graph, 1.0);
assert_eq!(communities.len(), 3);
}
}