1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
//! Hierarchical community detection output.
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::partition::Partition;
/// A single level in the hierarchical community detection output.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub struct HierarchyLevel {
/// Number of nodes at this aggregation level.
pub node_count: usize,
/// Number of communities at this level.
pub num_communities: usize,
/// Quality score at this level.
pub quality: f64,
/// Community membership of each original node at this level.
pub membership: Vec<usize>,
}
/// Hierarchical output from the Leiden algorithm, capturing intermediate
/// community structure at each aggregation level.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub struct HierarchicalOutput {
/// Community structure at each aggregation level.
pub levels: Vec<HierarchyLevel>,
/// The final community partition (same as `run()` would produce).
pub partition: Partition,
/// Quality score of the final partition.
pub quality: f64,
}
impl HierarchicalOutput {
/// Number of hierarchy levels recorded.
pub fn num_levels(&self) -> usize {
self.levels.len()
}
/// Get community of original node at a specific level.
///
/// Level 0 = finest granularity, last level = coarsest.
///
/// Returns 0 if `level` or `node` is out of range.
pub fn community_of_at_level(&self, node: usize, level: usize) -> usize {
if level >= self.levels.len() || node >= self.levels[level].membership.len() {
return 0;
}
self.levels[level].membership[node]
}
/// Get the membership vector at a specific level.
pub fn membership_at_level(&self, level: usize) -> &[usize] {
&self.levels[level].membership
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::partition::Partition;
#[test]
fn test_community_of_at_level_bounds_check() {
let level = HierarchyLevel {
node_count: 3,
num_communities: 2,
quality: 0.5,
membership: vec![0, 0, 1],
};
let output = HierarchicalOutput {
levels: vec![level],
partition: Partition::new(3),
quality: 0.5,
};
// Node out of range — should not panic
let result = output.community_of_at_level(10, 0);
assert_eq!(result, 0, "out-of-range node should return 0");
// Level out of range — should not panic
let result = output.community_of_at_level(0, 5);
assert_eq!(result, 0, "out-of-range level should return 0");
// Both out of range — should not panic
let result = output.community_of_at_level(10, 5);
assert_eq!(result, 0, "both out of range should return 0");
}
}