scirs2_graph/algorithms/community/
fluid.rs1use super::types::{CommunityResult, CommunityStructure};
4use crate::base::{EdgeWeight, Graph, IndexType, Node};
5use std::collections::HashMap;
6use std::hash::Hash;
7
8#[deprecated(
25 since = "0.1.0-beta.2",
26 note = "Use `fluid_communities_result` instead"
27)]
28#[allow(dead_code)]
29pub fn fluid_communities<N, E, Ix>(
30 graph: &Graph<N, E, Ix>,
31 num_communities: usize,
32 max_iterations: usize,
33) -> CommunityStructure<N>
34where
35 N: Node + Clone + Hash + Eq + std::fmt::Debug,
36 E: EdgeWeight + Into<f64> + Copy,
37 Ix: IndexType,
38{
39 let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
40 let n = nodes.len();
41
42 if n == 0 || num_communities == 0 {
43 return CommunityStructure {
44 node_communities: HashMap::new(),
45 modularity: 0.0,
46 };
47 }
48
49 let num_communities = num_communities.min(n);
50 let mut rng = scirs2_core::random::rng();
51
52 let mut node_fluids: HashMap<N, Vec<f64>> = HashMap::new();
54 for node in &nodes {
55 let mut fluids = vec![0.0; num_communities];
56 use scirs2_core::random::Rng;
58 let initial_fluid = rng.gen_range(0..num_communities);
59 fluids[initial_fluid] = 1.0;
60 node_fluids.insert(node.clone(), fluids);
61 }
62
63 for _iteration in 0..max_iterations {
65 let mut new_fluids: HashMap<N, Vec<f64>> = HashMap::new();
66
67 for node in &nodes {
68 let mut fluid_sums = vec![0.0; num_communities];
69
70 if let Ok(neighbors) = graph.neighbors(node) {
72 let neighbor_count = neighbors.len();
73 if neighbor_count > 0 {
74 for neighbor in neighbors {
75 if let Some(neighbor_fluids) = node_fluids.get(&neighbor) {
76 for (i, &fluid_amount) in neighbor_fluids.iter().enumerate() {
77 fluid_sums[i] += fluid_amount;
78 }
79 }
80 }
81
82 for fluid_sum in fluid_sums.iter_mut() {
84 *fluid_sum /= neighbor_count as f64;
85 }
86 } else {
87 if let Some(current_fluids) = node_fluids.get(node) {
89 fluid_sums = current_fluids.clone();
90 }
91 }
92 } else {
93 if let Some(current_fluids) = node_fluids.get(node) {
95 fluid_sums = current_fluids.clone();
96 }
97 }
98
99 let total: f64 = fluid_sums.iter().sum();
101 if total > 0.0 {
102 for fluid in fluid_sums.iter_mut() {
103 *fluid /= total;
104 }
105 } else {
106 use scirs2_core::random::Rng;
108 let random_fluid = rng.gen_range(0..num_communities);
109 fluid_sums[random_fluid] = 1.0;
110 }
111
112 new_fluids.insert(node.clone(), fluid_sums);
113 }
114
115 node_fluids = new_fluids;
117 }
118
119 let mut communities: HashMap<N, usize> = HashMap::new();
121 for (node, fluids) in &node_fluids {
122 let max_fluid_idx = fluids
123 .iter()
124 .enumerate()
125 .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
126 .map(|(i, _)| i)
127 .unwrap_or(0);
128 communities.insert(node.clone(), max_fluid_idx);
129 }
130
131 let mut community_map: HashMap<usize, usize> = HashMap::new();
133 let mut next_id = 0;
134 for &comm in communities.values() {
135 if let std::collections::hash_map::Entry::Vacant(e) = community_map.entry(comm) {
136 e.insert(next_id);
137 next_id += 1;
138 }
139 }
140
141 for (_, comm) in communities.iter_mut() {
143 *comm = community_map[comm];
144 }
145
146 let mod_score = super::modularity(graph, &communities);
148
149 CommunityStructure {
150 node_communities: communities,
151 modularity: mod_score,
152 }
153}
154
155#[allow(dead_code)]
196pub fn fluid_communities_result<N, E, Ix>(
197 graph: &Graph<N, E, Ix>,
198 num_communities: usize,
199 max_iterations: usize,
200) -> CommunityResult<N>
201where
202 N: Node + Clone + Hash + Eq + std::fmt::Debug,
203 E: EdgeWeight + Into<f64> + Copy,
204 Ix: IndexType,
205{
206 #[allow(deprecated)]
207 let structure = fluid_communities(graph, num_communities, max_iterations);
208 let mut result = CommunityResult::from_node_map(structure.node_communities.clone());
209
210 let mod_score = super::modularity(graph, &structure.node_communities);
212 result.quality_score = Some(mod_score);
213 result
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219 use crate::error::Result as GraphResult;
220 use crate::generators::create_graph;
221
222 #[test]
223 fn test_fluid_communities() -> GraphResult<()> {
224 let mut graph = create_graph::<i32, f64>();
226
227 graph.add_edge(0, 1, 1.0)?;
229 graph.add_edge(1, 2, 1.0)?;
230 graph.add_edge(2, 0, 1.0)?;
231
232 graph.add_edge(3, 4, 1.0)?;
234 graph.add_edge(4, 5, 1.0)?;
235 graph.add_edge(5, 3, 1.0)?;
236
237 graph.add_edge(2, 3, 0.1)?;
239
240 let result = fluid_communities_result(&graph, 2, 30);
241
242 assert_eq!(result.node_communities.len(), 6);
244
245 assert!(result.num_communities <= 2);
247 assert!(result.num_communities > 0);
248
249 assert!(result.quality_score.is_some());
251
252 Ok(())
253 }
254
255 #[test]
256 fn test_fluid_communities_empty_graph() {
257 let graph = create_graph::<i32, f64>();
258 let result = fluid_communities_result(&graph, 2, 10);
259
260 assert!(result.node_communities.is_empty());
261 assert_eq!(result.quality_score.unwrap_or(0.0), 0.0);
262 }
263
264 #[test]
265 fn test_fluid_communities_single_node() -> GraphResult<()> {
266 let mut graph = create_graph::<&str, f64>();
267 graph.add_node("A");
268
269 let result = fluid_communities_result(&graph, 1, 10);
270
271 assert_eq!(result.node_communities.len(), 1);
272 assert!(result.node_communities.contains_key(&"A"));
273 assert_eq!(result.node_communities[&"A"], 0);
274
275 Ok(())
276 }
277}