Align knapsack instance generation with academic benchmarks

This commit is contained in:
Fatigue24 2024-11-06 18:23:50 +02:00
parent cd12b6f72d
commit fea23bf221

View File

@ -77,29 +77,48 @@ impl crate::ChallengeTrait<Solution, Difficulty, 2> for Challenge {
fn generate_instance(seed: [u8; 32], difficulty: &Difficulty) -> Result<Challenge> {
let mut rng = SmallRng::from_seed(StdRng::from_seed(seed).gen());
// Set constant density for value generation
let density = 0.25;
// Generate weights w_i in the range [1, 50]
let weights: Vec<u32> = (0..difficulty.num_items)
.map(|_| rng.gen_range(1..=50))
.collect();
// Generate values v_i in the range [50, 100]
// Generate values v_i in the range [1, 100] with density probability, 0 otherwise
let values: Vec<u32> = (0..difficulty.num_items)
.map(|_| rng.gen_range(50..=100))
.map(|_| {
if rng.gen_bool(density) {
rng.gen_range(1..=100)
} else {
0
}
})
.collect();
// Generate interactive values V_ij in the range [1, 50], with V_ij == V_ji and V_ij where i==j is 0.
let mut interaction_values: Vec<Vec<i32>> =
vec![vec![0; difficulty.num_items]; difficulty.num_items];
// Generate interaction values V_ij with the following properties:
// - V_ij == V_ji (symmetric matrix)
// - V_ii == 0 (diagonal is zero)
// - Values are in range [1, 100] with density probability, 0 otherwise
let mut interaction_values: Vec<Vec<i32>> = vec![vec![0; difficulty.num_items]; difficulty.num_items];
for i in 0..difficulty.num_items {
for j in (i + 1)..difficulty.num_items {
let value = rng.gen_range(-50..=50);
let value = if rng.gen_bool(density) {
rng.gen_range(1..=100)
} else {
0
};
// Set both V_ij and V_ji due to symmetry
interaction_values[i][j] = value;
interaction_values[j][i] = value;
}
}
let max_weight: u32 = weights.iter().sum::<u32>() / 2;
// Precompute the ratio between the total value (value + sum of interactive values) and
// weight for each item. Pair the ratio with the item's weight and index
let mut item_values: Vec<(usize, f32)> = (0..difficulty.num_items)
@ -109,15 +128,16 @@ impl crate::ChallengeTrait<Solution, Difficulty, 2> for Challenge {
(i, ratio)
})
.collect();
// Sort the list of ratios in descending order
item_values.sort_unstable_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
// Step 1: Initial solution obtained by greedily selecting items based on value-weight ratio
let mut selected_items = Vec::with_capacity(difficulty.num_items);
let mut unselected_items = Vec::with_capacity(difficulty.num_items);
let mut total_weight = 0;
let mut total_value = 0;
let mut is_selected = vec![false; difficulty.num_items];
for &(item, _) in &item_values {
if total_weight + weights[item] <= max_weight {
@ -128,13 +148,14 @@ impl crate::ChallengeTrait<Solution, Difficulty, 2> for Challenge {
total_value += interaction_values[item][prev_item];
}
selected_items.push(item);
is_selected[item] = true;
} else {
unselected_items.push(item);
}
}
// Step 2: Improvement of solution with Local Search and Tabu-List
// Precompute sum of interaction values with each selected item for all items(if i==j, then interaction_values[i][j] == 0)
// Precompute sum of interaction values with each selected item for all items
let mut interaction_sum_list = vec![0; difficulty.num_items];
for x in 0..difficulty.num_items {
interaction_sum_list[x] = values[x] as i32;
@ -143,10 +164,17 @@ impl crate::ChallengeTrait<Solution, Difficulty, 2> for Challenge {
}
}
let mut min_selected_item_values = i32::MAX;
for x in 0..difficulty.num_items {
if is_selected[x] {
min_selected_item_values = min_selected_item_values.min(interaction_sum_list[x]);
}
}
// Optimized local search with tabu list
let max_iterations = 100;
let mut tabu_list = vec![0; difficulty.num_items];
for _ in 0..max_iterations {
let mut best_improvement = 0;
let mut best_swap = None;
@ -158,30 +186,27 @@ impl crate::ChallengeTrait<Solution, Difficulty, 2> for Challenge {
}
let new_item_values_sum = interaction_sum_list[new_item];
// Greedy assumption that remove_item_values_sum + interaction_values[new_item][remove_item] is higher or equal to zero
// It's not only a huge optimization, but it also produces better solutions most of the time
if new_item_values_sum < best_improvement {
if new_item_values_sum < best_improvement + min_selected_item_values {
continue;
}
// Compute minimal weight of remove_item required to put new_item
let min_weight = weights[new_item] as i32 - (max_weight as i32 - total_weight as i32);
let min_weight = weights[new_item] as i32 - (max_weight as i32 - total_weight as i32);
for j in 0..selected_items.len() {
let remove_item = selected_items[j];
if tabu_list[remove_item] > 0 {
continue;
}
// Don't check the weight if there is enough remaining capacity
if min_weight >= 0
{
if min_weight > 0 {
// Skip a remove_item if the remaining capacity after removal is insufficient to push a new_item
let removed_item_weight = weights[remove_item] as i32;
if removed_item_weight < min_weight {
continue;
}
}
let remove_item_values_sum = interaction_sum_list[remove_item];
let value_diff = new_item_values_sum - remove_item_values_sum - interaction_values[new_item][remove_item];
@ -201,12 +226,19 @@ impl crate::ChallengeTrait<Solution, Difficulty, 2> for Challenge {
selected_items.push(new_item);
unselected_items.push(remove_item);
is_selected[new_item] = true;
is_selected[remove_item] = false;
total_value += best_improvement;
total_weight = total_weight + weights[new_item] - weights[remove_item];
// Update sum of interaction values after swapping items
min_selected_item_values = i32::MAX;
for x in 0..difficulty.num_items {
interaction_sum_list[x] += interaction_values[x][new_item] - interaction_values[x][remove_item];
if is_selected[x] {
min_selected_item_values = min_selected_item_values.min(interaction_sum_list[x]);
}
}
// Update tabu list
@ -215,17 +247,17 @@ impl crate::ChallengeTrait<Solution, Difficulty, 2> for Challenge {
} else {
break; // No improvement found, terminate local search
}
// Decrease tabu counters
for t in tabu_list.iter_mut() {
*t = if *t > 0 { *t - 1 } else { 0 };
}
}
let mut min_value = calculate_total_value(&selected_items, &values, &interaction_values);
min_value = (min_value as f32 * (1.0 + difficulty.better_than_baseline as f32 / 1000.0))
.round() as u32;
Ok(Challenge {
seed,
difficulty: difficulty.clone(),