mod coarsen;
mod initial;
mod model;
mod refine_flow;
mod refine_fm;
#[cfg(test)]
mod tests;
use coarsen::{CoarseningLevel, coarsen_one_level};
use initial::{hyperedge_cut, initial_partition};
use refine_flow::refine_finest_level;
use refine_fm::refine_level;
use crate::Error;
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};
const MIN_HG_COARSEN_SIZE: usize = 20;
const MAX_EFFORT: f64 = 100.0;
pub use model::Hypergraph;
#[derive(Clone, Copy, Debug, PartialEq)]
#[must_use]
pub struct HypergraphBisectionConfig {
max_imbalance: f64,
seed: u64,
effort: f64,
}
impl HypergraphBisectionConfig {
pub fn new(max_imbalance: f64, seed: u64) -> Self {
Self {
max_imbalance,
seed,
effort: 1.0,
}
}
pub fn with_effort(mut self, effort: f64) -> Self {
self.effort = effort;
self
}
}
fn num_hg_restarts(n: usize, effort_scale: f64) -> usize {
let base = if n >= 400 {
6
} else if n >= 100 {
4
} else {
2
};
(((base as f64) * effort_scale.sqrt()).round() as usize).max(1)
}
fn multilevel_pass(
hg: &Hypergraph,
existing_part: Option<&[u8]>,
rng: &mut Xorshift64,
imbalance: f64,
) -> Vec<u8> {
let n = hg.num_vertices;
let mut levels: Vec<CoarseningLevel> = Vec::new();
let mut current = hg;
let mut projected_part: Option<Vec<u8>> = existing_part.map(|p| p.to_vec());
let mut projection_counts = Vec::with_capacity(n);
let mut projection = Vec::with_capacity(n);
loop {
let coarse_part_ref = projected_part.as_deref();
if let Some(level) = coarsen_one_level(current, MIN_HG_COARSEN_SIZE, rng, coarse_part_ref) {
if let Some(ref mut pp) = projected_part {
let nc = level.hg.num_vertices;
project_to_coarse(
pp,
&level.mapping,
nc,
&mut projection_counts,
&mut projection,
);
std::mem::swap(pp, &mut projection);
}
levels.push(level);
current = &levels.last().unwrap().hg;
} else {
break;
}
}
let mut part = if let Some(pp) = projected_part {
pp
} else {
initial_partition(current, rng, imbalance)
};
refine_level(current, &mut part, imbalance);
for (li, level) in levels.iter().enumerate().rev() {
lift_to_fine(&part, &level.mapping, &mut projection);
std::mem::swap(&mut part, &mut projection);
let fine_hg = if li > 0 { &levels[li - 1].hg } else { hg };
if li == 0 {
refine_finest_level(fine_hg, &mut part, imbalance);
} else {
refine_level(fine_hg, &mut part, imbalance);
}
}
repair_bisection(part, imbalance)
}
fn multilevel_bisect_once(
hg: &Hypergraph,
rng: &mut Xorshift64,
imbalance: f64,
effort_scale: f64,
) -> Vec<u8> {
let mut part = multilevel_pass(hg, None, rng, imbalance);
let vc_base = if hg.num_vertices >= 400 {
4
} else if hg.num_vertices >= 100 {
2
} else {
1
};
let num_vcycles = (vc_base as f64 * effort_scale.sqrt()).round() as usize;
for _ in 0..num_vcycles {
let old_cut = hyperedge_cut(hg, &part);
let new_part = multilevel_pass(hg, Some(&part), rng, imbalance);
let new_cut = hyperedge_cut(hg, &new_part);
if new_cut < old_cut {
part = new_part;
} else {
break;
}
}
part
}
pub fn multilevel_hypergraph_bisect(
hg: &Hypergraph,
config: HypergraphBisectionConfig,
) -> Result<Bisection, Error> {
validate_max_imbalance(config.max_imbalance, "hypergraph-bisection")?;
if !config.effort.is_finite() || config.effort <= 0.0 || config.effort > MAX_EFFORT {
return Err(Error::InvalidInput(format!(
"hypergraph-bisection effort must be greater than 0.0 and at most {MAX_EFFORT}, got {}",
config.effort,
)));
}
let num_vertices = hg.num_vertices;
if let Some(part) = tiny_bisection(num_vertices) {
return Ok(Bisection::new(part));
}
if hg.num_hyperedges() == 0 {
return Ok(Bisection::new(index_split(num_vertices)));
}
let mut best_part = Vec::new();
let mut best_cut = u32::MAX;
let restarts = num_hg_restarts(num_vertices, config.effort);
for restart in 0..restarts {
let mut rng = bisector_stream(restart_seed(config.seed, restart));
let part = multilevel_bisect_once(hg, &mut rng, config.max_imbalance, config.effort);
let candidate_cut = hyperedge_cut(hg, &part);
if candidate_cut < best_cut {
best_cut = candidate_cut;
best_part = part;
}
}
Ok(Bisection::new(best_part))
}