pub fn kl_refine(
adj_offsets: &[i64],
adj_neighbours: &[i32],
adj_scc_abs: &[f64],
vertex_weights: &[f64],
part_map: &mut [i32],
parts_concat: &[i32],
parts_offsets: &[i64],
n_parts: i32,
kl_iterations: i32,
correlation_penalty: f64,
) -> u64 {
let n_parts_us = n_parts as usize;
let mut parts: Vec<Vec<i32>> = vec![Vec::new(); n_parts_us];
for i in 0..n_parts_us {
let lo = parts_offsets[i] as usize;
let hi = parts_offsets[i + 1] as usize;
parts[i].extend_from_slice(&parts_concat[lo..hi]);
}
let mut weight_to: Vec<f64> = vec![0.0; n_parts_us];
let mut total_moves: u64 = 0;
for _ in 0..kl_iterations {
let mut improved = false;
for i in 0..n_parts_us {
let snapshot: Vec<i32> = parts[i].clone();
for v_i32 in snapshot {
let v = v_i32 as usize;
if parts[i].len() <= 1 {
continue;
}
let cur_part = part_map[v];
if (cur_part as usize) != i {
continue;
}
let vw = vertex_weights[v];
for w in weight_to.iter_mut() {
*w = 0.0;
}
let mut total_weight: f64 = 0.0;
let lo = adj_offsets[v] as usize;
let hi = adj_offsets[v + 1] as usize;
for k in lo..hi {
let n = adj_neighbours[k] as usize;
let scc = adj_scc_abs[k];
let contrib = vw * (1.0 + scc * correlation_penalty);
total_weight += contrib;
let tgt = part_map[n];
if (0..n_parts).contains(&tgt) {
weight_to[tgt as usize] += contrib;
}
}
let current_cost = total_weight - weight_to[i];
let mut best_target = i as i32;
let mut best_gain: f64 = 0.0;
for j in 0..n_parts_us {
if j == i {
continue;
}
let cand_cost = total_weight - weight_to[j];
let gain = current_cost - cand_cost;
if gain > best_gain {
best_gain = gain;
best_target = j as i32;
}
}
if best_target != (i as i32) && best_gain > 0.0 {
let pos = parts[i]
.iter()
.position(|&x| x == v_i32)
.expect("v in parts[i]");
parts[i].remove(pos);
parts[best_target as usize].push(v_i32);
part_map[v] = best_target;
total_moves += 1;
improved = true;
}
}
}
if !improved {
break;
}
}
total_moves
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_graph_zero_moves() {
let part: &mut [i32] = &mut [];
let n = kl_refine(&[0], &[], &[], &[], part, &[], &[0, 0, 0], 2, 5, 0.5);
assert_eq!(n, 0);
}
#[test]
fn single_partition_no_moves() {
let mut pm = vec![0i32, 0, 0, 0];
let offsets = vec![0i64, 1, 2, 3, 4];
let neighbours = vec![1i32, 0, 3, 2];
let scc = vec![0.1, 0.1, 0.1, 0.1];
let vw = vec![1.0; 4];
let parts_concat = vec![0i32, 1, 2, 3];
let parts_offsets = vec![0i64, 4];
let n = kl_refine(
&offsets,
&neighbours,
&scc,
&vw,
&mut pm,
&parts_concat,
&parts_offsets,
1,
5,
0.5,
);
assert_eq!(n, 0);
assert_eq!(pm, vec![0, 0, 0, 0]);
}
#[test]
fn two_partitions_isolated_swap() {
let offsets = vec![0i64, 1, 2, 3, 4];
let neighbours = vec![1i32, 0, 3, 2];
let scc = vec![0.5, 0.5, 0.5, 0.5];
let vw = vec![1.0; 4];
let mut pm = vec![0i32, 1, 0, 1];
let parts_concat = vec![0i32, 2, 1, 3];
let parts_offsets = vec![0i64, 2, 4];
let n = kl_refine(
&offsets,
&neighbours,
&scc,
&vw,
&mut pm,
&parts_concat,
&parts_offsets,
2,
5,
0.5,
);
assert!(n > 0);
}
#[test]
fn part_size_one_vertex_pinned() {
let offsets = vec![0i64, 2, 3, 4];
let neighbours = vec![1i32, 2, 0, 0];
let scc = vec![0.1, 0.1, 0.1, 0.1];
let vw = vec![1.0; 3];
let mut pm = vec![0i32, 1, 1];
let parts_concat = vec![0i32, 1, 2];
let parts_offsets = vec![0i64, 1, 3];
let _ = kl_refine(
&offsets,
&neighbours,
&scc,
&vw,
&mut pm,
&parts_concat,
&parts_offsets,
2,
5,
0.5,
);
assert_eq!(pm[0], 0, "lone-vertex partition must stay populated");
}
}