use crate::core::view::NodeId;
use crate::graph::graph::Graph;
use std::collections::HashMap;
use std::fmt::Debug;
use std::hash::Hash;
pub use crate::core::algo::community::{CommunityConfig, CommunityDetectionError};
#[deprecated(
since = "3.0.0",
note = "superseded by `xgraph::core::CommunityDetection`, which is implemented for \
every graph view — including filtered ones — and carries no unused type \
parameters"
)]
pub trait CommunityDetection<W, N, E>
where
W: Copy + Into<f64> + Default + PartialEq,
N: Clone + Eq + Hash + Debug,
E: Clone + Default + Debug,
{
fn detect_communities(
&self,
gamma: f64,
) -> Result<HashMap<usize, Vec<NodeId>>, CommunityDetectionError>;
fn detect_communities_with_config(
&self,
config: CommunityConfig,
) -> Result<HashMap<usize, Vec<NodeId>>, CommunityDetectionError>;
}
#[allow(deprecated)]
impl<W, N, E> CommunityDetection<W, N, E> for Graph<W, N, E>
where
W: Copy + Into<f64> + Default + PartialEq,
N: Clone + Eq + Hash + Debug,
E: Clone + Default + Debug,
{
fn detect_communities(
&self,
gamma: f64,
) -> Result<HashMap<usize, Vec<NodeId>>, CommunityDetectionError> {
crate::core::CommunityDetection::detect_communities(self, gamma)
}
fn detect_communities_with_config(
&self,
config: CommunityConfig,
) -> Result<HashMap<usize, Vec<NodeId>>, CommunityDetectionError> {
crate::core::CommunityDetection::detect_communities_with_config(self, config)
}
}
#[cfg(test)]
#[allow(deprecated)]
mod tests {
use super::*;
use crate::core::view::NodeId;
fn create_test_graph() -> Graph<f64, String, String> {
let mut graph = Graph::new(false);
let node0 = graph.add_node("Node0".to_string());
let node1 = graph.add_node("Node1".to_string());
let node2 = graph.add_node("Node2".to_string());
let _node3 = graph.add_node("Node3".to_string());
graph
.add_edge(node0, node1, 1.0, "e01".to_string())
.unwrap();
graph
.add_edge(node1, node2, 1.0, "e12".to_string())
.unwrap();
graph
.add_edge(node0, node2, 1.0, "e02".to_string())
.unwrap();
graph
}
#[test]
fn test_community_detection_basic() {
let graph = create_test_graph();
let config = CommunityConfig {
gamma: 1.0,
deterministic: true,
seed: Some(42),
..CommunityConfig::default()
};
let communities = graph.detect_communities_with_config(config).unwrap();
assert_eq!(communities.len(), 2);
let mut found_clique = false;
let mut found_isolated = false;
for (_, nodes) in communities {
if nodes.len() == 3
&& nodes.contains(&NodeId::from_index(0))
&& nodes.contains(&NodeId::from_index(1))
&& nodes.contains(&NodeId::from_index(2))
{
found_clique = true;
}
if nodes.len() == 1 && nodes.contains(&NodeId::from_index(3)) {
found_isolated = true;
}
}
assert!(found_clique);
assert!(found_isolated);
}
#[test]
fn test_deterministic_behavior() {
let graph = create_test_graph();
let config = CommunityConfig {
deterministic: true,
seed: Some(42),
..CommunityConfig::default()
};
let communities1 = graph
.detect_communities_with_config(config.clone())
.unwrap();
let communities2 = graph.detect_communities_with_config(config).unwrap();
assert_eq!(communities1, communities2);
}
#[test]
fn test_empty_graph() {
let graph: Graph<f64, String, String> = Graph::new(false);
let communities = graph.detect_communities(1.0).unwrap();
assert!(communities.is_empty());
}
#[test]
fn test_single_node() {
let mut graph: Graph<f64, String, String> = Graph::new(false);
let node = graph.add_node("Single".to_string());
let communities = graph.detect_communities(1.0).unwrap();
assert_eq!(communities.len(), 1);
assert_eq!(communities.get(&node.index()), Some(&vec![node]));
}
#[test]
fn test_every_node_lands_in_exactly_one_community() {
let graph = create_test_graph();
let communities = graph.detect_communities(1.0).unwrap();
let mut members: Vec<NodeId> = communities.values().flatten().copied().collect();
members.sort_unstable();
let mut expected: Vec<NodeId> = graph.node_ids().collect();
expected.sort_unstable();
assert_eq!(members, expected);
}
#[test]
fn test_directed_graph() {
let mut graph = Graph::new(true);
let node0 = graph.add_node("Node0".to_string());
let node1 = graph.add_node("Node1".to_string());
let node2 = graph.add_node("Node2".to_string());
graph
.add_edge(node0, node1, 1.0, "e01".to_string())
.unwrap();
graph
.add_edge(node1, node2, 1.0, "e12".to_string())
.unwrap();
graph
.add_edge(node2, node0, 1.0, "e20".to_string())
.unwrap();
let config = CommunityConfig {
gamma: 1.0,
deterministic: true,
seed: Some(42),
..CommunityConfig::default()
};
let communities = graph.detect_communities_with_config(config).unwrap();
assert_eq!(communities.len(), 1);
let nodes = communities.values().next().unwrap();
assert_eq!(nodes.len(), 3);
assert!(nodes.contains(&node0) && nodes.contains(&node1) && nodes.contains(&node2));
}
}