1use crate::graph::data::GraphData;
13use crate::graph::GraphDataBuilder;
14use crate::partition::Partition;
15use rand::rngs::StdRng;
16use rand::seq::SliceRandom;
17use rand::SeedableRng;
18use rustc_hash::FxHashMap;
19
20#[derive(Debug, Clone, PartialEq)]
27pub struct FlowData {
28 pub flow: f64,
30 pub enter_flow: f64,
32 pub exit_flow: f64,
34 pub teleport_flow: f64,
36}
37
38#[derive(Debug, Clone, PartialEq)]
43pub struct DeltaFlow {
44 pub module: usize,
46 pub delta_exit: f64,
48 pub delta_enter: f64,
50}
51
52#[derive(Debug, Clone, PartialEq)]
58pub struct ModuleFlowData {
59 pub flow: f64,
61 pub enter_flow: f64,
63 pub exit_flow: f64,
65}
66
67#[derive(Debug, Clone)]
80pub struct MapEquation {
81 pub module_data: Vec<ModuleFlowData>,
83
84 pub enter_flow: f64,
87 pub enter_flow_log: f64,
89 pub enter_log_enter: f64,
91 pub exit_log_exit: f64,
93 pub flow_log_flow: f64,
95 pub node_flow_log: f64,
97 pub exit_network_flow: f64,
99}
100
101#[derive(Debug, Clone, PartialEq)]
106pub struct InfomapConfig {
107 pub seed: Option<u64>,
109 pub max_iterations: usize,
111 pub teleportation_rate: f64,
113 pub num_trials: usize,
115 pub tolerance: f64,
117}
118
119impl Default for InfomapConfig {
120 fn default() -> Self {
121 Self {
122 seed: None,
123 max_iterations: 100,
124 teleportation_rate: 0.15,
125 num_trials: 10,
126 tolerance: 1e-10,
127 }
128 }
129}
130
131impl InfomapConfig {
132 #[must_use = "constructor returns a new instance"]
134 pub fn new(seed: Option<u64>) -> Self {
135 Self {
136 seed,
137 ..Default::default()
138 }
139 }
140}
141
142#[derive(Debug, Clone, PartialEq)]
144pub struct InfomapOutput {
145 pub partition: Partition,
147 pub codelength: f64,
149 pub num_levels: usize,
151 pub iterations: usize,
153}
154
155#[must_use]
162pub fn plogp(x: f64) -> f64 {
163 if x > 0.0 {
164 x * x.log2()
165 } else {
166 0.0
167 }
168}
169
170#[must_use]
191pub fn compute_flow(
192 graph: &GraphData,
193 teleport_rate: f64,
194 tolerance: f64,
195 max_iter: usize,
196) -> Vec<FlowData> {
197 let n = graph.node_count();
198 if n == 0 {
199 return Vec::new();
200 }
201
202 let mut flow = vec![1.0 / n as f64; n];
204 let teleport_dist = 1.0 / n as f64; for _ in 0..max_iter {
207 let mut flow_new = vec![0.0; n];
208
209 for (j, f) in flow.iter().enumerate() {
213 let out_deg = graph.out_degree_of(j);
214 if out_deg > 0.0 {
215 for (target, weight) in graph.out_neighbors(j) {
217 flow_new[target] += f * (weight / out_deg);
218 }
219 } else {
220 for item in flow_new.iter_mut() {
222 *item += f * teleport_dist;
223 }
224 }
225 }
226
227 let damping = 1.0 - teleport_rate;
233 for item in flow_new.iter_mut() {
234 *item = teleport_rate * teleport_dist + damping * *item;
235 }
236
237 let mut max_diff = 0.0;
239 for i in 0..n {
240 let diff = (flow_new[i] - flow[i]).abs();
241 if diff > max_diff {
242 max_diff = diff;
243 }
244 }
245
246 flow = flow_new;
247
248 if max_diff < tolerance {
249 break;
250 }
251 }
252
253 flow.into_iter()
255 .map(|f| FlowData {
256 flow: f,
257 enter_flow: 0.0,
258 exit_flow: 0.0,
259 teleport_flow: 0.0,
260 })
261 .collect()
262}
263
264impl MapEquation {
267 #[must_use]
273 pub fn new(node_flows: &[FlowData], exit_network_flow: f64) -> Self {
274 let num_modules = node_flows.len();
275 let mut module_data = Vec::with_capacity(num_modules);
276
277 let mut enter_flow = 0.0_f64;
278 let mut enter_log_enter = 0.0_f64;
279 let mut exit_log_exit = 0.0_f64;
280 let mut flow_log_flow = 0.0_f64;
281 let mut node_flow_log = 0.0_f64;
282
283 for fd in node_flows {
284 let module = ModuleFlowData {
285 flow: fd.flow,
286 enter_flow: fd.enter_flow,
287 exit_flow: fd.exit_flow,
288 };
289
290 enter_flow += module.enter_flow;
291 enter_log_enter += plogp(module.enter_flow);
292 exit_log_exit += plogp(module.exit_flow);
293 flow_log_flow += plogp(module.exit_flow + module.flow);
294 node_flow_log += plogp(fd.flow);
295
296 module_data.push(module);
297 }
298
299 enter_flow += exit_network_flow;
300 let enter_flow_log = plogp(enter_flow);
301
302 Self {
303 module_data,
304 enter_flow,
305 enter_flow_log,
306 enter_log_enter,
307 exit_log_exit,
308 flow_log_flow,
309 node_flow_log,
310 exit_network_flow,
311 }
312 }
313
314 #[must_use]
319 pub fn codelength(&self) -> f64 {
320 let index_codelength =
321 self.enter_flow_log - self.enter_log_enter - plogp(self.exit_network_flow);
322 let module_codelength = -self.exit_log_exit + self.flow_log_flow - self.node_flow_log;
323 index_codelength + module_codelength
324 }
325
326 #[must_use]
340 pub fn get_delta_codelength_on_moving_node(
341 &self,
342 current: &FlowData,
343 old_module: usize,
344 new_module: usize,
345 old_delta: &DeltaFlow,
346 new_delta: &DeltaFlow,
347 ) -> f64 {
348 let dee_old = old_delta.delta_enter + old_delta.delta_exit;
349 let dee_new = new_delta.delta_enter + new_delta.delta_exit;
350
351 let old_data = &self.module_data[old_module];
352 let new_data = &self.module_data[new_module];
353
354 let delta_enter = plogp(self.enter_flow + dee_old - dee_new) - self.enter_flow_log;
356
357 let delta_enter_log_enter = -plogp(old_data.enter_flow)
359 - plogp(new_data.enter_flow)
360 + plogp(old_data.enter_flow - current.enter_flow + dee_old)
361 + plogp(new_data.enter_flow + current.enter_flow - dee_new);
362
363 let delta_exit_log_exit = -plogp(old_data.exit_flow)
365 - plogp(new_data.exit_flow)
366 + plogp(old_data.exit_flow - current.exit_flow + dee_old)
367 + plogp(new_data.exit_flow + current.exit_flow - dee_new);
368
369 let old_total = old_data.exit_flow + old_data.flow;
371 let new_total = new_data.exit_flow + new_data.flow;
372 let delta_flow_log_flow = -plogp(old_total)
373 - plogp(new_total)
374 + plogp(old_total - current.exit_flow - current.flow + dee_old)
375 + plogp(new_total + current.exit_flow + current.flow - dee_new);
376
377 delta_enter - delta_enter_log_enter - delta_exit_log_exit + delta_flow_log_flow
379 }
380
381 pub fn update_codelength_on_moving_node(
386 &mut self,
387 current: &FlowData,
388 old_module: usize,
389 new_module: usize,
390 old_delta: &DeltaFlow,
391 new_delta: &DeltaFlow,
392 ) {
393 let dee_old = old_delta.delta_enter + old_delta.delta_exit;
394 let dee_new = new_delta.delta_enter + new_delta.delta_exit;
395
396 let old_data = &self.module_data[old_module];
397 let new_data = &self.module_data[new_module];
398 self.enter_log_enter -= plogp(old_data.enter_flow) + plogp(new_data.enter_flow);
399 self.exit_log_exit -= plogp(old_data.exit_flow) + plogp(new_data.exit_flow);
400 self.flow_log_flow -=
401 plogp(old_data.exit_flow + old_data.flow) + plogp(new_data.exit_flow + new_data.flow);
402
403 self.remove_flow_from_module(old_module, current);
404 self.add_flow_to_module(new_module, current);
405
406 self.module_data[old_module].enter_flow += dee_old;
409 self.module_data[old_module].exit_flow += dee_old;
410 self.module_data[new_module].enter_flow -= dee_new;
411 self.module_data[new_module].exit_flow -= dee_new;
412
413 self.enter_flow += dee_old - dee_new;
414 self.enter_flow_log = plogp(self.enter_flow);
415
416 let old_data = &self.module_data[old_module];
417 let new_data = &self.module_data[new_module];
418 self.enter_log_enter += plogp(old_data.enter_flow) + plogp(new_data.enter_flow);
419 self.exit_log_exit += plogp(old_data.exit_flow) + plogp(new_data.exit_flow);
420 self.flow_log_flow +=
421 plogp(old_data.exit_flow + old_data.flow) + plogp(new_data.exit_flow + new_data.flow);
422 }
423
424 pub fn add_flow_to_module(&mut self, module: usize, flow: &FlowData) {
426 self.module_data[module].flow += flow.flow;
427 self.module_data[module].enter_flow += flow.enter_flow;
428 self.module_data[module].exit_flow += flow.exit_flow;
429 }
430
431 pub fn remove_flow_from_module(&mut self, module: usize, flow: &FlowData) {
433 self.module_data[module].flow -= flow.flow;
434 self.module_data[module].enter_flow -= flow.enter_flow;
435 self.module_data[module].exit_flow -= flow.exit_flow;
436 }
437
438 pub fn recalc_terms(&mut self) {
440 self.enter_flow_log = plogp(self.enter_flow);
441 self.enter_log_enter = 0.0;
442 self.exit_log_exit = 0.0;
443 self.flow_log_flow = 0.0;
444 for module in &self.module_data {
445 self.enter_log_enter += plogp(module.enter_flow);
446 self.exit_log_exit += plogp(module.exit_flow);
447 self.flow_log_flow += plogp(module.exit_flow + module.flow);
448 }
449 }
450}
451
452#[must_use]
454pub fn calc_codelength(map_eq: &MapEquation) -> f64 {
455 map_eq.codelength()
456}
457
458fn populate_boundary_flows(
473 graph: &GraphData,
474 flow_data: &mut [FlowData],
475 teleport_rate: f64,
476) {
477 let n = graph.node_count();
478 let damping = 1.0 - teleport_rate;
479
480 let mut enter_flow = vec![0.0; n];
481 let mut exit_flow = vec![0.0; n];
482
483 for u in 0..n {
484 let out_deg = graph.out_degree_of(u);
485 if out_deg > 0.0 {
486 for (v, w) in graph.out_neighbors(u) {
487 let lf = damping * (w / out_deg) * flow_data[u].flow;
488 exit_flow[u] += lf;
489 enter_flow[v] += lf;
490 }
491 }
492 }
493
494 for i in 0..n {
495 flow_data[i].enter_flow = enter_flow[i];
496 flow_data[i].exit_flow = exit_flow[i];
497 }
498}
499
500pub fn run_local_moving(
520 map_eq: &mut MapEquation,
521 flow_data: &[FlowData],
522 graph: &GraphData,
523 partition: &mut [usize],
524 rng: &mut StdRng,
525) -> bool {
526 let n = graph.node_count();
527 if n <= 1 {
528 return false;
529 }
530
531 let mut changed = false;
532
533 loop {
534 let mut improved = false;
535
536 let mut order: Vec<usize> = (0..n).collect();
537 order.shuffle(rng);
538
539 for &node in &order {
540 let old_module = partition[node];
541 let node_exit = flow_data[node].exit_flow;
542 let node_out_deg = graph.out_degree_of(node);
543
544 let mut delta_exit_map: FxHashMap<usize, f64> = FxHashMap::default();
547 let mut delta_enter_map: FxHashMap<usize, f64> = FxHashMap::default();
548
549 if graph.is_directed() {
550 if node_out_deg > 0.0 {
552 for (v, w) in graph.out_neighbors(node) {
553 let target_module = partition[v];
554 let lf = (w / node_out_deg) * node_exit;
555 *delta_exit_map.entry(target_module).or_default() += lf;
556 }
557 }
558 for (v, w) in graph.in_neighbors(node) {
560 let source_module = partition[v];
561 let v_out_deg = graph.out_degree_of(v);
562 if v_out_deg > 0.0 {
563 let lf = (w / v_out_deg) * flow_data[v].exit_flow;
564 *delta_enter_map.entry(source_module).or_default() += lf;
565 }
566 }
567 } else {
568 if node_out_deg > 0.0 {
570 for (v, w) in graph.out_neighbors(node) {
571 let target_module = partition[v];
572 let exit_lf = (w / node_out_deg) * node_exit;
574 *delta_exit_map.entry(target_module).or_default() += exit_lf;
575 let v_out_deg = graph.out_degree_of(v);
577 if v_out_deg > 0.0 {
578 let enter_lf = (w / v_out_deg) * flow_data[v].exit_flow;
579 *delta_enter_map.entry(target_module).or_default() += enter_lf;
580 }
581 }
582 }
583 }
584
585 let mut all_modules: FxHashMap<usize, ()> = FxHashMap::default();
587 for &m in delta_exit_map.keys() {
588 all_modules.insert(m, ());
589 }
590 for &m in delta_enter_map.keys() {
591 all_modules.insert(m, ());
592 }
593
594 let mut delta_flows: FxHashMap<usize, DeltaFlow> = FxHashMap::default();
595 for &m in all_modules.keys() {
596 delta_flows.insert(
597 m,
598 DeltaFlow {
599 module: m,
600 delta_exit: delta_exit_map.get(&m).copied().unwrap_or(0.0),
601 delta_enter: delta_enter_map.get(&m).copied().unwrap_or(0.0),
602 },
603 );
604 }
605
606 let self_delta = delta_flows.remove(&old_module).unwrap_or(DeltaFlow {
608 module: old_module,
609 delta_exit: 0.0,
610 delta_enter: 0.0,
611 });
612
613 let mut best_module = old_module;
616 let mut best_delta = 0.0_f64; for (&new_module, new_delta) in &delta_flows {
619 if new_module == old_module {
620 continue;
621 }
622 let dl = map_eq.get_delta_codelength_on_moving_node(
623 &flow_data[node],
624 old_module,
625 new_module,
626 &self_delta,
627 new_delta,
628 );
629 if dl < best_delta {
630 best_delta = dl;
631 best_module = new_module;
632 }
633 }
634
635 let new_module_id = map_eq.module_data.len();
637 map_eq.module_data.push(ModuleFlowData {
638 flow: 0.0,
639 enter_flow: 0.0,
640 exit_flow: 0.0,
641 });
642 let empty_delta = DeltaFlow {
643 module: new_module_id,
644 delta_exit: 0.0,
645 delta_enter: 0.0,
646 };
647 let dl = map_eq.get_delta_codelength_on_moving_node(
648 &flow_data[node],
649 old_module,
650 new_module_id,
651 &self_delta,
652 &empty_delta,
653 );
654 if dl < best_delta {
655 best_module = new_module_id;
656 } else {
658 map_eq.module_data.pop();
659 }
660
661 if best_module != old_module {
664 let actual_new_delta = if best_module == new_module_id {
665 empty_delta
666 } else {
667 delta_flows
668 .get(&best_module)
669 .cloned()
670 .unwrap_or(DeltaFlow {
671 module: best_module,
672 delta_exit: 0.0,
673 delta_enter: 0.0,
674 })
675 };
676
677 map_eq.update_codelength_on_moving_node(
678 &flow_data[node],
679 old_module,
680 best_module,
681 &self_delta,
682 &actual_new_delta,
683 );
684 partition[node] = best_module;
685 improved = true;
686 changed = true;
687 }
688 }
689
690 if !improved {
691 break;
692 }
693 }
694
695 changed
696}
697
698pub fn aggregate_graph(graph: &GraphData, partition: &[usize]) -> GraphData {
709 let n = graph.node_count();
710 if n == 0 {
711 return GraphDataBuilder::new(0).build().unwrap();
712 }
713
714 let mut module_to_super: FxHashMap<usize, usize> = FxHashMap::default();
716 let mut orig_to_super = vec![0usize; n];
717 let mut num_super = 0usize;
718 for i in 0..n {
719 let m = partition[i];
720 let super_id = *module_to_super.entry(m).or_insert_with(|| {
721 let id = num_super;
722 num_super += 1;
723 id
724 });
725 orig_to_super[i] = super_id;
726 }
727
728 if num_super == 0 {
729 return GraphDataBuilder::new(0).build().unwrap();
730 }
731
732 let mut edge_map: FxHashMap<(usize, usize), f64> = FxHashMap::default();
734
735 if graph.is_directed() {
736 for u in 0..n {
737 for (v, w) in graph.out_neighbors(u) {
738 let su = orig_to_super[u];
739 let sv = orig_to_super[v];
740 *edge_map.entry((su, sv)).or_default() += w;
741 }
742 }
743 } else {
744 for u in 0..n {
745 for (v, w) in graph.out_neighbors(u) {
746 if u == v {
747 let su = orig_to_super[u];
749 *edge_map.entry((su, su)).or_default() += w;
750 } else if v > u {
751 let su = orig_to_super[u];
753 let sv = orig_to_super[v];
754 let key = if su <= sv { (su, sv) } else { (sv, su) };
755 *edge_map.entry(key).or_default() += w;
756 }
757 }
758 }
759 }
760
761 let mut builder = GraphDataBuilder::new(num_super);
763 if graph.is_directed() {
764 builder = builder.directed();
765 }
766 for ((u, v), w) in &edge_map {
767 builder.add_edge(*u, *v, *w).unwrap();
768 }
769
770 let mut super_weights = vec![0.0f64; num_super];
772 for i in 0..n {
773 super_weights[orig_to_super[i]] += graph.node_weight(i);
774 }
775 for (node, &w) in super_weights.iter().enumerate() {
776 if (w - 1.0).abs() > f64::EPSILON {
777 builder.set_node_weight(node, w).unwrap();
778 }
779 }
780
781 builder.build().unwrap()
782}
783
784pub fn run_multi_level(
797 graph: &GraphData,
798 flow_data: &[FlowData],
799 config: &InfomapConfig,
800) -> (Vec<usize>, f64) {
801 run_multi_level_impl(graph, flow_data, config, 0)
802}
803
804fn run_multi_level_impl(
806 graph: &GraphData,
807 flow_data: &[FlowData],
808 config: &InfomapConfig,
809 depth: usize,
810) -> (Vec<usize>, f64) {
811 let n = graph.node_count();
812 if n == 0 {
813 return (Vec::new(), 0.0);
814 }
815 if n == 1 {
816 let map_eq = MapEquation::new(
817 &[FlowData {
818 flow: flow_data[0].flow,
819 enter_flow: 0.0,
820 exit_flow: 0.0,
821 teleport_flow: 0.0,
822 }],
823 0.0,
824 );
825 return (vec![0], map_eq.codelength());
826 }
827
828 let mut enriched = flow_data.to_vec();
830 if enriched.iter().all(|fd| fd.enter_flow == 0.0 && fd.exit_flow == 0.0) {
831 populate_boundary_flows(graph, &mut enriched, config.teleportation_rate);
832 }
833
834 let mut partition: Vec<usize> = (0..n).collect();
836 let mut map_eq = MapEquation::new(&enriched, 0.0);
837 let mut rng = StdRng::seed_from_u64(config.seed.unwrap_or(0).wrapping_add(depth as u64));
838
839 let improved = run_local_moving(&mut map_eq, &enriched, graph, &mut partition, &mut rng);
840
841 let max_mod = *partition.iter().max().unwrap_or(&0);
843 let mut renumber = vec![usize::MAX; max_mod + 1];
844 let mut next_id = 0usize;
845 for p in partition.iter_mut() {
846 if renumber[*p] == usize::MAX {
847 renumber[*p] = next_id;
848 next_id += 1;
849 }
850 *p = renumber[*p];
851 }
852
853 if improved && depth < config.max_iterations {
855 let aggregate = aggregate_graph(graph, &partition);
856 if aggregate.node_count() < n {
857 let agg_flow = compute_flow(
859 &aggregate,
860 config.teleportation_rate,
861 config.tolerance,
862 config.max_iterations,
863 );
864 let (agg_partition, _agg_codelength) =
865 run_multi_level_impl(&aggregate, &agg_flow, config, depth + 1);
866
867 for i in 0..n {
871 partition[i] = agg_partition[partition[i]];
872 }
873 }
874 }
875
876 let max_mod = *partition.iter().max().unwrap_or(&0);
878 let mut mapping = vec![usize::MAX; max_mod + 1];
879 let mut next_id = 0usize;
880 for p in partition.iter_mut() {
881 if mapping[*p] == usize::MAX {
882 mapping[*p] = next_id;
883 next_id += 1;
884 }
885 *p = mapping[*p];
886 }
887
888 let cl = compute_codelength_for_partition(graph, &enriched, &partition, config.teleportation_rate);
891
892 (partition, cl)
893}
894
895fn compute_codelength_for_partition(
900 graph: &GraphData,
901 flow_data: &[FlowData],
902 partition: &[usize],
903 teleport_rate: f64,
904) -> f64 {
905 let n = graph.node_count();
906 if n == 0 {
907 return 0.0;
908 }
909
910 let damping = 1.0 - teleport_rate;
911 let num_modules = *partition.iter().max().unwrap_or(&0) + 1;
912
913 let mut module_flow = vec![0.0; num_modules];
915
916 for i in 0..n {
917 module_flow[partition[i]] += flow_data[i].flow;
918 }
919
920 let mut module_enter = vec![0.0; num_modules];
922 let mut module_exit = vec![0.0; num_modules];
923
924 for u in 0..n {
925 let u_out_deg = graph.out_degree_of(u);
926 if u_out_deg > 0.0 {
927 for (v, w) in graph.out_neighbors(u) {
928 let mu = partition[u];
929 let mv = partition[v];
930 if mu != mv {
931 let lf = damping * (w / u_out_deg) * flow_data[u].flow;
932 module_exit[mu] += lf;
933 module_enter[mv] += lf;
934 }
935 }
936 }
937 }
938
939 let module_data: Vec<ModuleFlowData> = (0..num_modules)
941 .map(|m| ModuleFlowData {
942 flow: module_flow[m],
943 enter_flow: module_enter[m],
944 exit_flow: module_exit[m],
945 })
946 .collect();
947
948 let enter_flow: f64 = module_enter.iter().sum();
949 let node_flow_log: f64 = flow_data.iter().map(|fd| plogp(fd.flow)).sum();
950
951 let map_eq = MapEquation {
952 module_data,
953 enter_flow,
954 enter_flow_log: plogp(enter_flow),
955 enter_log_enter: module_enter.iter().map(|&x| plogp(x)).sum(),
956 exit_log_exit: module_exit.iter().map(|&x| plogp(x)).sum(),
957 flow_log_flow: (0..num_modules)
958 .map(|m| plogp(module_exit[m] + module_flow[m]))
959 .sum(),
960 node_flow_log,
961 exit_network_flow: 0.0,
962 };
963
964 map_eq.codelength()
965}
966
967#[must_use]
980pub fn compute_node_flows(
981 graph: &GraphData,
982 flow_data: &[FlowData],
983 teleport_rate: f64,
984) -> Vec<FlowData> {
985 let n = flow_data.len();
986 let mut result = flow_data.to_vec();
987 let damping = 1.0 - teleport_rate;
988
989 let mut enter_flow = vec![0.0; n];
990 let mut exit_flow = vec![0.0; n];
991
992 for u in 0..n {
993 let out_deg = graph.out_degree_of(u);
994 if out_deg > 0.0 {
995 for (v, w) in graph.out_neighbors(u) {
996 let link_flow = damping * (w / out_deg) * result[u].flow;
997 exit_flow[u] += link_flow;
998 enter_flow[v] += link_flow;
999 }
1000 }
1001 }
1002
1003 for i in 0..n {
1004 result[i].enter_flow = enter_flow[i];
1005 result[i].exit_flow = exit_flow[i];
1006 }
1007
1008 result
1009}
1010
1011pub fn fine_tuning(
1021 map_eq: &mut MapEquation,
1022 flow_data: &[FlowData],
1023 graph: &GraphData,
1024 partition: &mut [usize],
1025 rng: &mut StdRng,
1026) -> bool {
1027 run_local_moving(map_eq, flow_data, graph, partition, rng)
1028}
1029
1030pub fn coarse_tuning(
1038 map_eq: &mut MapEquation,
1039 flow_data: &[FlowData],
1040 graph: &GraphData,
1041 partition: &mut [usize],
1042 rng: &mut StdRng,
1043) -> bool {
1044 run_local_moving(map_eq, flow_data, graph, partition, rng)
1045}
1046
1047fn renumber_partition(partition: &mut [usize]) {
1049 let max_mod = *partition.iter().max().unwrap_or(&0);
1050 let mut mapping = vec![usize::MAX; max_mod + 1];
1051 let mut next_id = 0usize;
1052 for p in partition.iter_mut() {
1053 if mapping[*p] == usize::MAX {
1054 mapping[*p] = next_id;
1055 next_id += 1;
1056 }
1057 *p = mapping[*p];
1058 }
1059}
1060
1061fn build_map_equation_for_partition(
1065 partition: &[usize],
1066 enriched: &[FlowData],
1067 graph: &GraphData,
1068 teleport_rate: f64,
1069) -> MapEquation {
1070 let n = partition.len();
1071 let num_mod = *partition.iter().max().unwrap_or(&0) + 1;
1072 let damping = 1.0 - teleport_rate;
1073
1074 let mut m_flow = vec![0.0; num_mod];
1075 let mut m_enter = vec![0.0; num_mod];
1076 let mut m_exit = vec![0.0; num_mod];
1077
1078 for i in 0..n {
1079 m_flow[partition[i]] += enriched[i].flow;
1080 }
1081 for u in 0..n {
1082 let u_od = graph.out_degree_of(u);
1083 if u_od > 0.0 {
1084 for (v, w) in graph.out_neighbors(u) {
1085 let mu = partition[u];
1086 let mv = partition[v];
1087 if mu != mv {
1088 let lf = damping * (w / u_od) * enriched[u].flow;
1089 m_exit[mu] += lf;
1090 m_enter[mv] += lf;
1091 }
1092 }
1093 }
1094 }
1095
1096 let enter_flow: f64 = m_enter.iter().sum();
1097 let node_flow_log: f64 = enriched.iter().map(|fd| plogp(fd.flow)).sum();
1098
1099 MapEquation {
1100 module_data: (0..num_mod)
1101 .map(|m| ModuleFlowData {
1102 flow: m_flow[m],
1103 enter_flow: m_enter[m],
1104 exit_flow: m_exit[m],
1105 })
1106 .collect(),
1107 enter_flow,
1108 enter_flow_log: plogp(enter_flow),
1109 enter_log_enter: m_enter.iter().map(|&x| plogp(x)).sum(),
1110 exit_log_exit: m_exit.iter().map(|&x| plogp(x)).sum(),
1111 flow_log_flow: (0..num_mod)
1112 .map(|m| plogp(m_exit[m] + m_flow[m]))
1113 .sum(),
1114 node_flow_log,
1115 exit_network_flow: 0.0,
1116 }
1117}
1118
1119pub struct Infomap {
1147 config: InfomapConfig,
1148}
1149
1150impl Infomap {
1151 #[must_use = "constructor returns a new instance"]
1153 pub fn new(config: InfomapConfig) -> Self {
1154 Self { config }
1155 }
1156
1157 pub fn run(&self, graph: &GraphData) -> InfomapOutput {
1169 let n = graph.node_count();
1170 if n == 0 {
1171 return InfomapOutput {
1172 partition: Partition::new(0),
1173 codelength: 0.0,
1174 num_levels: 1,
1175 iterations: 0,
1176 };
1177 }
1178
1179 let flow_values = compute_flow(
1180 graph,
1181 self.config.teleportation_rate,
1182 self.config.tolerance,
1183 self.config.max_iterations,
1184 );
1185
1186 let flow_data = compute_node_flows(graph, &flow_values, self.config.teleportation_rate);
1187 let mut enriched = flow_data.clone();
1188 populate_boundary_flows(graph, &mut enriched, self.config.teleportation_rate);
1189
1190 let mut best_partition: Option<Vec<usize>> = None;
1191 let mut best_codelength = f64::INFINITY;
1192 let mut total_iterations = 0usize;
1193
1194 for trial in 0..self.config.num_trials {
1195 let seed = self
1196 .config
1197 .seed
1198 .map(|s| s.wrapping_add((trial as u64).wrapping_mul(0x9e3779b97f4a7c15)));
1199 let mut rng = StdRng::seed_from_u64(seed.unwrap_or_else(|| {
1200 (trial as u64).wrapping_mul(0x517cc1b727220a95)
1201 }));
1202
1203 let trial_config = InfomapConfig {
1204 seed,
1205 ..self.config.clone()
1206 };
1207 let (mut partition, _cl) = run_multi_level(graph, &flow_data, &trial_config);
1208 total_iterations += 1;
1209
1210 let mut map_eq = build_map_equation_for_partition(
1211 &partition, &enriched, graph, self.config.teleportation_rate,
1212 );
1213
1214 let _ = fine_tuning(&mut map_eq, &enriched, graph, &mut partition, &mut rng);
1215 renumber_partition(&mut partition);
1216
1217 for _ in 0..10 {
1218 let cl_before = compute_codelength_for_partition(
1219 graph, &enriched, &partition, self.config.teleportation_rate,
1220 );
1221
1222 map_eq = build_map_equation_for_partition(
1223 &partition, &enriched, graph, self.config.teleportation_rate,
1224 );
1225
1226 let improved1 = coarse_tuning(
1227 &mut map_eq, &enriched, graph, &mut partition, &mut rng,
1228 );
1229 renumber_partition(&mut partition);
1230
1231 map_eq = build_map_equation_for_partition(
1232 &partition, &enriched, graph, self.config.teleportation_rate,
1233 );
1234
1235 let improved2 = fine_tuning(
1236 &mut map_eq, &enriched, graph, &mut partition, &mut rng,
1237 );
1238 renumber_partition(&mut partition);
1239 total_iterations += 1;
1240
1241 if !improved1 && !improved2 {
1242 break;
1243 }
1244
1245 let cl_after = compute_codelength_for_partition(
1246 graph, &enriched, &partition, self.config.teleportation_rate,
1247 );
1248
1249 if (cl_before - cl_after).abs() < self.config.tolerance {
1250 break;
1251 }
1252 }
1253
1254 let cl = compute_codelength_for_partition(
1255 graph, &enriched, &partition, self.config.teleportation_rate,
1256 );
1257 if cl < best_codelength {
1258 best_codelength = cl;
1259 best_partition = Some(partition);
1260 }
1261 }
1262
1263 let mut partition = best_partition.unwrap_or_else(|| (0..n).collect());
1264 renumber_partition(&mut partition);
1265
1266 InfomapOutput {
1267 partition: Partition::from_membership(partition),
1268 codelength: best_codelength,
1269 num_levels: 1,
1270 iterations: total_iterations,
1271 }
1272 }
1273}
1274
1275#[cfg(test)]
1278mod tests {
1279 use super::*;
1280 use crate::graph::GraphDataBuilder;
1281
1282 #[test]
1285 fn test_plogp_zero() {
1286 let result = plogp(0.0);
1288 assert!(
1289 result == 0.0,
1290 "plogp(0.0) should be exactly 0.0, got {result}"
1291 );
1292 assert!(!result.is_nan(), "plogp(0.0) must not be NaN");
1293 }
1294
1295 #[test]
1296 fn test_plogp_negative() {
1297 assert_eq!(plogp(-1.0), 0.0, "plogp(negative) should be 0.0");
1298 assert_eq!(plogp(-0.001), 0.0);
1299 }
1300
1301 #[test]
1302 fn test_plogp_value() {
1303 let result = plogp(0.5);
1305 assert!(
1306 (result - (-0.5)).abs() < 1e-10,
1307 "plogp(0.5) should be -0.5, got {result}"
1308 );
1309 }
1310
1311 #[test]
1312 fn test_plogp_one() {
1313 let result = plogp(1.0);
1315 assert!(
1316 result.abs() < 1e-10,
1317 "plogp(1.0) should be 0.0, got {result}"
1318 );
1319 }
1320
1321 #[test]
1324 fn test_flow_data_construction() {
1325 let fd = FlowData {
1326 flow: 0.25,
1327 enter_flow: 0.1,
1328 exit_flow: 0.15,
1329 teleport_flow: 0.05,
1330 };
1331 assert!((fd.flow - 0.25).abs() < 1e-10);
1332 assert!((fd.enter_flow - 0.1).abs() < 1e-10);
1333 assert!((fd.exit_flow - 0.15).abs() < 1e-10);
1334 assert!((fd.teleport_flow - 0.05).abs() < 1e-10);
1335 }
1336
1337 #[test]
1338 fn test_flow_data_default_fields() {
1339 let fd = FlowData {
1340 flow: 0.5,
1341 enter_flow: 0.0,
1342 exit_flow: 0.0,
1343 teleport_flow: 0.0,
1344 };
1345 assert!((fd.flow - 0.5).abs() < 1e-10);
1346 assert_eq!(fd.enter_flow, 0.0);
1347 assert_eq!(fd.exit_flow, 0.0);
1348 assert_eq!(fd.teleport_flow, 0.0);
1349 }
1350
1351 #[test]
1354 fn test_delta_flow_construction() {
1355 let df = DeltaFlow {
1356 module: 2,
1357 delta_exit: 0.3,
1358 delta_enter: 0.1,
1359 };
1360 assert_eq!(df.module, 2);
1361 assert!((df.delta_exit - 0.3).abs() < 1e-10);
1362 assert!((df.delta_enter - 0.1).abs() < 1e-10);
1363 }
1364
1365 #[test]
1368 fn test_infomap_config_default() {
1369 let cfg = InfomapConfig::default();
1370 assert_eq!(cfg.seed, None);
1371 assert_eq!(cfg.max_iterations, 100);
1372 assert!((cfg.teleportation_rate - 0.15).abs() < 1e-10);
1373 assert_eq!(cfg.num_trials, 10);
1374 assert!((cfg.tolerance - 1e-10).abs() < 1e-20);
1375 }
1376
1377 #[test]
1378 fn test_infomap_config_new() {
1379 let cfg = InfomapConfig::new(Some(42));
1380 assert_eq!(cfg.seed, Some(42));
1381 assert_eq!(cfg.max_iterations, 100); }
1383
1384 #[test]
1387 fn test_infomap_output_construction() {
1388 let output = InfomapOutput {
1389 partition: Partition::new(3),
1390 codelength: 1.5,
1391 num_levels: 2,
1392 iterations: 10,
1393 };
1394 assert_eq!(output.partition.len(), 3);
1395 assert!((output.codelength - 1.5).abs() < 1e-10);
1396 assert_eq!(output.num_levels, 2);
1397 assert_eq!(output.iterations, 10);
1398 }
1399
1400 #[test]
1403 fn test_pagerank_empty_graph() {
1404 let graph = GraphDataBuilder::new(0).build().unwrap();
1405 let result = compute_flow(&graph, 0.15, 1e-10, 100);
1406 assert!(result.is_empty());
1407 }
1408
1409 #[test]
1410 fn test_pagerank_single_node() {
1411 let graph = GraphDataBuilder::new(1).build().unwrap();
1412 let result = compute_flow(&graph, 0.15, 1e-10, 100);
1413 assert_eq!(result.len(), 1);
1414 assert!((result[0].flow - 1.0).abs() < 1e-6);
1415 }
1416
1417 #[test]
1418 fn test_pagerank_triangle() {
1419 let mut b = GraphDataBuilder::new(3);
1421 b.add_edge(0, 1, 1.0).unwrap();
1422 b.add_edge(1, 2, 1.0).unwrap();
1423 b.add_edge(0, 2, 1.0).unwrap();
1424 let graph = b.build().unwrap();
1425
1426 let result = compute_flow(&graph, 0.15, 1e-10, 200);
1427 assert_eq!(result.len(), 3);
1428
1429 let expected = 1.0 / 3.0;
1430 for (i, fd) in result.iter().enumerate() {
1431 assert!(
1432 (fd.flow - expected).abs() < 1e-6,
1433 "node {i}: flow = {}, expected {expected}",
1434 fd.flow
1435 );
1436 }
1437
1438 let total: f64 = result.iter().map(|fd| fd.flow).sum();
1440 assert!(
1441 (total - 1.0).abs() < 1e-6,
1442 "total flow = {total}, expected 1.0"
1443 );
1444 }
1445
1446 #[test]
1447 fn test_pagerank_dangling() {
1448 let mut b = GraphDataBuilder::new(3).directed();
1451 b.add_edge(0, 1, 1.0).unwrap();
1452 let graph = b.build().unwrap();
1454
1455 let result = compute_flow(&graph, 0.15, 1e-10, 200);
1456 assert_eq!(result.len(), 3);
1457
1458 let total: f64 = result.iter().map(|fd| fd.flow).sum();
1460 assert!(
1461 (total - 1.0).abs() < 1e-6,
1462 "total flow = {total}, expected 1.0"
1463 );
1464
1465 assert!(
1467 result[2].flow > 0.0,
1468 "dangling node 2 should have flow > 0, got {}",
1469 result[2].flow
1470 );
1471 }
1472
1473 #[test]
1474 fn test_pagerank_directed_path() {
1475 let mut b = GraphDataBuilder::new(2).directed();
1481 b.add_edge(0, 1, 1.0).unwrap();
1482 let graph = b.build().unwrap();
1483
1484 let result = compute_flow(&graph, 0.15, 1e-10, 200);
1485 assert_eq!(result.len(), 2);
1486
1487 let total: f64 = result.iter().map(|fd| fd.flow).sum();
1488 assert!(
1489 (total - 1.0).abs() < 1e-6,
1490 "total flow = {total}, expected 1.0"
1491 );
1492
1493 assert!(result[0].flow > 0.0, "node 0 flow should be positive");
1495 assert!(result[1].flow > 0.0, "node 1 flow should be positive");
1496
1497 assert!(
1499 result[1].flow > result[0].flow,
1500 "node 1 ({}) should have higher flow than node 0 ({})",
1501 result[1].flow,
1502 result[0].flow
1503 );
1504 }
1505
1506 #[test]
1507 fn test_pagerank_disconnected() {
1508 let mut b = GraphDataBuilder::new(4);
1511 b.add_edge(0, 1, 1.0).unwrap();
1512 b.add_edge(2, 3, 1.0).unwrap();
1513 let graph = b.build().unwrap();
1514
1515 let result = compute_flow(&graph, 0.15, 1e-10, 200);
1516 assert_eq!(result.len(), 4);
1517
1518 let total: f64 = result.iter().map(|fd| fd.flow).sum();
1519 assert!(
1520 (total - 1.0).abs() < 1e-6,
1521 "total flow = {total}, expected 1.0"
1522 );
1523
1524 for (i, fd) in result.iter().enumerate() {
1527 assert!(
1528 (fd.flow - 0.25).abs() < 0.05,
1529 "node {i}: flow = {}, expected ~0.25",
1530 fd.flow
1531 );
1532 }
1533 }
1534
1535 #[test]
1536 fn test_pagerank_converges_before_max_iter() {
1537 let mut b = GraphDataBuilder::new(3);
1539 b.add_edge(0, 1, 1.0).unwrap();
1540 b.add_edge(1, 2, 1.0).unwrap();
1541 b.add_edge(2, 0, 1.0).unwrap();
1542 let graph = b.build().unwrap();
1543
1544 let result_strict = compute_flow(&graph, 0.15, 1e-10, 200);
1545 let result_loose = compute_flow(&graph, 0.15, 1e-10, 200);
1546
1547 for (a, b) in result_strict.iter().zip(result_loose.iter()) {
1549 assert!(
1550 (a.flow - b.flow).abs() < 1e-10,
1551 "results should be identical"
1552 );
1553 }
1554 }
1555
1556 fn fd(flow: f64, enter_flow: f64, exit_flow: f64) -> FlowData {
1560 FlowData {
1561 flow,
1562 enter_flow,
1563 exit_flow,
1564 teleport_flow: 0.0,
1565 }
1566 }
1567
1568 fn four_node_flows() -> [FlowData; 4] {
1578 [
1579 fd(0.30, 0.10, 0.12),
1580 fd(0.25, 0.09, 0.09),
1581 fd(0.25, 0.08, 0.08),
1582 fd(0.20, 0.10, 0.08),
1583 ]
1584 }
1585
1586 #[test]
1587 fn test_map_equation_singleton_partition_codelength() {
1588 let nodes = four_node_flows();
1589 let map_eq = MapEquation::new(&nodes, 0.0);
1590
1591 let cl = map_eq.codelength();
1592 assert!(
1594 (cl - 1.889169354568307).abs() < 1e-12,
1595 "singleton codelength: got {cl:.15}, expected 1.889169354568307"
1596 );
1597
1598 assert!(
1600 (calc_codelength(&map_eq) - cl).abs() < 1e-15,
1601 "calc_codelength should match method"
1602 );
1603 }
1604
1605 #[test]
1606 fn test_map_equation_calc_codelength_matches_terms() {
1607 let nodes = four_node_flows();
1608 let map_eq = MapEquation::new(&nodes, 0.0);
1609
1610 let expected_enter_flow_log = plogp(map_eq.enter_flow);
1612 let expected_enter_log_enter: f64 =
1613 map_eq.module_data.iter().map(|m| plogp(m.enter_flow)).sum();
1614 let expected_exit_log_exit: f64 =
1615 map_eq.module_data.iter().map(|m| plogp(m.exit_flow)).sum();
1616 let expected_flow_log_flow: f64 = map_eq
1617 .module_data
1618 .iter()
1619 .map(|m| plogp(m.exit_flow + m.flow))
1620 .sum();
1621
1622 assert!(
1623 (map_eq.enter_flow_log - expected_enter_flow_log).abs() < 1e-15,
1624 "enter_flow_log mismatch"
1625 );
1626 assert!(
1627 (map_eq.enter_log_enter - expected_enter_log_enter).abs() < 1e-15,
1628 "enter_log_enter mismatch"
1629 );
1630 assert!(
1631 (map_eq.exit_log_exit - expected_exit_log_exit).abs() < 1e-15,
1632 "exit_log_exit mismatch"
1633 );
1634 assert!(
1635 (map_eq.flow_log_flow - expected_flow_log_flow).abs() < 1e-15,
1636 "flow_log_flow mismatch"
1637 );
1638 }
1639
1640 #[test]
1641 fn test_map_equation_recalc_terms() {
1642 let nodes = four_node_flows();
1643 let mut map_eq = MapEquation::new(&nodes, 0.0);
1644 let original_cl = map_eq.codelength();
1645
1646 map_eq.recalc_terms();
1647
1648 assert!(
1649 (map_eq.codelength() - original_cl).abs() < 1e-15,
1650 "recalc_terms should not change codelength"
1651 );
1652 }
1653
1654 #[test]
1657 fn test_delta_codelength_singleton_to_singleton() {
1658 let nodes = four_node_flows();
1669 let map_eq = MapEquation::new(&nodes, 0.0);
1670
1671 let current = nodes[0].clone();
1672 let old_delta = DeltaFlow {
1673 module: 0,
1674 delta_exit: 0.0,
1675 delta_enter: 0.0,
1676 };
1677 let new_delta = DeltaFlow {
1678 module: 1,
1679 delta_exit: 0.05,
1680 delta_enter: 0.04,
1681 };
1682
1683 let dl = map_eq.get_delta_codelength_on_moving_node(
1684 ¤t, 0, 1, &old_delta, &new_delta,
1685 );
1686
1687 let expected = 0.058917207167547;
1688 assert!(
1689 (dl - expected).abs() < 1e-12,
1690 "ΔL singleton→singleton: got {dl:.15}, expected {expected:.15}"
1691 );
1692
1693 let cl_before = map_eq.codelength();
1695 let mut map_eq2 = map_eq.clone();
1696 map_eq2.update_codelength_on_moving_node(
1697 ¤t, 0, 1, &old_delta, &new_delta,
1698 );
1699 let cl_after = map_eq2.codelength();
1700 assert!(
1701 ((cl_after - cl_before) - dl).abs() < 1e-12,
1702 "codelength diff ({:.15}) should equal ΔL ({:.15})",
1703 cl_after - cl_before,
1704 dl
1705 );
1706 }
1707
1708 #[test]
1711 fn test_delta_codelength_into_multi_node_module() {
1712 let nodes = four_node_flows();
1728 let module_data = vec![
1729 ModuleFlowData {
1730 flow: 0.55,
1731 enter_flow: 0.10,
1732 exit_flow: 0.12,
1733 },
1734 ModuleFlowData {
1735 flow: 0.25,
1736 enter_flow: 0.08,
1737 exit_flow: 0.08,
1738 },
1739 ModuleFlowData {
1740 flow: 0.20,
1741 enter_flow: 0.10,
1742 exit_flow: 0.08,
1743 },
1744 ];
1745 let enter_flow = 0.28_f64;
1746 let enter_log_enter: f64 = module_data.iter().map(|m| plogp(m.enter_flow)).sum();
1747 let exit_log_exit: f64 = module_data.iter().map(|m| plogp(m.exit_flow)).sum();
1748 let flow_log_flow: f64 = module_data.iter().map(|m| plogp(m.exit_flow + m.flow)).sum();
1749 let map_eq = MapEquation {
1750 module_data,
1751 enter_flow,
1752 enter_flow_log: plogp(enter_flow),
1753 enter_log_enter,
1754 exit_log_exit,
1755 flow_log_flow,
1756 node_flow_log: nodes.iter().map(|n| plogp(n.flow)).sum(),
1757 exit_network_flow: 0.0,
1758 };
1759
1760 let current = nodes[2].clone();
1761 let old_delta = DeltaFlow {
1762 module: 1,
1763 delta_exit: 0.0,
1764 delta_enter: 0.0,
1765 };
1766 let new_delta = DeltaFlow {
1767 module: 0,
1768 delta_exit: 0.04,
1769 delta_enter: 0.06,
1770 };
1771
1772 let dl = map_eq.get_delta_codelength_on_moving_node(
1773 ¤t, 1, 0, &old_delta, &new_delta,
1774 );
1775
1776 let expected = 0.188460591871736;
1777 assert!(
1778 (dl - expected).abs() < 1e-12,
1779 "ΔL into multi-node module: got {dl:.15}, expected {expected:.15}"
1780 );
1781
1782 let cl_before = map_eq.codelength();
1783 let mut map_eq2 = map_eq.clone();
1784 map_eq2.update_codelength_on_moving_node(
1785 ¤t, 1, 0, &old_delta, &new_delta,
1786 );
1787 let cl_after = map_eq2.codelength();
1788 assert!(
1789 ((cl_after - cl_before) - dl).abs() < 1e-12,
1790 "codelength diff ({:.15}) should equal ΔL ({:.15})",
1791 cl_after - cl_before,
1792 dl
1793 );
1794 }
1795
1796 #[test]
1799 fn test_delta_codelength_out_of_multi_node_module() {
1800 let nodes = four_node_flows();
1816 let module_data = vec![
1817 ModuleFlowData {
1818 flow: 0.80,
1819 enter_flow: 0.08,
1820 exit_flow: 0.10,
1821 },
1822 ModuleFlowData {
1823 flow: 0.20,
1824 enter_flow: 0.10,
1825 exit_flow: 0.08,
1826 },
1827 ModuleFlowData {
1828 flow: 0.0,
1829 enter_flow: 0.0,
1830 exit_flow: 0.0,
1831 },
1832 ];
1833 let enter_flow = 0.18_f64;
1834 let enter_log_enter: f64 = module_data.iter().map(|m| plogp(m.enter_flow)).sum();
1835 let exit_log_exit: f64 = module_data.iter().map(|m| plogp(m.exit_flow)).sum();
1836 let flow_log_flow: f64 = module_data.iter().map(|m| plogp(m.exit_flow + m.flow)).sum();
1837 let map_eq = MapEquation {
1838 module_data,
1839 enter_flow,
1840 enter_flow_log: plogp(enter_flow),
1841 enter_log_enter,
1842 exit_log_exit,
1843 flow_log_flow,
1844 node_flow_log: nodes.iter().map(|n| plogp(n.flow)).sum(),
1845 exit_network_flow: 0.0,
1846 };
1847
1848 let current = nodes[1].clone();
1849 let old_delta = DeltaFlow {
1850 module: 0,
1851 delta_exit: 0.07,
1852 delta_enter: 0.07,
1853 };
1854 let new_delta = DeltaFlow {
1855 module: 2,
1856 delta_exit: 0.0,
1857 delta_enter: 0.0,
1858 };
1859
1860 let dl = map_eq.get_delta_codelength_on_moving_node(
1861 ¤t, 0, 2, &old_delta, &new_delta,
1862 );
1863
1864 let expected = -0.038503252540231;
1865 assert!(
1866 (dl - expected).abs() < 1e-12,
1867 "ΔL out of multi-node module: got {dl:.15}, expected {expected:.15}"
1868 );
1869
1870 let cl_before = map_eq.codelength();
1871 let mut map_eq2 = map_eq.clone();
1872 map_eq2.update_codelength_on_moving_node(
1873 ¤t, 0, 2, &old_delta, &new_delta,
1874 );
1875 let cl_after = map_eq2.codelength();
1876 assert!(
1877 ((cl_after - cl_before) - dl).abs() < 1e-12,
1878 "codelength diff ({:.15}) should equal ΔL ({:.15})",
1879 cl_after - cl_before,
1880 dl
1881 );
1882
1883 assert!(
1889 (map_eq2.module_data[0].flow - 0.55).abs() < 1e-12,
1890 "module 0 flow after move"
1891 );
1892 assert!(
1893 (map_eq2.module_data[0].enter_flow - 0.13).abs() < 1e-12,
1894 "module 0 enter after move"
1895 );
1896 assert!(
1897 (map_eq2.module_data[0].exit_flow - 0.15).abs() < 1e-12,
1898 "module 0 exit after move"
1899 );
1900 assert!(
1901 (map_eq2.module_data[2].flow - 0.25).abs() < 1e-12,
1902 "module 2 flow after move"
1903 );
1904 assert!(
1905 (map_eq2.module_data[2].enter_flow - 0.09).abs() < 1e-12,
1906 "module 2 enter after move"
1907 );
1908 assert!(
1909 (map_eq2.module_data[2].exit_flow - 0.09).abs() < 1e-12,
1910 "module 2 exit after move"
1911 );
1912 }
1913
1914 #[test]
1915 fn test_map_equation_update_recalc_consistency() {
1916 let nodes = four_node_flows();
1918 let mut map_eq = MapEquation::new(&nodes, 0.0);
1919
1920 let current = nodes[0].clone();
1921 let old_delta = DeltaFlow {
1922 module: 0,
1923 delta_exit: 0.0,
1924 delta_enter: 0.0,
1925 };
1926 let new_delta = DeltaFlow {
1927 module: 1,
1928 delta_exit: 0.05,
1929 delta_enter: 0.04,
1930 };
1931
1932 map_eq.update_codelength_on_moving_node(
1933 ¤t, 0, 1, &old_delta, &new_delta,
1934 );
1935 let cl_incremental = map_eq.codelength();
1936
1937 map_eq.recalc_terms();
1938 let cl_recalc = map_eq.codelength();
1939
1940 assert!(
1941 (cl_incremental - cl_recalc).abs() < 1e-15,
1942 "incremental update ({cl_incremental:.15}) should match recalc ({cl_recalc:.15})"
1943 );
1944 }
1945
1946 #[test]
1947 fn test_map_equation_add_remove_flow_roundtrip() {
1948 let nodes = four_node_flows();
1949 let map_eq = MapEquation::new(&nodes, 0.0);
1950 let mut map_eq2 = map_eq.clone();
1951
1952 map_eq2.add_flow_to_module(1, &nodes[0]);
1954 map_eq2.remove_flow_from_module(1, &nodes[0]);
1955
1956 assert!(
1958 (map_eq2.module_data[1].flow - map_eq.module_data[1].flow).abs() < 1e-15,
1959 "flow roundtrip"
1960 );
1961 assert!(
1962 (map_eq2.module_data[1].enter_flow - map_eq.module_data[1].enter_flow).abs() < 1e-15,
1963 "enter roundtrip"
1964 );
1965 assert!(
1966 (map_eq2.module_data[1].exit_flow - map_eq.module_data[1].exit_flow).abs() < 1e-15,
1967 "exit roundtrip"
1968 );
1969 }
1970
1971 #[test]
1974 fn test_infomap_local_moving_reduces_codelength() {
1975 let mut b = GraphDataBuilder::new(6);
1976 b.add_edge(0, 1, 5.0).unwrap();
1977 b.add_edge(1, 2, 5.0).unwrap();
1978 b.add_edge(0, 2, 5.0).unwrap();
1979 b.add_edge(3, 4, 5.0).unwrap();
1980 b.add_edge(4, 5, 5.0).unwrap();
1981 b.add_edge(3, 5, 5.0).unwrap();
1982 b.add_edge(2, 3, 0.1).unwrap();
1983 let graph = b.build().unwrap();
1984
1985 let mut flow_data = compute_flow(&graph, 0.15, 1e-10, 200);
1986 populate_boundary_flows(&graph, &mut flow_data, 0.15);
1987
1988 let mut map_eq = MapEquation::new(&flow_data, 0.0);
1989 let cl_before = map_eq.codelength();
1990
1991 let mut partition: Vec<usize> = (0..6).collect();
1992 let mut rng = StdRng::seed_from_u64(42);
1993 let changed = run_local_moving(&mut map_eq, &flow_data, &graph, &mut partition, &mut rng);
1994
1995 assert!(changed, "local moving should move at least one node");
1996 let cl_after = map_eq.codelength();
1997 assert!(
1998 cl_after < cl_before,
1999 "codelength should decrease: before={cl_before:.6}, after={cl_after:.6}"
2000 );
2001 }
2002
2003 #[test]
2004 fn test_infomap_local_moving_finds_two_communities() {
2005 let mut b = GraphDataBuilder::new(6);
2006 b.add_edge(0, 1, 5.0).unwrap();
2007 b.add_edge(1, 2, 5.0).unwrap();
2008 b.add_edge(0, 2, 5.0).unwrap();
2009 b.add_edge(3, 4, 5.0).unwrap();
2010 b.add_edge(4, 5, 5.0).unwrap();
2011 b.add_edge(3, 5, 5.0).unwrap();
2012 b.add_edge(2, 3, 0.1).unwrap();
2013 let graph = b.build().unwrap();
2014
2015 let mut flow_data = compute_flow(&graph, 0.15, 1e-10, 200);
2016 populate_boundary_flows(&graph, &mut flow_data, 0.15);
2017
2018 let mut map_eq = MapEquation::new(&flow_data, 0.0);
2019 let mut partition: Vec<usize> = (0..6).collect();
2020 let mut rng = StdRng::seed_from_u64(42);
2021 run_local_moving(&mut map_eq, &flow_data, &graph, &mut partition, &mut rng);
2022
2023 assert_eq!(partition[0], partition[1], "nodes 0,1 same community");
2024 assert_eq!(partition[1], partition[2], "nodes 1,2 same community");
2025 assert_eq!(partition[3], partition[4], "nodes 3,4 same community");
2026 assert_eq!(partition[4], partition[5], "nodes 4,5 same community");
2027 assert_ne!(partition[0], partition[3], "two distinct communities");
2028 }
2029
2030 #[test]
2031 fn test_infomap_convergence_monotone_codelength() {
2032 let mut b = GraphDataBuilder::new(6);
2033 b.add_edge(0, 1, 3.0).unwrap();
2034 b.add_edge(1, 2, 3.0).unwrap();
2035 b.add_edge(0, 2, 3.0).unwrap();
2036 b.add_edge(3, 4, 3.0).unwrap();
2037 b.add_edge(4, 5, 3.0).unwrap();
2038 b.add_edge(3, 5, 3.0).unwrap();
2039 b.add_edge(2, 3, 0.5).unwrap();
2040 let graph = b.build().unwrap();
2041
2042 let mut flow_data = compute_flow(&graph, 0.15, 1e-10, 200);
2043 populate_boundary_flows(&graph, &mut flow_data, 0.15);
2044
2045 let mut map_eq = MapEquation::new(&flow_data, 0.0);
2046 let mut partition: Vec<usize> = (0..6).collect();
2047 let mut rng = StdRng::seed_from_u64(123);
2048
2049 let mut codelengths: Vec<f64> = vec![map_eq.codelength()];
2050
2051 for _ in 0..5 {
2052 let n = graph.node_count();
2053 let mut order: Vec<usize> = (0..n).collect();
2054 order.shuffle(&mut rng);
2055
2056 let mut any_moved = false;
2057 for &node in &order {
2058 let old_module = partition[node];
2059 let node_exit = flow_data[node].exit_flow;
2060 let node_out_deg = graph.out_degree_of(node);
2061
2062 let mut delta_exit_map: FxHashMap<usize, f64> = FxHashMap::default();
2063 let mut delta_enter_map: FxHashMap<usize, f64> = FxHashMap::default();
2064
2065 if node_out_deg > 0.0 {
2066 for (v, w) in graph.out_neighbors(node) {
2067 let tm = partition[v];
2068 *delta_exit_map.entry(tm).or_default() += (w / node_out_deg) * node_exit;
2069 let v_out_deg = graph.out_degree_of(v);
2070 if v_out_deg > 0.0 {
2071 *delta_enter_map.entry(tm).or_default() +=
2072 (w / v_out_deg) * flow_data[v].exit_flow;
2073 }
2074 }
2075 }
2076
2077 let mut all_modules: FxHashMap<usize, ()> = FxHashMap::default();
2078 for &m in delta_exit_map.keys() {
2079 all_modules.insert(m, ());
2080 }
2081 for &m in delta_enter_map.keys() {
2082 all_modules.insert(m, ());
2083 }
2084
2085 let mut delta_flows: FxHashMap<usize, DeltaFlow> = FxHashMap::default();
2086 for &m in all_modules.keys() {
2087 delta_flows.insert(
2088 m,
2089 DeltaFlow {
2090 module: m,
2091 delta_exit: delta_exit_map.get(&m).copied().unwrap_or(0.0),
2092 delta_enter: delta_enter_map.get(&m).copied().unwrap_or(0.0),
2093 },
2094 );
2095 }
2096
2097 let self_delta = delta_flows.remove(&old_module).unwrap_or(DeltaFlow {
2098 module: old_module,
2099 delta_exit: 0.0,
2100 delta_enter: 0.0,
2101 });
2102
2103 let mut best_module = old_module;
2104 let mut best_delta = 0.0_f64;
2105
2106 for (&nm, nd) in &delta_flows {
2107 let dl = map_eq.get_delta_codelength_on_moving_node(
2108 &flow_data[node], old_module, nm, &self_delta, nd,
2109 );
2110 if dl < best_delta {
2111 best_delta = dl;
2112 best_module = nm;
2113 }
2114 }
2115
2116 if best_module != old_module {
2117 let actual = delta_flows.get(&best_module).cloned().unwrap_or(DeltaFlow {
2118 module: best_module,
2119 delta_exit: 0.0,
2120 delta_enter: 0.0,
2121 });
2122 map_eq.update_codelength_on_moving_node(
2123 &flow_data[node], old_module, best_module, &self_delta, &actual,
2124 );
2125 partition[node] = best_module;
2126 any_moved = true;
2127 }
2128 }
2129
2130 codelengths.push(map_eq.codelength());
2131 if !any_moved {
2132 break;
2133 }
2134 }
2135
2136 for i in 1..codelengths.len() {
2137 assert!(
2138 codelengths[i] <= codelengths[i - 1] + 1e-12,
2139 "codelength must decrease monotonically: step {} => {:.12} > {:.12}",
2140 i,
2141 codelengths[i],
2142 codelengths[i - 1]
2143 );
2144 }
2145 }
2146
2147 #[test]
2148 fn test_infomap_aggregate_graph_two_communities() {
2149 let mut b = GraphDataBuilder::new(4);
2150 b.add_edge(0, 1, 2.0).unwrap();
2151 b.add_edge(1, 2, 1.0).unwrap();
2152 b.add_edge(2, 3, 2.0).unwrap();
2153 let graph = b.build().unwrap();
2154
2155 let partition: Vec<usize> = vec![0, 0, 1, 1];
2156 let agg = aggregate_graph(&graph, &partition);
2157
2158 assert_eq!(agg.node_count(), 2, "should have 2 super-nodes");
2159
2160 let nbrs_0: Vec<(usize, f64)> = agg.neighbors(0).collect();
2161 let has_self_0 = nbrs_0.iter().any(|(n, w)| *n == 0 && (*w - 2.0).abs() < 1e-10);
2162 let has_bridge_0 = nbrs_0.iter().any(|(n, w)| *n == 1 && (*w - 1.0).abs() < 1e-10);
2163 assert!(has_self_0, "super-node 0 should have self-loop w=2.0");
2164 assert!(has_bridge_0, "super-node 0 should have edge to super 1 w=1.0");
2165 }
2166
2167 #[test]
2168 fn test_infomap_aggregate_graph_single_community() {
2169 let mut b = GraphDataBuilder::new(3);
2170 b.add_edge(0, 1, 1.0).unwrap();
2171 b.add_edge(1, 2, 1.0).unwrap();
2172 let graph = b.build().unwrap();
2173
2174 let partition: Vec<usize> = vec![0, 0, 0];
2175 let agg = aggregate_graph(&graph, &partition);
2176
2177 assert_eq!(agg.node_count(), 1, "single community -> 1 super-node");
2178 assert!((agg.node_weight(0) - 3.0).abs() < 1e-10, "weight = 3.0");
2179 }
2180
2181 #[test]
2182 fn test_infomap_aggregate_graph_directed() {
2183 let mut b = GraphDataBuilder::new(4).directed();
2184 b.add_edge(0, 1, 2.0).unwrap();
2185 b.add_edge(1, 0, 1.0).unwrap();
2186 b.add_edge(2, 3, 3.0).unwrap();
2187 b.add_edge(3, 2, 1.5).unwrap();
2188 b.add_edge(1, 2, 0.5).unwrap();
2189 let graph = b.build().unwrap();
2190
2191 let partition: Vec<usize> = vec![0, 0, 1, 1];
2192 let agg = aggregate_graph(&graph, &partition);
2193
2194 assert_eq!(agg.node_count(), 2);
2195 assert!(agg.is_directed());
2196
2197 let out_0: Vec<(usize, f64)> = agg.out_neighbors(0).collect();
2198 let has_self = out_0.iter().any(|(n, w)| *n == 0 && (*w - 3.0).abs() < 1e-10);
2199 let has_out = out_0.iter().any(|(n, w)| *n == 1 && (*w - 0.5).abs() < 1e-10);
2200 assert!(has_self, "directed self-loop 0->0 w=3.0");
2201 assert!(has_out, "directed edge 0->1 w=0.5");
2202 }
2203
2204 #[test]
2205 fn test_infomap_multi_level_planted_partition() {
2206 use crate::generators::generate_planted_partition;
2207 use crate::metrics::nmi;
2208
2209 let (graph, ground_truth) =
2210 generate_planted_partition(30, 3, 0.8, 0.05, Some(42)).unwrap();
2211
2212 let flow_data = compute_flow(&graph, 0.15, 1e-10, 200);
2213 let config = InfomapConfig {
2214 seed: Some(42),
2215 max_iterations: 10,
2216 ..Default::default()
2217 };
2218 let (partition, codelength) = run_multi_level(&graph, &flow_data, &config);
2219
2220 assert!(
2221 codelength.is_finite() && codelength >= 0.0,
2222 "codelength should be finite and non-negative, got {codelength}"
2223 );
2224 assert_eq!(partition.len(), 30);
2225 assert!(partition.iter().all(|&m| m < 30));
2226
2227 let num_comms = *partition.iter().max().unwrap_or(&0) + 1;
2228 assert!(
2229 (2..=5).contains(&num_comms),
2230 "expected 2-5 communities, found {num_comms}"
2231 );
2232
2233 let score = nmi(&ground_truth, &partition);
2234 assert!(
2235 score > 0.7,
2236 "NMI should be > 0.7 for strong planted partition, got {score:.4}"
2237 );
2238 }
2239
2240 #[test]
2241 fn test_infomap_multi_level_single_node() {
2242 let graph = GraphDataBuilder::new(1).build().unwrap();
2243 let flow_data = compute_flow(&graph, 0.15, 1e-10, 200);
2244 let config = InfomapConfig::default();
2245 let (partition, cl) = run_multi_level(&graph, &flow_data, &config);
2246 assert_eq!(partition, vec![0]);
2247 assert!(cl >= 0.0);
2248 }
2249
2250 #[test]
2251 fn test_infomap_multi_level_empty_graph() {
2252 let graph = GraphDataBuilder::new(0).build().unwrap();
2253 let flow_data = compute_flow(&graph, 0.15, 1e-10, 200);
2254 let config = InfomapConfig::default();
2255 let (partition, cl) = run_multi_level(&graph, &flow_data, &config);
2256 assert!(partition.is_empty());
2257 assert!((cl - 0.0).abs() < 1e-10);
2258 }
2259
2260 #[test]
2261 fn test_infomap_multi_level_triangle() {
2262 let mut b = GraphDataBuilder::new(3);
2263 b.add_edge(0, 1, 1.0).unwrap();
2264 b.add_edge(1, 2, 1.0).unwrap();
2265 b.add_edge(0, 2, 1.0).unwrap();
2266 let graph = b.build().unwrap();
2267
2268 let flow_data = compute_flow(&graph, 0.15, 1e-10, 200);
2269 let config = InfomapConfig {
2270 seed: Some(42),
2271 ..Default::default()
2272 };
2273 let (partition, _cl) = run_multi_level(&graph, &flow_data, &config);
2274
2275 assert_eq!(partition[0], partition[1], "triangle should be one community");
2276 assert_eq!(partition[1], partition[2], "triangle should be one community");
2277 }
2278
2279 #[test]
2280 fn test_infomap_populate_boundary_flows() {
2281 let mut b = GraphDataBuilder::new(3);
2282 b.add_edge(0, 1, 1.0).unwrap();
2283 b.add_edge(1, 2, 1.0).unwrap();
2284 b.add_edge(0, 2, 1.0).unwrap();
2285 let graph = b.build().unwrap();
2286
2287 let mut flow_data = compute_flow(&graph, 0.15, 1e-10, 200);
2288 assert!(flow_data.iter().all(|fd| fd.enter_flow == 0.0 && fd.exit_flow == 0.0));
2289
2290 populate_boundary_flows(&graph, &mut flow_data, 0.15);
2291
2292 for (i, fd) in flow_data.iter().enumerate() {
2293 assert!(fd.enter_flow > 0.0, "node {i} enter_flow should be positive");
2294 assert!(fd.exit_flow > 0.0, "node {i} exit_flow should be positive");
2295 }
2296
2297 for (i, fd) in flow_data.iter().enumerate() {
2298 assert!(
2299 (fd.enter_flow - fd.exit_flow).abs() < 1e-10,
2300 "node {i}: enter ({:.6}) should ~= exit ({:.6})",
2301 fd.enter_flow,
2302 fd.exit_flow
2303 );
2304 }
2305
2306 let total_exit: f64 = flow_data.iter().map(|fd| fd.exit_flow).sum();
2307 assert!(
2308 (total_exit - 0.85).abs() < 1e-6,
2309 "total exit flow should be ~0.85, got {total_exit:.6}"
2310 );
2311 }
2312
2313 #[test]
2316 fn test_infomap_compute_node_flows() {
2317 let mut b = GraphDataBuilder::new(3);
2318 b.add_edge(0, 1, 1.0).unwrap();
2319 b.add_edge(1, 2, 1.0).unwrap();
2320 b.add_edge(0, 2, 1.0).unwrap();
2321 let graph = b.build().unwrap();
2322
2323 let flow_data = compute_flow(&graph, 0.15, 1e-10, 200);
2324 let result = compute_node_flows(&graph, &flow_data, 0.15);
2325
2326 for (i, fd) in result.iter().enumerate() {
2327 assert!(fd.flow > 0.0, "node {i} flow should be positive");
2328 assert!(fd.enter_flow > 0.0, "node {i} enter_flow should be positive");
2329 assert!(fd.exit_flow > 0.0, "node {i} exit_flow should be positive");
2330 }
2331 }
2332
2333 #[test]
2336 fn test_infomap_struct_empty_graph() {
2337 let graph = GraphDataBuilder::new(0).build().unwrap();
2338 let config = InfomapConfig::default();
2339 let infomap = Infomap::new(config);
2340 let result = infomap.run(&graph);
2341 assert_eq!(result.partition.len(), 0);
2342 assert!((result.codelength - 0.0).abs() < 1e-10);
2343 }
2344
2345 #[test]
2346 fn test_infomap_struct_single_node() {
2347 let graph = GraphDataBuilder::new(1).build().unwrap();
2348 let config = InfomapConfig { seed: Some(42), ..Default::default() };
2349 let infomap = Infomap::new(config);
2350 let result = infomap.run(&graph);
2351 assert_eq!(result.partition.len(), 1);
2352 assert!(result.codelength.is_finite());
2353 }
2354
2355 #[test]
2356 fn test_infomap_struct_two_communities() {
2357 let mut b = GraphDataBuilder::new(6);
2358 b.add_edge(0, 1, 5.0).unwrap();
2359 b.add_edge(1, 2, 5.0).unwrap();
2360 b.add_edge(0, 2, 5.0).unwrap();
2361 b.add_edge(3, 4, 5.0).unwrap();
2362 b.add_edge(4, 5, 5.0).unwrap();
2363 b.add_edge(3, 5, 5.0).unwrap();
2364 b.add_edge(2, 3, 0.1).unwrap();
2365 let graph = b.build().unwrap();
2366
2367 let config = InfomapConfig {
2368 seed: Some(42),
2369 num_trials: 3,
2370 ..Default::default()
2371 };
2372 let infomap = Infomap::new(config);
2373 let result = infomap.run(&graph);
2374
2375 assert_eq!(result.partition.len(), 6);
2376 assert!(result.codelength > 0.0 && result.codelength.is_finite());
2377 assert_eq!(result.num_levels, 1);
2378 assert!(result.iterations > 0);
2379
2380 let membership: Vec<usize> = (0..result.partition.len())
2381 .map(|i| result.partition.community_of(i))
2382 .collect();
2383
2384 assert_eq!(membership[0], membership[1]);
2385 assert_eq!(membership[1], membership[2]);
2386 assert_eq!(membership[3], membership[4]);
2387 assert_eq!(membership[4], membership[5]);
2388 assert_ne!(membership[0], membership[3]);
2389 }
2390
2391 #[test]
2392 fn test_infomap_struct_planted_partition_nmi() {
2393 use crate::generators::generate_planted_partition;
2394 use crate::metrics::nmi;
2395
2396 let (graph, ground_truth) =
2397 generate_planted_partition(30, 3, 0.8, 0.05, Some(42)).unwrap();
2398
2399 let config = InfomapConfig {
2400 seed: Some(42),
2401 num_trials: 5,
2402 ..Default::default()
2403 };
2404 let infomap = Infomap::new(config);
2405 let result = infomap.run(&graph);
2406
2407 let membership: Vec<usize> = (0..result.partition.len())
2408 .map(|i| result.partition.community_of(i))
2409 .collect();
2410
2411 let score = nmi(&ground_truth, &membership);
2412 assert!(
2413 score > 0.7,
2414 "NMI should be > 0.7 for strong planted partition, got {score:.4}"
2415 );
2416 }
2417
2418 #[test]
2419 fn test_infomap_struct_multi_trial_improves() {
2420 let mut b = GraphDataBuilder::new(6);
2421 b.add_edge(0, 1, 5.0).unwrap();
2422 b.add_edge(1, 2, 5.0).unwrap();
2423 b.add_edge(0, 2, 5.0).unwrap();
2424 b.add_edge(3, 4, 5.0).unwrap();
2425 b.add_edge(4, 5, 5.0).unwrap();
2426 b.add_edge(3, 5, 5.0).unwrap();
2427 b.add_edge(2, 3, 0.1).unwrap();
2428 let graph = b.build().unwrap();
2429
2430 let config = InfomapConfig {
2431 seed: Some(42),
2432 num_trials: 5,
2433 ..Default::default()
2434 };
2435 let infomap = Infomap::new(config);
2436 let result = infomap.run(&graph);
2437
2438 assert!(
2439 result.codelength > 0.0,
2440 "codelength should be positive, got {}",
2441 result.codelength
2442 );
2443 assert!(result.codelength.is_finite());
2444
2445 let membership: Vec<usize> = (0..result.partition.len())
2446 .map(|i| result.partition.community_of(i))
2447 .collect();
2448 let num_comms = membership.iter().max().unwrap() + 1;
2449 assert!(
2450 (2..=3).contains(&num_comms),
2451 "expected 2-3 communities, got {num_comms}"
2452 );
2453 }
2454
2455 #[test]
2456 fn test_infomap_fine_coarse_tuning() {
2457 let mut b = GraphDataBuilder::new(6);
2458 b.add_edge(0, 1, 5.0).unwrap();
2459 b.add_edge(1, 2, 5.0).unwrap();
2460 b.add_edge(0, 2, 5.0).unwrap();
2461 b.add_edge(3, 4, 5.0).unwrap();
2462 b.add_edge(4, 5, 5.0).unwrap();
2463 b.add_edge(3, 5, 5.0).unwrap();
2464 b.add_edge(2, 3, 0.1).unwrap();
2465 let graph = b.build().unwrap();
2466
2467 let mut flow_data = compute_flow(&graph, 0.15, 1e-10, 200);
2468 populate_boundary_flows(&graph, &mut flow_data, 0.15);
2469
2470 let mut map_eq = MapEquation::new(&flow_data, 0.0);
2471 let mut partition: Vec<usize> = (0..6).collect();
2472 let mut rng = StdRng::seed_from_u64(42);
2473
2474 run_local_moving(&mut map_eq, &flow_data, &graph, &mut partition, &mut rng);
2475 renumber_partition(&mut partition);
2476
2477 let cl_after_local = map_eq.codelength();
2478
2479 let ft = fine_tuning(&mut map_eq, &flow_data, &graph, &mut partition, &mut rng);
2480 let cl_after_fine = map_eq.codelength();
2481
2482 assert!(
2483 cl_after_fine <= cl_after_local + 1e-12,
2484 "fine-tuning should not increase codelength: before={cl_after_local:.6}, after={cl_after_fine:.6}"
2485 );
2486
2487 let ct = coarse_tuning(&mut map_eq, &flow_data, &graph, &mut partition, &mut rng);
2488 let cl_after_coarse = map_eq.codelength();
2489
2490 assert!(
2491 cl_after_coarse <= cl_after_fine + 1e-12,
2492 "coarse-tuning should not increase codelength: before={cl_after_fine:.6}, after={cl_after_coarse:.6}"
2493 );
2494 }
2495
2496 #[test]
2497 fn test_infomap_renumber_partition() {
2498 let mut partition = vec![5, 5, 3, 7, 3];
2499 renumber_partition(&mut partition);
2500 assert_eq!(partition, vec![0, 0, 1, 2, 1]);
2501 }
2502}