diff --git a/tig-challenges/src/knapsack.rs b/tig-challenges/src/knapsack.rs index d4e833a..06a5af3 100644 --- a/tig-challenges/src/knapsack.rs +++ b/tig-challenges/src/knapsack.rs @@ -77,29 +77,48 @@ impl crate::ChallengeTrait for Challenge { fn generate_instance(seed: [u8; 32], difficulty: &Difficulty) -> Result { 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 = (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 = (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![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![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::() / 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 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 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 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 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 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 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(),