#[cfg(test)]
mod tests;
use std::collections::BTreeMap;
use crate::Error;
use crate::rng::Xorshift64;
pub(super) fn validate_max_imbalance(value: f64, kind: &str) -> Result<(), Error> {
if !value.is_finite() || !(0.0..=0.5).contains(&value) {
return Err(Error::InvalidInput(format!(
"{kind} imbalance must be in 0.0..=0.5, got {value}"
)));
}
Ok(())
}
pub(super) fn index_split(n: usize) -> Vec<u8> {
let mut part = vec![0u8; n];
part[n / 2..].fill(1);
part
}
pub(super) fn tiny_bisection(n: usize) -> Option<Vec<u8>> {
match n {
0 => Some(Vec::new()),
1 => Some(vec![0]),
2 => Some(vec![0, 1]),
_ => None,
}
}
pub(super) fn project_to_coarse(
fine: &[u8],
mapping: &[u32],
num_coarse_vertices: usize,
counts: &mut Vec<[u32; 2]>,
coarse: &mut Vec<u8>,
) {
counts.clear();
counts.resize(num_coarse_vertices, [0, 0]);
for (vertex, &coarse_vertex) in mapping.iter().enumerate() {
counts[coarse_vertex as usize][fine[vertex] as usize] += 1;
}
coarse.clear();
coarse.resize(num_coarse_vertices, 0);
for vertex in 0..num_coarse_vertices {
coarse[vertex] = u8::from(counts[vertex][1] > counts[vertex][0]);
}
}
pub(super) fn lift_to_fine(coarse: &[u8], mapping: &[u32], fine: &mut Vec<u8>) {
fine.clear();
fine.extend(mapping.iter().map(|&vertex| coarse[vertex as usize]));
}
pub(super) fn repair_bisection(mut part: Vec<u8>, max_imbalance: f64) -> Vec<u8> {
let num_vertices = part.len();
if num_vertices < 2 {
return part;
}
let max_side_size = ((num_vertices as f64) * (0.5 + max_imbalance))
.ceil()
.min((num_vertices - 1) as f64) as usize;
let side_zero_size = part.iter().filter(|&&side| side == 0).count();
if side_zero_size > max_side_size {
let mut to_move = side_zero_size - max_side_size;
for side in part.iter_mut().rev() {
if *side == 0 && to_move > 0 {
*side = 1;
to_move -= 1;
}
}
} else if num_vertices - side_zero_size > max_side_size {
let mut to_move = num_vertices - side_zero_size - max_side_size;
for side in part.iter_mut().rev() {
if *side == 1 && to_move > 0 {
*side = 0;
to_move -= 1;
}
}
}
part
}
pub(super) fn balance_bounds(vertex_weights: &[u32], max_imbalance: f64) -> (u32, u32) {
let total_weight: u32 = vertex_weights.iter().sum();
let max_part_weight = ((total_weight as f64) * (0.5 + max_imbalance)).ceil() as u32;
let min_part_weight = total_weight.saturating_sub(max_part_weight);
(min_part_weight, max_part_weight)
}
pub(super) struct FmBalance {
pub(super) weight: [u32; 2],
pub(super) min_part_weight: u32,
pub(super) max_part_weight: u32,
}
pub(super) fn fm_balance(
n: usize,
vertex_weights: &[u32],
part: &[u8],
max_imbalance: f64,
) -> Option<FmBalance> {
if n <= 2 {
return None;
}
let (min_part_weight, max_part_weight) = balance_bounds(vertex_weights, max_imbalance);
let mut weight = [0u32; 2];
for v in 0..n {
weight[part[v] as usize] += vertex_weights[v];
}
Some(FmBalance {
weight,
min_part_weight,
max_part_weight,
})
}
pub(super) fn random_bisection(vertex_weights: &[u32], rng: &mut Xorshift64) -> Vec<u8> {
let n = vertex_weights.len();
let total_weight: u32 = vertex_weights.iter().sum();
let target = total_weight / 2;
let mut perm: Vec<usize> = (0..n).collect();
for i in (1..n).rev() {
let j = (rng.next_u64() as usize) % (i + 1);
perm.swap(i, j);
}
let mut part = vec![1u8; n];
let mut weight0: u32 = 0;
for &v in &perm {
if weight0 + vertex_weights[v] <= target {
part[v] = 0;
weight0 += vertex_weights[v];
}
}
part
}
pub(super) fn commit_best_prefix(
moves: &[usize],
cumulative_gain: &[i64],
part: &mut [u8],
) -> bool {
if moves.is_empty() {
return false;
}
let mut best_index = None;
let mut best_prefix_gain = 0i64;
for (index, &gain) in cumulative_gain.iter().enumerate() {
if gain > best_prefix_gain {
best_prefix_gain = gain;
best_index = Some(index);
}
}
let Some(best_index) = best_index else {
for &v in moves.iter().rev() {
part[v] = 1 - part[v];
}
return false;
};
for &v in moves[(best_index + 1)..].iter().rev() {
part[v] = 1 - part[v];
}
true
}
pub(super) struct GainBuckets {
buckets: BTreeMap<i64, Vec<usize>>,
gain_of: Vec<Option<i64>>,
pos_in_bucket: Vec<usize>,
}
impl GainBuckets {
pub(super) fn new(n: usize) -> Self {
let mut queue = GainBuckets::empty();
queue.reset(n);
queue
}
pub(super) fn empty() -> Self {
GainBuckets {
buckets: BTreeMap::new(),
gain_of: Vec::new(),
pos_in_bucket: Vec::new(),
}
}
pub(super) fn reset(&mut self, n: usize) {
self.buckets.clear();
self.gain_of.clear();
self.gain_of.resize(n, None);
self.pos_in_bucket.clear();
self.pos_in_bucket.resize(n, usize::MAX);
}
pub(super) fn contains(&self, v: usize) -> bool {
self.gain_of[v].is_some()
}
pub(super) fn best_satisfying(
&self,
mut predicate: impl FnMut(usize) -> bool,
) -> Option<usize> {
self.buckets
.iter()
.rev()
.flat_map(|(_, vertices)| vertices.iter().rev().copied())
.find(|&vertex| predicate(vertex))
}
pub(super) fn insert(&mut self, v: usize, gain: i64) {
debug_assert!(self.gain_of[v].is_none());
let bucket = self.buckets.entry(gain).or_default();
self.pos_in_bucket[v] = bucket.len();
bucket.push(v);
self.gain_of[v] = Some(gain);
}
pub(super) fn remove(&mut self, v: usize) {
let Some(gain) = self.gain_of[v].take() else {
return;
};
let pos = self.pos_in_bucket[v];
let remove_bucket = {
let bucket = self.buckets.get_mut(&gain).expect("gain bucket missing");
bucket.swap_remove(pos);
if pos < bucket.len() {
let moved = bucket[pos];
self.pos_in_bucket[moved] = pos;
}
bucket.is_empty()
};
if remove_bucket {
self.buckets.remove(&gain);
}
self.pos_in_bucket[v] = usize::MAX;
}
pub(super) fn update(&mut self, v: usize, new_gain: i64) {
self.remove(v);
self.insert(v, new_gain);
}
}
pub(super) struct Stall {
limit: usize,
since_improvement: usize,
best_gain: i64,
}
impl Stall {
pub(super) fn new(limit: usize) -> Self {
Stall {
limit,
since_improvement: 0,
best_gain: 0,
}
}
pub(super) fn record(&mut self, running_gain: i64) -> bool {
if running_gain > self.best_gain {
self.best_gain = running_gain;
self.since_improvement = 0;
false
} else {
self.since_improvement += 1;
self.since_improvement >= self.limit
}
}
}