use crate::partition::Bisection;
use crate::partition::common::{
index_split, lift_to_fine, project_to_coarse, repair_bisection, tiny_bisection,
validate_max_imbalance,
};
use crate::rng::{Xorshift64, bisector_stream, restart_seed};
use crate::{Error, Graph};
mod coarsen;
mod csr;
mod initial;
mod refine_fm;
#[cfg(test)]
mod tests;
use coarsen::{CoarseningLevel, coarsen_one_level};
use csr::{CsrGraph, build_csr};
use initial::{edge_cut, initial_partition};
use refine_fm::{FmScratch, refine_finest_level, refine_level};
const MIN_COARSEN_SIZE: usize = 20;
const MAX_BISECTION_EDGES: usize = u32::MAX as usize / 2;
fn validate_size(num_edges: usize) -> Result<(), Error> {
if num_edges > MAX_BISECTION_EDGES {
return Err(Error::TooLarge(format!(
"graph has {num_edges} edges; multilevel bisection supports at most {MAX_BISECTION_EDGES}"
)));
}
Ok(())
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[must_use]
pub struct GraphBisectionConfig {
max_imbalance: f64,
seed: u64,
}
impl GraphBisectionConfig {
pub fn new(max_imbalance: f64, seed: u64) -> Self {
Self {
max_imbalance,
seed,
}
}
}
fn multilevel_pass(
graph: &CsrGraph,
part: Option<&[u8]>,
rng: &mut Xorshift64,
max_imbalance: f64,
scratch: &mut FmScratch,
) -> Vec<u8> {
let n = graph.num_vertices();
let mut count_scratch: Vec<[u32; 2]> = Vec::with_capacity(n);
let mut proj_scratch: Vec<u8> = Vec::with_capacity(n);
let mut levels: Vec<CoarseningLevel> = Vec::new();
let mut current = graph;
loop {
let mut fine_part: Option<Vec<u8>> = None;
if let Some(p) = part {
let mut fp = p.to_vec();
for lv in &levels {
let nc = lv.graph.num_vertices();
project_to_coarse(&fp, &lv.mapping, nc, &mut count_scratch, &mut proj_scratch);
std::mem::swap(&mut fp, &mut proj_scratch);
}
fine_part = Some(fp);
}
let level = coarsen_one_level(current, MIN_COARSEN_SIZE, rng, fine_part.as_deref());
if let Some(level) = level {
levels.push(level);
current = &levels.last().unwrap().graph;
} else {
break;
}
}
let mut coarse_part = if let Some(p) = part {
let mut fine_part = p.to_vec();
for level in &levels {
let nc = level.graph.num_vertices();
project_to_coarse(
&fine_part,
&level.mapping,
nc,
&mut count_scratch,
&mut proj_scratch,
);
std::mem::swap(&mut fine_part, &mut proj_scratch);
}
fine_part
} else {
initial_partition(current, rng, max_imbalance, scratch)
};
refine_level(current, &mut coarse_part, max_imbalance, scratch);
let mut result_part = coarse_part;
for (li, level) in levels.iter().enumerate().rev() {
lift_to_fine(&result_part, &level.mapping, &mut proj_scratch);
std::mem::swap(&mut result_part, &mut proj_scratch);
let fine_graph = if li > 0 { &levels[li - 1].graph } else { graph };
if li == 0 {
refine_finest_level(fine_graph, &mut result_part, max_imbalance, scratch);
} else {
refine_level(fine_graph, &mut result_part, max_imbalance, scratch);
}
}
repair_bisection(result_part, max_imbalance)
}
fn multilevel_graph_bisect_once(
graph: &CsrGraph,
rng: &mut Xorshift64,
max_imbalance: f64,
scratch: &mut FmScratch,
) -> Vec<u8> {
let mut part = multilevel_pass(graph, None, rng, max_imbalance, scratch);
let num_vcycles = if graph.num_vertices() >= 400 {
4
} else if graph.num_vertices() >= 100 {
2
} else {
1
};
for _ in 0..num_vcycles {
let old_cut = edge_cut(graph, &part);
let new_part = multilevel_pass(graph, Some(&part), rng, max_imbalance, scratch);
let new_cut = edge_cut(graph, &new_part);
if new_cut < old_cut {
part = new_part;
} else {
break;
}
}
part
}
pub fn multilevel_graph_bisect(
graph: &Graph,
config: GraphBisectionConfig,
) -> Result<Bisection, Error> {
validate_max_imbalance(config.max_imbalance, "graph-bisection")?;
validate_size(graph.edges.len())?;
let n = graph.num_vertices as usize;
if let Some(part) = tiny_bisection(n) {
return Ok(Bisection::new(part));
}
let graph = build_csr(n, &graph.edges);
if graph.neighbors.is_empty() {
return Ok(Bisection::new(index_split(n)));
}
let mut scratch = FmScratch::new();
let mut rng = bisector_stream(restart_seed(config.seed, 0));
Ok(Bisection::new(multilevel_graph_bisect_once(
&graph,
&mut rng,
config.max_imbalance,
&mut scratch,
)))
}