From eeec85ae5bb7b57646cb878f4c4e59ac5fb66ccf Mon Sep 17 00:00:00 2001 From: Fatigue24 Date: Sun, 3 Nov 2024 02:29:14 +0200 Subject: [PATCH] Improving the baseline algorithm for QKP --- tig-challenges/src/knapsack.rs | 126 +++++++++++++++++++++++++++++---- 1 file changed, 111 insertions(+), 15 deletions(-) diff --git a/tig-challenges/src/knapsack.rs b/tig-challenges/src/knapsack.rs index e65e238..a354c7e 100644 --- a/tig-challenges/src/knapsack.rs +++ b/tig-challenges/src/knapsack.rs @@ -102,31 +102,127 @@ impl crate::ChallengeTrait for Challenge { // 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 value_weight_ratios: Vec<(usize, f32, u32)> = (0..difficulty.num_items) + let mut item_values: Vec<(usize, f32)> = (0..difficulty.num_items) .map(|i| { let total_value = values[i] as i32 + interaction_values[i].iter().sum::(); - let weight = weights[i]; - let ratio = total_value as f32 / weight as f32; - (i, ratio, weight) + let ratio = total_value as f32 / weights[i] as f32; + (i, ratio) }) .collect(); - // Sort the list of tuples by value-to-weight ratio in descending order - value_weight_ratios.sort_unstable_by(|&(_, ratio_a, _), &(_, ratio_b, _)| { - ratio_b.partial_cmp(&ratio_a).unwrap() - }); + // 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 selected_indices = Vec::new(); - for &(i, _, weight) in &value_weight_ratios { - if total_weight + weight <= max_weight { - selected_indices.push(i); - total_weight += weight; + let mut total_value = 0; + + for &(item, _) in &item_values { + if total_weight + weights[item] <= max_weight { + total_weight += weights[item]; + total_value += values[item] as i32; + + for &prev_item in &selected_items { + total_value += interaction_values[item][prev_item]; + } + selected_items.push(item); + } else { + unselected_items.push(item); } } - selected_indices.sort_unstable(); + + // 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) + 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; + for &item in &selected_items { + interaction_sum_list[x] += interaction_values[x][item]; + } + } + + // Optimized local search with tabu list + let max_iterations = 100; + let mut tabu_list = vec![0; difficulty.num_items]; - let mut min_value = calculate_total_value(&selected_indices, &values, &interaction_values); + for _ in 0..max_iterations { + let mut best_improvement = 0; + let mut best_swap = None; + + for i in 0..unselected_items.len() { + let new_item = unselected_items[i]; + if tabu_list[new_item] > 0 { + continue; + } + + 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 { + 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); + 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 + { + // 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]; + + if value_diff > best_improvement { + best_improvement = value_diff; + best_swap = Some((i, j)); + } + } + } + + if let Some((unselected_index, selected_index)) = best_swap { + let new_item = unselected_items[unselected_index]; + let remove_item = selected_items[selected_index]; + + selected_items.swap_remove(selected_index); + unselected_items.swap_remove(unselected_index); + selected_items.push(new_item); + unselected_items.push(remove_item); + + total_value += best_improvement; + total_weight = total_weight + weights[new_item] - weights[remove_item]; + + // Update sum of interaction values after swapping items + for x in 0..difficulty.num_items { + interaction_sum_list[x] += interaction_values[x][new_item] - interaction_values[x][remove_item]; + } + + // Update tabu list + tabu_list[new_item] = 3; + tabu_list[remove_item] = 3; + } 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;