Skip to content

Vn first multi #57

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 36 additions & 136 deletions src/algorithms/vn/first.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
use crate::Error;
use itertools::Itertools as _;
use num::FromPrimitive;
use num::One;
use num::Zero;
use rayon::iter::IntoParallelRefIterator as _;
use rayon::iter::ParallelIterator as _;
use std::iter::Sum;
use std::ops::AddAssign;
use std::ops::Sub;
use std::ops::SubAssign;

fn vn_first_mono<T>(
partition: &mut [usize],
Expand Down Expand Up @@ -39,18 +35,20 @@ where
let (min_load, mut max_load) = part_loads.iter().cloned().minmax().into_option().unwrap();
let mut imbalance = max_load.clone() - min_load;

let mut i = weights.len();
let mut i = 0;
let mut i_last = 0;
let mut algo_iterations = 0;
while i != i_last {
i = (i + 1) % weights.len();

loop {
// loop through the weights.
let p = partition[i];

if part_loads[p] < max_load {
// weight #i is not in the heaviest partition, and thus the move
// will not reduce the max imbalance.
i = (i + 1) % weights.len();
if i == i_last {
break;
}
continue;
}

Expand All @@ -66,150 +64,44 @@ where
let (new_min_load, new_max_load) =
part_loads.iter().cloned().minmax().into_option().unwrap();
let new_imbalance = new_max_load.clone() - new_min_load.clone();
if imbalance < new_imbalance {
if new_imbalance <= imbalance {
// The move decreases the partition imbalance.
imbalance = new_imbalance;
max_load = new_max_load;
partition[i] = q;
i_last = i;
} else {
// The move does not decrease the partition imbalance.
part_loads[p] += weights[i].clone();
part_loads[q] = part_loads[p].clone() - weights[i].clone();
continue;
}
imbalance = new_imbalance;
max_load = new_max_load;
partition[i] = q;
i_last = i;
Comment on lines -69 to -78
Copy link
Member Author

@hhirtz hhirtz Mar 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This diff is so that vnfirst does not make the move when T::partial_cmp(imbalance, new_imbalance) returns None (imbalance < new_imbalance would return false, thus making the move)

However, this means vnfirst only makes a move when there are gains on all criterion at once, which is overly restrictive.

}

algo_iterations += 1;
}

Ok(algo_iterations)
}

#[allow(dead_code)]
pub fn vn_first<const C: usize, T>(
partition: &mut [usize],
criteria: &[[T; C]],
num_parts: usize,
) -> usize
where
T: AddAssign + SubAssign + Sub<Output = T> + Sum,
T: Zero + One + FromPrimitive,
T: Copy + Ord,
{
if num_parts < 2 || criteria.is_empty() || C == 0 {
return 0;
}

assert_eq!(criteria.len(), partition.len());

let mut part_loads_per_criterion = {
let mut loads = vec![[T::zero(); C]; num_parts];
for (w, weight) in criteria.iter().enumerate() {
for (part_load, criterion) in loads[partition[w]].iter_mut().zip(weight) {
*part_load += *criterion;
}
}
loads
};
let total_weight_per_criterion = {
// TODO replace with .collect() once [_; C] implements FromIterator.
let mut ws = [T::zero(); C];
for c in 0..C {
ws[c] = part_loads_per_criterion[c].iter().cloned().sum();
}
ws
};
if total_weight_per_criterion.contains(&T::zero()) {
return 0;
}

let min_max_loads = |part_loads_per_criterion: &Vec<[T; C]>| -> [(T, T); C] {
// TODO replace with .collect() once [_; C] implements FromIterator.
let mut imbs = [(T::zero(), T::zero()); C];
for c in 0..C {
imbs[c] = part_loads_per_criterion[c]
.iter()
.cloned()
.minmax()
.into_option()
.unwrap();
}
imbs
};

let (global_min_load, mut global_max_load) = *min_max_loads(&part_loads_per_criterion)
.iter()
.max_by_key(|(min_load, max_load)| *max_load - *min_load)
.unwrap();
let mut imbalance = global_max_load - global_min_load;

let mut i = 0;
let mut i_last = 0;
let mut algo_iterations = 0;
while i != i_last {
i = (i + 1) % criteria.len();

// loop through the weights.
let p = partition[i];

if part_loads_per_criterion[p]
.iter()
.all(|criterion_load| *criterion_load < global_max_load)
{
// weight #i is not in the heaviest partition, and thus the move
// will not reduce the max imbalance.
continue;
}

for q in 0..num_parts {
// loop through the parts.
if p == q {
// weight #i is already in partition #q.
continue;
}

for c in 0..C {
part_loads_per_criterion[p][c] -= criteria[i][c];
part_loads_per_criterion[q][c] += criteria[i][c];
}
let (new_global_min_load, new_global_max_load) =
*min_max_loads(&part_loads_per_criterion)
.iter()
.max_by_key(|(min_load, max_load)| *max_load - *min_load)
.unwrap();
let new_imbalance = new_global_max_load - new_global_min_load;
if imbalance < new_imbalance {
// The move does not decrease the partition imbalance.
for c in 0..C {
part_loads_per_criterion[p][c] += criteria[i][c];
part_loads_per_criterion[q][c] -= criteria[i][c];
}
continue;
}
imbalance = new_imbalance;
global_max_load = new_global_max_load;
partition[i] = q;
i_last = i;
i = (i + 1) % weights.len();
if i == i_last {
break;
}

algo_iterations += 1;
}

algo_iterations
Ok(algo_iterations)
}

/// Trait alias for values accepted as weights by [VnFirst].
pub trait VnFirstWeight
where
Self: Clone + Send + Sync,
Self: Sum + PartialOrd + num::FromPrimitive + num::Zero + num::One,
Self: Sum + PartialOrd + num::Zero,
Self: Sub<Output = Self> + AddAssign,
{
}

impl<T> VnFirstWeight for T
where
Self: Clone + Send + Sync,
Self: Sum + PartialOrd + num::FromPrimitive + num::Zero + num::One,
Self: Sum + PartialOrd + num::Zero,
Self: Sub<Output = Self> + AddAssign,
{
}
Expand Down Expand Up @@ -306,18 +198,26 @@ mod tests {
#[test]
fn small() {
const W: [[i32; 2]; 6] = [[1, 2], [2, 4], [3, 6], [8, 4], [10, 5], [12, 6]];
let mut part = [0; W.len()];
let w: Vec<_> = W
.iter()
.map(|w| nalgebra::SVector::<i32, 2>::from(*w))
.collect();
let mut partition = [0; W.len()];

vn_first(&mut part, &W, 1);
vn_first_mono(&mut partition, &w, 2).unwrap();
println!("partition: {partition:?}");
let imbs_ini: Vec<i32> = (0..W[0].len())
.map(|c| imbalance::max_imbalance(2, &part, W.par_iter().map(|w| w[c])))
.map(|c| imbalance::max_imbalance(2, &partition, W.par_iter().map(|w| w[c])))
.collect();
vn_first_mono(&mut partition, &w, 2).unwrap();
println!("partition: {partition:?}");
let imbs_end: Vec<i32> = (0..W[0].len())
.map(|c| imbalance::max_imbalance(2, &partition, W.par_iter().map(|w| w[c])))
.collect();
vn_first(&mut part, &W, 2);
let imbs_end =
(0..W[0].len()).map(|c| imbalance::max_imbalance(2, &part, W.par_iter().map(|w| w[c])));
println!("imbalances: {imbs_end:?} < {imbs_ini:?}");
for (imb_ini, imb_end) in imbs_ini.into_iter().zip(imbs_end) {
println!("imbalance: {imb_end} < {imb_ini}");
assert!(imb_end <= imb_ini);
println!("imbalance : {} < {}", imb_end, imb_ini);
}
}

Expand All @@ -329,7 +229,7 @@ mod tests {
(weights, mut partition) in
(2..200_usize).prop_flat_map(|num_weights| {
let weights = prop::collection::vec(
(1..1000_i32, 1..1000_i32).prop_map(|(a, b)| [a, b]),
(1..1000_i32, 1..1000_i32).prop_map(|(a, b)| nalgebra::SVector::<i32, 2>::new(a, b)),
num_weights
);
let partition = prop::collection::vec(0..1_usize, num_weights);
Expand All @@ -339,7 +239,7 @@ mod tests {
let imbs_ini: Vec<i32> = (0..C)
.map(|c| imbalance::max_imbalance(2, &partition, weights.par_iter().map(|w| w[c])))
.collect();
vn_first(&mut partition, &weights, 2);
vn_first_mono(&mut partition, &weights, 2).unwrap();
let imbs_end: Vec<i32> = (0..C)
.map(|c| imbalance::max_imbalance(2, &partition, weights.par_iter().map(|w| w[c])))
.collect();
Expand Down