use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum PartitionMethod {
SpectralBisection,
MultilevelKL,
Streaming,
}
impl fmt::Display for PartitionMethod {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::SpectralBisection => write!(f, "SpectralBisection"),
Self::MultilevelKL => write!(f, "MultilevelKL"),
Self::Streaming => write!(f, "Streaming"),
}
}
}
#[derive(Debug, Clone)]
pub struct PartitionConfig {
pub n_partitions: usize,
pub balance_tolerance: f64,
pub coarsening_threshold: usize,
pub kl_max_passes: usize,
pub method: PartitionMethod,
}
impl Default for PartitionConfig {
fn default() -> Self {
Self {
n_partitions: 2,
balance_tolerance: 0.03,
coarsening_threshold: 100,
kl_max_passes: 10,
method: PartitionMethod::MultilevelKL,
}
}
}
#[derive(Debug, Clone)]
pub struct PartitionResult {
pub assignments: Vec<usize>,
pub edge_cut: usize,
pub partition_sizes: Vec<usize>,
pub imbalance: f64,
}
impl PartitionResult {
pub(crate) fn from_assignments(
assignments: &[usize],
adj: &scirs2_core::ndarray::Array2<f64>,
n_partitions: usize,
) -> Self {
let n = assignments.len();
let mut partition_sizes = vec![0usize; n_partitions];
for &a in assignments {
if a < n_partitions {
partition_sizes[a] += 1;
}
}
let mut edge_cut = 0usize;
for i in 0..n {
for j in (i + 1)..n {
if adj[[i, j]].abs() > 1e-15 && assignments[i] != assignments[j] {
edge_cut += 1;
}
}
}
let ideal = n as f64 / n_partitions as f64;
let imbalance = if ideal > 0.0 {
partition_sizes
.iter()
.map(|&s| ((s as f64) - ideal).abs() / ideal)
.fold(0.0f64, f64::max)
} else {
0.0
};
Self {
assignments: assignments.to_vec(),
edge_cut,
partition_sizes,
imbalance,
}
}
}
impl fmt::Display for PartitionResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"PartitionResult {{ partitions: {}, edge_cut: {}, sizes: {:?}, imbalance: {:.4} }}",
self.partition_sizes.len(),
self.edge_cut,
self.partition_sizes,
self.imbalance
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use scirs2_core::ndarray::Array2;
#[test]
fn test_default_config() {
let cfg = PartitionConfig::default();
assert_eq!(cfg.n_partitions, 2);
assert!((cfg.balance_tolerance - 0.03).abs() < 1e-10);
assert_eq!(cfg.coarsening_threshold, 100);
assert_eq!(cfg.kl_max_passes, 10);
assert_eq!(cfg.method, PartitionMethod::MultilevelKL);
}
#[test]
fn test_partition_result_from_assignments() {
let mut adj = Array2::<f64>::zeros((4, 4));
adj[[0, 1]] = 1.0;
adj[[1, 0]] = 1.0;
adj[[1, 2]] = 1.0;
adj[[2, 1]] = 1.0;
adj[[2, 3]] = 1.0;
adj[[3, 2]] = 1.0;
let assignments = vec![0, 0, 1, 1];
let result = PartitionResult::from_assignments(&assignments, &adj, 2);
assert_eq!(result.partition_sizes, vec![2, 2]);
assert_eq!(result.edge_cut, 1); assert!(result.imbalance < 1e-10); }
#[test]
fn test_partition_method_display() {
assert_eq!(
format!("{}", PartitionMethod::SpectralBisection),
"SpectralBisection"
);
assert_eq!(format!("{}", PartitionMethod::MultilevelKL), "MultilevelKL");
assert_eq!(format!("{}", PartitionMethod::Streaming), "Streaming");
}
#[test]
fn test_partition_result_display() {
let result = PartitionResult {
assignments: vec![0, 0, 1, 1],
edge_cut: 1,
partition_sizes: vec![2, 2],
imbalance: 0.0,
};
let s = format!("{}", result);
assert!(s.contains("edge_cut: 1"));
assert!(s.contains("partitions: 2"));
}
}