Skip to content

Commit f9a1e66

Browse files
committed
vn/first: multi-criteria support
1 parent 7e38e90 commit f9a1e66

File tree

1 file changed

+34
-134
lines changed

1 file changed

+34
-134
lines changed

src/algorithms/vn/first.rs

Lines changed: 34 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
use crate::Error;
22
use itertools::Itertools as _;
3-
use num::FromPrimitive;
4-
use num::One;
5-
use num::Zero;
63
use rayon::iter::IntoParallelRefIterator as _;
74
use rayon::iter::ParallelIterator as _;
85
use std::iter::Sum;
96
use std::ops::AddAssign;
107
use std::ops::Sub;
11-
use std::ops::SubAssign;
128

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

42-
let mut i = weights.len();
38+
let mut i = 0;
4339
let mut i_last = 0;
4440
let mut algo_iterations = 0;
45-
while i != i_last {
46-
i = (i + 1) % weights.len();
47-
41+
loop {
4842
// loop through the weights.
4943
let p = partition[i];
5044

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

@@ -66,135 +64,29 @@ where
6664
let (new_min_load, new_max_load) =
6765
part_loads.iter().cloned().minmax().into_option().unwrap();
6866
let new_imbalance = new_max_load.clone() - new_min_load.clone();
69-
if imbalance < new_imbalance {
67+
if new_imbalance <= imbalance {
68+
// The move decreases the partition imbalance.
69+
imbalance = new_imbalance;
70+
max_load = new_max_load;
71+
partition[i] = q;
72+
i_last = i;
73+
} else {
7074
// The move does not decrease the partition imbalance.
7175
part_loads[p] += weights[i].clone();
7276
part_loads[q] = part_loads[p].clone() - weights[i].clone();
7377
continue;
7478
}
75-
imbalance = new_imbalance;
76-
max_load = new_max_load;
77-
partition[i] = q;
78-
i_last = i;
79-
}
80-
81-
algo_iterations += 1;
82-
}
83-
84-
Ok(algo_iterations)
85-
}
86-
87-
#[allow(dead_code)]
88-
pub fn vn_first<const C: usize, T>(
89-
partition: &mut [usize],
90-
criteria: &[[T; C]],
91-
num_parts: usize,
92-
) -> usize
93-
where
94-
T: AddAssign + SubAssign + Sub<Output = T> + Sum,
95-
T: Zero + One + FromPrimitive,
96-
T: Copy + Ord,
97-
{
98-
if num_parts < 2 || criteria.is_empty() || C == 0 {
99-
return 0;
100-
}
101-
102-
assert_eq!(criteria.len(), partition.len());
103-
104-
let mut part_loads_per_criterion = {
105-
let mut loads = vec![[T::zero(); C]; num_parts];
106-
for (w, weight) in criteria.iter().enumerate() {
107-
for (part_load, criterion) in loads[partition[w]].iter_mut().zip(weight) {
108-
*part_load += *criterion;
109-
}
110-
}
111-
loads
112-
};
113-
let total_weight_per_criterion = {
114-
// TODO replace with .collect() once [_; C] implements FromIterator.
115-
let mut ws = [T::zero(); C];
116-
for c in 0..C {
117-
ws[c] = part_loads_per_criterion[c].iter().cloned().sum();
118-
}
119-
ws
120-
};
121-
if total_weight_per_criterion.contains(&T::zero()) {
122-
return 0;
123-
}
124-
125-
let min_max_loads = |part_loads_per_criterion: &Vec<[T; C]>| -> [(T, T); C] {
126-
// TODO replace with .collect() once [_; C] implements FromIterator.
127-
let mut imbs = [(T::zero(), T::zero()); C];
128-
for c in 0..C {
129-
imbs[c] = part_loads_per_criterion[c]
130-
.iter()
131-
.cloned()
132-
.minmax()
133-
.into_option()
134-
.unwrap();
135-
}
136-
imbs
137-
};
138-
139-
let (global_min_load, mut global_max_load) = *min_max_loads(&part_loads_per_criterion)
140-
.iter()
141-
.max_by_key(|(min_load, max_load)| *max_load - *min_load)
142-
.unwrap();
143-
let mut imbalance = global_max_load - global_min_load;
144-
145-
let mut i = 0;
146-
let mut i_last = 0;
147-
let mut algo_iterations = 0;
148-
while i != i_last {
149-
i = (i + 1) % criteria.len();
150-
151-
// loop through the weights.
152-
let p = partition[i];
153-
154-
if part_loads_per_criterion[p]
155-
.iter()
156-
.all(|criterion_load| *criterion_load < global_max_load)
157-
{
158-
// weight #i is not in the heaviest partition, and thus the move
159-
// will not reduce the max imbalance.
160-
continue;
16179
}
16280

163-
for q in 0..num_parts {
164-
// loop through the parts.
165-
if p == q {
166-
// weight #i is already in partition #q.
167-
continue;
168-
}
169-
170-
for c in 0..C {
171-
part_loads_per_criterion[p][c] -= criteria[i][c];
172-
part_loads_per_criterion[q][c] += criteria[i][c];
173-
}
174-
let (new_global_min_load, new_global_max_load) =
175-
*min_max_loads(&part_loads_per_criterion)
176-
.iter()
177-
.max_by_key(|(min_load, max_load)| *max_load - *min_load)
178-
.unwrap();
179-
let new_imbalance = new_global_max_load - new_global_min_load;
180-
if imbalance < new_imbalance {
181-
// The move does not decrease the partition imbalance.
182-
for c in 0..C {
183-
part_loads_per_criterion[p][c] += criteria[i][c];
184-
part_loads_per_criterion[q][c] -= criteria[i][c];
185-
}
186-
continue;
187-
}
188-
imbalance = new_imbalance;
189-
global_max_load = new_global_max_load;
190-
partition[i] = q;
191-
i_last = i;
81+
i = (i + 1) % weights.len();
82+
if i == i_last {
83+
break;
19284
}
19385

19486
algo_iterations += 1;
19587
}
19688

197-
algo_iterations
89+
Ok(algo_iterations)
19890
}
19991

20092
/// Trait alias for values accepted as weights by [VnFirst].
@@ -306,18 +198,26 @@ mod tests {
306198
#[test]
307199
fn small() {
308200
const W: [[i32; 2]; 6] = [[1, 2], [2, 4], [3, 6], [8, 4], [10, 5], [12, 6]];
309-
let mut part = [0; W.len()];
201+
let w: Vec<_> = W
202+
.iter()
203+
.map(|w| nalgebra::SVector::<i32, 2>::from(*w))
204+
.collect();
205+
let mut partition = [0; W.len()];
310206

311-
vn_first(&mut part, &W, 1);
207+
vn_first_mono(&mut partition, &w, 2).unwrap();
208+
println!("partition: {partition:?}");
312209
let imbs_ini: Vec<i32> = (0..W[0].len())
313-
.map(|c| imbalance::max_imbalance(2, &part, W.par_iter().map(|w| w[c])))
210+
.map(|c| imbalance::max_imbalance(2, &partition, W.par_iter().map(|w| w[c])))
211+
.collect();
212+
vn_first_mono(&mut partition, &w, 2).unwrap();
213+
println!("partition: {partition:?}");
214+
let imbs_end: Vec<i32> = (0..W[0].len())
215+
.map(|c| imbalance::max_imbalance(2, &partition, W.par_iter().map(|w| w[c])))
314216
.collect();
315-
vn_first(&mut part, &W, 2);
316-
let imbs_end =
317-
(0..W[0].len()).map(|c| imbalance::max_imbalance(2, &part, W.par_iter().map(|w| w[c])));
217+
println!("imbalances: {imbs_end:?} < {imbs_ini:?}");
318218
for (imb_ini, imb_end) in imbs_ini.into_iter().zip(imbs_end) {
219+
println!("imbalance: {imb_end} < {imb_ini}");
319220
assert!(imb_end <= imb_ini);
320-
println!("imbalance : {} < {}", imb_end, imb_ini);
321221
}
322222
}
323223

@@ -329,7 +229,7 @@ mod tests {
329229
(weights, mut partition) in
330230
(2..200_usize).prop_flat_map(|num_weights| {
331231
let weights = prop::collection::vec(
332-
(1..1000_i32, 1..1000_i32).prop_map(|(a, b)| [a, b]),
232+
(1..1000_i32, 1..1000_i32).prop_map(|(a, b)| nalgebra::SVector::<i32, 2>::new(a, b)),
333233
num_weights
334234
);
335235
let partition = prop::collection::vec(0..1_usize, num_weights);
@@ -339,7 +239,7 @@ mod tests {
339239
let imbs_ini: Vec<i32> = (0..C)
340240
.map(|c| imbalance::max_imbalance(2, &partition, weights.par_iter().map(|w| w[c])))
341241
.collect();
342-
vn_first(&mut partition, &weights, 2);
242+
vn_first_mono(&mut partition, &weights, 2).unwrap();
343243
let imbs_end: Vec<i32> = (0..C)
344244
.map(|c| imbalance::max_imbalance(2, &partition, weights.par_iter().map(|w| w[c])))
345245
.collect();

0 commit comments

Comments
 (0)