scirs2_graph/partitioning/
types.rs1use std::fmt;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10#[non_exhaustive]
11pub enum PartitionMethod {
12 SpectralBisection,
14 MultilevelKL,
16 Streaming,
18}
19
20impl fmt::Display for PartitionMethod {
21 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22 match self {
23 Self::SpectralBisection => write!(f, "SpectralBisection"),
24 Self::MultilevelKL => write!(f, "MultilevelKL"),
25 Self::Streaming => write!(f, "Streaming"),
26 }
27 }
28}
29
30#[derive(Debug, Clone)]
32pub struct PartitionConfig {
33 pub n_partitions: usize,
35 pub balance_tolerance: f64,
37 pub coarsening_threshold: usize,
39 pub kl_max_passes: usize,
41 pub method: PartitionMethod,
43}
44
45impl Default for PartitionConfig {
46 fn default() -> Self {
47 Self {
48 n_partitions: 2,
49 balance_tolerance: 0.03,
50 coarsening_threshold: 100,
51 kl_max_passes: 10,
52 method: PartitionMethod::MultilevelKL,
53 }
54 }
55}
56
57#[derive(Debug, Clone)]
59pub struct PartitionResult {
60 pub assignments: Vec<usize>,
62 pub edge_cut: usize,
64 pub partition_sizes: Vec<usize>,
66 pub imbalance: f64,
69}
70
71impl PartitionResult {
72 pub(crate) fn from_assignments(
74 assignments: &[usize],
75 adj: &scirs2_core::ndarray::Array2<f64>,
76 n_partitions: usize,
77 ) -> Self {
78 let n = assignments.len();
79 let mut partition_sizes = vec![0usize; n_partitions];
80 for &a in assignments {
81 if a < n_partitions {
82 partition_sizes[a] += 1;
83 }
84 }
85
86 let mut edge_cut = 0usize;
88 for i in 0..n {
89 for j in (i + 1)..n {
90 if adj[[i, j]].abs() > 1e-15 && assignments[i] != assignments[j] {
91 edge_cut += 1;
92 }
93 }
94 }
95
96 let ideal = n as f64 / n_partitions as f64;
98 let imbalance = if ideal > 0.0 {
99 partition_sizes
100 .iter()
101 .map(|&s| ((s as f64) - ideal).abs() / ideal)
102 .fold(0.0f64, f64::max)
103 } else {
104 0.0
105 };
106
107 Self {
108 assignments: assignments.to_vec(),
109 edge_cut,
110 partition_sizes,
111 imbalance,
112 }
113 }
114}
115
116impl fmt::Display for PartitionResult {
117 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118 write!(
119 f,
120 "PartitionResult {{ partitions: {}, edge_cut: {}, sizes: {:?}, imbalance: {:.4} }}",
121 self.partition_sizes.len(),
122 self.edge_cut,
123 self.partition_sizes,
124 self.imbalance
125 )
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132 use scirs2_core::ndarray::Array2;
133
134 #[test]
135 fn test_default_config() {
136 let cfg = PartitionConfig::default();
137 assert_eq!(cfg.n_partitions, 2);
138 assert!((cfg.balance_tolerance - 0.03).abs() < 1e-10);
139 assert_eq!(cfg.coarsening_threshold, 100);
140 assert_eq!(cfg.kl_max_passes, 10);
141 assert_eq!(cfg.method, PartitionMethod::MultilevelKL);
142 }
143
144 #[test]
145 fn test_partition_result_from_assignments() {
146 let mut adj = Array2::<f64>::zeros((4, 4));
148 adj[[0, 1]] = 1.0;
149 adj[[1, 0]] = 1.0;
150 adj[[1, 2]] = 1.0;
151 adj[[2, 1]] = 1.0;
152 adj[[2, 3]] = 1.0;
153 adj[[3, 2]] = 1.0;
154
155 let assignments = vec![0, 0, 1, 1];
156 let result = PartitionResult::from_assignments(&assignments, &adj, 2);
157 assert_eq!(result.partition_sizes, vec![2, 2]);
158 assert_eq!(result.edge_cut, 1); assert!(result.imbalance < 1e-10); }
161
162 #[test]
163 fn test_partition_method_display() {
164 assert_eq!(
165 format!("{}", PartitionMethod::SpectralBisection),
166 "SpectralBisection"
167 );
168 assert_eq!(format!("{}", PartitionMethod::MultilevelKL), "MultilevelKL");
169 assert_eq!(format!("{}", PartitionMethod::Streaming), "Streaming");
170 }
171
172 #[test]
173 fn test_partition_result_display() {
174 let result = PartitionResult {
175 assignments: vec![0, 0, 1, 1],
176 edge_cut: 1,
177 partition_sizes: vec![2, 2],
178 imbalance: 0.0,
179 };
180 let s = format!("{}", result);
181 assert!(s.contains("edge_cut: 1"));
182 assert!(s.contains("partitions: 2"));
183 }
184}