goatd 0.1.1

Greatest Of All Tree Decompositions: tree decompositions of graphs — elimination orders, FlowCutter, multilevel bisection — with PACE .gr/.td I/O and a command-line solver.
Documentation
use crate::partition::common::{
    GainBuckets, Stall, balance_bounds, commit_best_prefix, fm_balance, index_split, lift_to_fine,
    project_to_coarse, random_bisection, repair_bisection, tiny_bisection,
};
use crate::rng::Xorshift64;

#[test]
fn the_index_and_tiny_fallbacks_cover_their_exact_domains() {
    assert_eq!(index_split(0), Vec::<u8>::new());
    assert_eq!(index_split(1), vec![1]);
    assert_eq!(index_split(5), vec![0, 0, 1, 1, 1]);

    assert_eq!(tiny_bisection(0), Some(vec![]));
    assert_eq!(tiny_bisection(1), Some(vec![0]));
    assert_eq!(tiny_bisection(2), Some(vec![0, 1]));
    assert_eq!(tiny_bisection(3), None);
}

#[test]
fn bisections_project_and_lift_through_a_coarsening() {
    let mut counts = Vec::new();
    let mut coarse = Vec::new();
    project_to_coarse(
        &[0, 1, 1, 1, 0],
        &[0, 0, 1, 1, 1],
        2,
        &mut counts,
        &mut coarse,
    );
    assert_eq!(coarse, [0, 1]);

    let mut fine = Vec::new();
    lift_to_fine(&coarse, &[0, 0, 1, 1, 1], &mut fine);
    assert_eq!(fine, [0, 0, 1, 1, 1]);
}

#[test]
fn bisection_repair_enforces_nonempty_sides() {
    assert_eq!(repair_bisection(vec![0, 0, 0, 0], 0.5), [0, 0, 0, 1]);
    assert_eq!(repair_bisection(vec![0, 1, 1], 0.5), [0, 1, 1]);
}

#[test]
fn bisection_repair_moves_only_the_excess_assignments() {
    assert_eq!(
        repair_bisection(vec![0, 0, 0, 0, 0, 1], 0.0),
        [0, 0, 0, 1, 1, 1],
    );
    assert_eq!(repair_bisection(vec![0, 1, 1, 1, 1], 0.1), [0, 1, 1, 1, 0],);
}

#[test]
fn balance_bounds_and_current_weights_use_vertex_weight() {
    assert_eq!(balance_bounds(&[3, 2, 1, 2], 0.25), (2, 6));
    assert_eq!(balance_bounds(&[1, 1, 1], 0.0), (1, 2));
    assert!(fm_balance(2, &[1, 1], &[0, 1], 0.0).is_none());

    let balance =
        fm_balance(4, &[3, 2, 1, 2], &[0, 1, 1, 0], 0.25).expect("four vertices can be refined");
    assert_eq!(balance.weight, [5, 3]);
    assert_eq!(balance.min_part_weight, 2);
    assert_eq!(balance.max_part_weight, 6);
}

#[test]
fn a_random_bisection_repeats_and_never_overfills_its_first_side() {
    let weights = [5, 3, 2, 1, 1];
    let run = || random_bisection(&weights, &mut Xorshift64::from_state(17));

    let part = run();
    assert_eq!(part, run());
    let weight0: u32 = weights
        .iter()
        .zip(&part)
        .map(|(&weight, &side)| if side == 0 { weight } else { 0 })
        .sum();
    assert!(weight0 <= weights.iter().sum::<u32>() / 2);
}

#[test]
fn committing_moves_keeps_only_the_best_positive_prefix() {
    let moves = [0, 1, 2];
    let mut part = vec![1, 1, 1];

    assert!(commit_best_prefix(&moves, &[-1, 2, 1], &mut part));
    assert_eq!(part, vec![1, 1, 0]);

    let mut non_improving = vec![1, 1];
    assert!(!commit_best_prefix(&[0, 1], &[-1, 0], &mut non_improving,));
    assert_eq!(non_improving, vec![0, 0]);

    let mut untouched = vec![0, 1];
    assert!(!commit_best_prefix(&[], &[], &mut untouched));
    assert_eq!(untouched, vec![0, 1]);
}

#[test]
fn gain_buckets_track_the_best_gain_and_most_recent_tie() {
    let mut queue = GainBuckets::new(3);
    queue.insert(0, -1);
    queue.insert(1, 2);
    queue.insert(2, 2);

    assert_eq!(queue.best_satisfying(|_| true), Some(2));
    assert!(queue.contains(0) && queue.contains(1) && queue.contains(2));

    queue.update(0, 3);
    assert_eq!(queue.best_satisfying(|_| true), Some(0));
    queue.remove(0);
    assert_eq!(queue.best_satisfying(|_| true), Some(2));
    queue.remove(2);
    assert_eq!(queue.best_satisfying(|_| true), Some(1));
    queue.remove(1);
    assert_eq!(queue.best_satisfying(|_| true), None);
    queue.remove(1);
}

#[test]
fn gain_buckets_do_not_allocate_the_numeric_range_between_gains() {
    let mut queue = GainBuckets::new(2);
    queue.insert(0, i64::MIN);
    queue.insert(1, i64::MAX);

    assert_eq!(queue.best_satisfying(|_| true), Some(1));
    queue.remove(1);
    assert_eq!(queue.best_satisfying(|_| true), Some(0));
}

#[test]
fn gain_buckets_skip_an_ineligible_vertex_without_removing_it() {
    let mut queue = GainBuckets::new(3);
    queue.insert(0, 5);
    queue.insert(1, 4);
    queue.insert(2, 3);

    assert_eq!(queue.best_satisfying(|vertex| vertex != 0), Some(1));
    assert_eq!(queue.best_satisfying(|_| true), Some(0));
}

#[test]
fn a_stall_resets_only_for_a_strictly_better_running_gain() {
    let mut stall = Stall::new(2);

    assert!(!stall.record(1));
    assert!(!stall.record(1));
    assert!(stall.record(0));

    let mut reset = Stall::new(2);
    assert!(!reset.record(0));
    assert!(!reset.record(1));
    assert!(!reset.record(1));
    assert!(reset.record(1));
}