4343 self . neighboring_clusters . resize ( size, 0 ) ;
4444 }
4545
46+ fn calculate_modularity_gain (
47+ & self ,
48+ node_weight : T ,
49+ k_i_in : T ,
50+ cluster_tot : T ,
51+ total_edge_weight_2m : T ,
52+ ) -> T {
53+ // ΔQ = [Σin + ki,in]/2m - [(Σtot + ki)/2m]² - [Σin/2m - (Σtot/2m)² - (ki/2m)²]
54+ // Simplifying: ΔQ = ki,in/2m - ki*Σtot/2m² - ki²/2m²
55+ // Further: ΔQ = (ki,in - ki*Σtot/2m - ki²/2m) / 2m
56+
57+ let two_m = total_edge_weight_2m;
58+ let term1 = k_i_in / two_m;
59+ let term2 = ( node_weight * cluster_tot * self . resolution ) / ( two_m * two_m) ;
60+
61+ term1 - term2
62+ }
63+
4664 pub fn iterate < C , R > (
4765 & mut self ,
4866 network : & Network < T , T > ,
@@ -53,119 +71,126 @@ where
5371 C : NetworkGrouping ,
5472 R : RngCore ,
5573 {
56- let mut update = false ;
5774 let node_count = network. nodes ( ) ;
58- self . ensure_capacity ( node_count) ;
75+ if node_count == 0 {
76+ return false ;
77+ }
5978
60- self . cluster_weights . fill ( T :: zero ( ) ) ;
61- self . nodes_per_cluster . fill ( 0 ) ;
62- self . edge_weight_per_cluster . fill ( T :: zero ( ) ) ;
79+ self . ensure_capacity ( node_count) ;
80+
81+ // Initialize cluster weights and counts
82+ self . cluster_weights [ ..node_count] . fill ( T :: zero ( ) ) ;
83+ self . nodes_per_cluster [ ..node_count] . fill ( 0 ) ;
84+ self . edge_weight_per_cluster [ ..node_count] . fill ( T :: zero ( ) ) ;
6385
86+ // Calculate initial cluster weights
6487 for i in 0 ..node_count {
6588 let cluster = clustering. get_group ( i) ;
6689 self . cluster_weights [ cluster] += network. weight ( i) ;
6790 self . nodes_per_cluster [ cluster] += 1 ;
6891 }
6992
93+ // Find unused clusters
7094 let mut num_unused_clusters = 0 ;
7195 for i in ( 0 ..node_count) . rev ( ) {
7296 if self . nodes_per_cluster [ i] == 0 {
7397 self . unused_clusters [ num_unused_clusters] = i;
7498 num_unused_clusters += 1 ;
7599 }
76100 }
77- //println!("Number of unused clusters: {:?}", num_unused_clusters);
78101
102+ // Shuffle node order once per iteration
79103 self . node_order . clear ( ) ;
80104 self . node_order . extend ( 0 ..node_count) ;
81105 self . node_order . shuffle ( rng) ;
82106
83- let total_edge_weight =
84- T :: from_f64 ( network. get_total_edge_weight ( ) . to_f64 ( ) . unwrap ( ) ) . unwrap ( ) ;
85- let mut num_unstable_nodes = node_count;
86- let mut i = 0 ;
87-
88- //println!("Total edge weight: {:?}, num unstable nodes: {:?}, i = {:?}", total_edge_weight.to_f64().unwrap(), num_unstable_nodes, i);
107+ let total_edge_weight_2m = network. get_total_edge_weight ( ) * T :: from ( 2 ) . unwrap ( ) ;
108+ let mut global_update = false ;
89109
90- while num_unstable_nodes > 0 {
91- //println!("ITERATION | Total edge weight: {:?}, num unstable nodes: {:?}, i = {:?}", total_edge_weight.to_f64().unwrap(), num_unstable_nodes, i);
92- let node = self . node_order [ i] ;
93- let current_cluster = clustering. get_group ( node) ;
94- //println!("ITERATION | Node: {:?}, Current Cluster: {:?}", node, current_cluster);
110+ // Keep iterating until no improvements are found
111+ let mut local_improvement = true ;
112+ while local_improvement {
113+ local_improvement = false ;
95114
96- // Remove node from current cluster
97- let node_weight = T :: from_f64 ( network. weight ( node) . to_f64 ( ) . unwrap ( ) ) . unwrap ( ) ;
98- self . cluster_weights [ current_cluster] -= node_weight;
99- self . nodes_per_cluster [ current_cluster] -= 1 ;
115+ for & node in & self . node_order {
116+ let current_cluster = clustering. get_group ( node) ;
117+ let node_weight = network. weight ( node) ;
100118
101- if self . nodes_per_cluster [ current_cluster] == 0 {
102- self . unused_clusters [ num_unused_clusters] = current_cluster;
103- num_unused_clusters += 1 ;
104- //println!("ITERATION | Nodes per cluster == 0, num unused clusters {:?}", num_unused_clusters);
105- }
119+ // Remove node from current cluster
120+ self . cluster_weights [ current_cluster] -= node_weight;
121+ self . nodes_per_cluster [ current_cluster] -= 1 ;
106122
107- // Find neighboring clusters
108- self . neighboring_clusters [ 0 ] = self . unused_clusters [ num_unused_clusters - 1 ] ;
109- let mut num_neighboring_clusters = 1 ;
110-
111- for ( target, weight) in network. neighbors ( node) {
112- let neighbor_cluster = clustering. get_group ( target) ;
113- let edge_weight = T :: from_f64 ( weight. to_f64 ( ) . unwrap ( ) ) . unwrap ( ) ;
114- //println!("ITERATION | FORLOOP | target: {:?}, neighbor cluster: {:?}, edge_weight: {:?}", target, neighbor_cluster, edge_weight.to_f64().unwrap());
115- if self . edge_weight_per_cluster [ neighbor_cluster] == T :: zero ( ) {
116- //println!("ITERATION | FORLOOP | Is T::zero, edge_weight_per_cluster");
117- self . neighboring_clusters [ num_neighboring_clusters] = neighbor_cluster;
118- num_neighboring_clusters += 1 ;
123+ if self . nodes_per_cluster [ current_cluster] == 0 {
124+ self . unused_clusters [ num_unused_clusters] = current_cluster;
125+ num_unused_clusters += 1 ;
119126 }
120- self . edge_weight_per_cluster [ neighbor_cluster] += edge_weight;
121- }
122127
123- // Find best cluster
124- let mut best_cluster = current_cluster;
125- let mut max_quality_increment = self . edge_weight_per_cluster [ current_cluster]
126- - ( node_weight * self . cluster_weights [ current_cluster] * self . resolution )
127- / ( T :: from_f64 ( 2.0 ) . unwrap ( ) * total_edge_weight) ;
128-
129- //println!("ITERATION | Best Cluster {:?} Max Quality Increment {:?}", best_cluster, max_quality_increment.to_f64().unwrap());
130-
131- for & cluster in & self . neighboring_clusters [ ..num_neighboring_clusters] {
132- let quality_increment = self . edge_weight_per_cluster [ cluster]
133- - ( node_weight * self . cluster_weights [ cluster] * self . resolution )
134- / num_traits:: Float :: powi ( T :: from_f64 ( 2.0 ) . unwrap ( ) * total_edge_weight, 2 ) ;
135- //println!("ITERATION | Cluster {:?} Quality Increment {:?}", cluster, quality_increment.to_f64().unwrap());
136- if quality_increment > max_quality_increment
137- || ( quality_increment == max_quality_increment && cluster < best_cluster)
138- {
139- best_cluster = cluster;
140- max_quality_increment = quality_increment;
141- //println!("ITERATION | Passes if for quality improvement")
128+ // Clear edge weights from previous iteration
129+ self . edge_weight_per_cluster [ ..node_count] . fill ( T :: zero ( ) ) ;
130+
131+ // Find neighboring clusters and calculate edge weights
132+ self . neighboring_clusters [ 0 ] = if num_unused_clusters > 0 {
133+ self . unused_clusters [ num_unused_clusters - 1 ]
134+ } else {
135+ current_cluster // fallback
136+ } ;
137+ let mut num_neighboring_clusters = 1 ;
138+
139+ for ( target, weight) in network. neighbors ( node) {
140+ let neighbor_cluster = clustering. get_group ( target) ;
141+
142+ if self . edge_weight_per_cluster [ neighbor_cluster] == T :: zero ( ) &&
143+ neighbor_cluster != self . neighboring_clusters [ 0 ] {
144+ self . neighboring_clusters [ num_neighboring_clusters] = neighbor_cluster;
145+ num_neighboring_clusters += 1 ;
146+ }
147+ self . edge_weight_per_cluster [ neighbor_cluster] += weight;
142148 }
143- self . edge_weight_per_cluster [ cluster] = T :: zero ( ) ;
144- }
145149
146- // Update cluster assignment
147- self . cluster_weights [ best_cluster] += node_weight;
148- self . nodes_per_cluster [ best_cluster] += 1 ;
150+ // Find best cluster
151+ let mut best_cluster = current_cluster;
152+ let mut max_quality_increment = self . calculate_modularity_gain (
153+ node_weight,
154+ self . edge_weight_per_cluster [ current_cluster] ,
155+ self . cluster_weights [ current_cluster] ,
156+ total_edge_weight_2m,
157+ ) ;
158+
159+ for & cluster in & self . neighboring_clusters [ ..num_neighboring_clusters] {
160+ let quality_increment = self . calculate_modularity_gain (
161+ node_weight,
162+ self . edge_weight_per_cluster [ cluster] ,
163+ self . cluster_weights [ cluster] ,
164+ total_edge_weight_2m,
165+ ) ;
166+
167+ if quality_increment > max_quality_increment ||
168+ ( quality_increment == max_quality_increment && cluster < best_cluster) {
169+ best_cluster = cluster;
170+ max_quality_increment = quality_increment;
171+ }
172+ }
149173
150- if best_cluster == self . unused_clusters [ num_unused_clusters - 1 ] {
151- num_unused_clusters -= 1 ;
152- }
174+ // Update cluster assignment
175+ self . cluster_weights [ best_cluster ] += node_weight ;
176+ self . nodes_per_cluster [ best_cluster ] += 1 ;
153177
154- num_unstable_nodes -= 1 ;
178+ if best_cluster == self . unused_clusters [ num_unused_clusters - 1 ] {
179+ num_unused_clusters -= 1 ;
180+ }
155181
156- if best_cluster != current_cluster {
157- clustering. set_group ( node, best_cluster) ;
158- update = true ;
182+ if best_cluster != current_cluster {
183+ clustering. set_group ( node, best_cluster) ;
184+ local_improvement = true ;
185+ global_update = true ;
186+ }
159187 }
160-
161- i = ( i + 1 ) % node_count;
162- //println!("END: {:?} best cluster {:?} num unstable nodes {:?} num unused clusters {:?}", i, best_cluster, num_unstable_nodes, num_unused_clusters)
163188 }
164189
165- if update {
190+ if global_update {
166191 clustering. normalize_groups ( ) ;
167192 }
168193
169- update
194+ global_update
170195 }
171196}
0 commit comments