diff --git a/tig-algorithms/src/knapsack/mod.rs b/tig-algorithms/src/knapsack/mod.rs index 33ddf59d..9396c6cd 100644 --- a/tig-algorithms/src/knapsack/mod.rs +++ b/tig-algorithms/src/knapsack/mod.rs @@ -174,7 +174,8 @@ // c003_a088 -// c003_a089 +pub mod relative_raw_ultra; +pub use relative_raw_ultra as c003_a089; // c003_a090 diff --git a/tig-algorithms/src/knapsack/relative_raw_ultra/README.md b/tig-algorithms/src/knapsack/relative_raw_ultra/README.md new file mode 100644 index 00000000..6809ebde --- /dev/null +++ b/tig-algorithms/src/knapsack/relative_raw_ultra/README.md @@ -0,0 +1,23 @@ +# TIG Code Submission + +## Submission Details + +* **Challenge Name:** knapsack +* **Algorithm Name:** relative_raw_ultra +* **Copyright:** 2025 syebastian +* **Identity of Submitter:** syebastian +* **Identity of Creator of Algorithmic Method:** null +* **Unique Algorithm Identifier (UAI):** null + +## License + +The files in this folder are under the following licenses: +* TIG Benchmarker Outbound License +* TIG Commercial License +* TIG Inbound Game License +* TIG Innovator Outbound Game License +* TIG Open Data License +* TIG THV Game License + +Copies of the licenses can be obtained at: +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses \ No newline at end of file diff --git a/tig-algorithms/src/knapsack/relative_raw_ultra/mod.rs b/tig-algorithms/src/knapsack/relative_raw_ultra/mod.rs new file mode 100644 index 00000000..9b79e1ed --- /dev/null +++ b/tig-algorithms/src/knapsack/relative_raw_ultra/mod.rs @@ -0,0 +1,444 @@ +use anyhow::Result; +use rand::{rngs::StdRng, Rng, SeedableRng}; +use serde_json::{Map, Value}; +use tig_challenges::knapsack::*; + +pub fn solve_challenge( + challenge: &Challenge, + save_solution: &dyn Fn(&Solution) -> anyhow::Result<()>, + hyperparameters: &Option>, +) -> anyhow::Result<()> { + Err(anyhow::anyhow!("This algorithm is no longer compatible.")) +} + +// Old code that is no longer compatible +#[cfg(none)] +mod dead_code { + fn compute_solution( + challenge: &SubInstance, + contribution_list: &mut [i32], + unselected_items: &mut Vec, + rng: &mut StdRng, + ) -> Result> { + let mut selected_items = Vec::new(); + let mut total_weight = 0; + let mut total_value = 0; + + let inv_weights: Vec = challenge.weights.iter().map(|&w| 1.0 / w as f32).collect(); + + const RCL_MAX: usize = 10; + + let probs: Vec = (0..RCL_MAX) + .map(|rank| 1.0 / ((rank + 1) as f32).exp()) + .collect(); + + let mut acc_probs: Vec = Vec::with_capacity(RCL_MAX); + let mut sum = 0.0; + for &prob in &probs { + sum += prob; + acc_probs.push(sum); + } + let total_prob_max = sum; + let max_item_weight = challenge.weights.iter().max().unwrap(); + + let mut item_densities: Vec<(usize, f32)> = unselected_items + .iter() + .map(|&idx| { + let ratio = contribution_list[idx] as f32 * inv_weights[idx]; + (idx, ratio) + }) + .collect(); + + let list_size = 2; + let mut top_ranks = vec![0; list_size]; + let mut top_densities = vec![f32::MIN; list_size]; + + while !item_densities.is_empty() { + let num_candidates = item_densities.len(); + if num_candidates < 2 { + break; + } + + let actual_rcl_size = num_candidates.min(RCL_MAX); + let total_prob = if actual_rcl_size == RCL_MAX { + total_prob_max + } else { + acc_probs[actual_rcl_size - 1] + }; + + let random_threshold = rng.gen_range(0.0..total_prob); + let mut selected_rank = match acc_probs[..actual_rcl_size] + .binary_search_by(|prob| prob.partial_cmp(&random_threshold).unwrap()) + { + Ok(i) | Err(i) => i, + }; + if selected_rank >= actual_rcl_size { + selected_rank = actual_rcl_size - 1; + } + let mut selected_item = 0; + if selected_rank < list_size && !selected_items.is_empty() { + selected_rank = top_ranks[selected_rank]; + selected_item = item_densities[selected_rank].0; + } else { + item_densities + .select_nth_unstable_by(selected_rank, |a, b| b.1.partial_cmp(&a.1).unwrap()); + selected_item = item_densities[selected_rank].0; + } + + selected_items.push(selected_item); + total_weight += challenge.weights[selected_item]; + total_value += contribution_list[selected_item]; + + if total_weight + max_item_weight > challenge.max_weight { + item_densities.retain(|(idx, _)| { + total_weight + challenge.weights[*idx] <= challenge.max_weight + && *idx != selected_item + }); + } else { + item_densities.swap_remove(selected_rank); + } + + unsafe { + for x in 0..challenge.num_items { + *contribution_list.get_unchecked_mut(x) += *challenge + .interaction_values + .get_unchecked(selected_item) + .get_unchecked(x); + } + + let mut first_density = f32::MIN; + let mut first_rank = 0; + let mut second_density = f32::MIN; + let mut second_rank = 0; + + for (i, density) in item_densities.iter_mut().enumerate() { + let interaction = unsafe { + *challenge + .interaction_values + .get_unchecked(selected_item) + .get_unchecked(density.0) + }; + density.1 += interaction as f32 * inv_weights[density.0]; + let current_density = density.1; + + if current_density > first_density { + second_density = first_density; + second_rank = first_rank; + first_density = current_density; + first_rank = i; + } else if current_density > second_density { + second_density = current_density; + second_rank = i; + } + } + + top_ranks[0] = first_rank; + top_ranks[1] = second_rank; + top_densities[0] = first_density; + top_densities[1] = second_density; + } + } + unselected_items.clear(); + unselected_items.extend(0..challenge.num_items); + + let mut sorted_selected = selected_items.clone(); + sorted_selected.sort_unstable_by(|a, b| b.cmp(a)); + + for &selected in &sorted_selected { + unselected_items.swap_remove(selected); + } + + unselected_items.sort_unstable_by_key(|&idx| challenge.weights[idx]); + + let local_search_iterations = 150; + let mut feasible_adds = Vec::new(); + let mut feasible_swaps = Vec::new(); + for _ in 0..local_search_iterations { + let mut improved = false; + + if total_weight < challenge.max_weight { + for (i, &cand) in unselected_items.iter().enumerate() { + let new_w = total_weight + challenge.weights[cand]; + let new_val = total_value + contribution_list[cand]; + if new_w > challenge.max_weight { + break; + } + + if new_val >= total_value { + feasible_adds.push(i); + } + } + if !feasible_adds.is_empty() { + let pick = rng.gen_range(0..feasible_adds.len()); + let add_idx = feasible_adds[pick]; + let new_item = unselected_items[add_idx]; + + unselected_items.remove(add_idx); + selected_items.push(new_item); + + total_weight += challenge.weights[new_item]; + total_value += contribution_list[new_item]; + improved = true; + + unsafe { + for x in 0..challenge.num_items { + *contribution_list.get_unchecked_mut(x) += *challenge + .interaction_values + .get_unchecked(x) + .get_unchecked(new_item); + } + } + } + feasible_adds.clear(); + } + + let free_capacity = challenge.max_weight as i32 - total_weight as i32; + for (j, &rem_item) in selected_items.iter().enumerate() { + let rem_w = challenge.weights[rem_item] as i32; + + for (i, &cand_item) in unselected_items.iter().enumerate() { + let cand_w = challenge.weights[cand_item] as i32; + if rem_w + free_capacity < cand_w { + break; + } + + let val_diff = contribution_list[cand_item] + - contribution_list[rem_item] + - challenge.interaction_values[cand_item][rem_item]; + if val_diff >= 0 { + feasible_swaps.push((i, j)); + } + } + } + + if !feasible_swaps.is_empty() { + let pick = rng.gen_range(0..feasible_swaps.len()); + let (unsel_idx, sel_idx) = feasible_swaps[pick]; + let new_item = unselected_items[unsel_idx]; + let remove_item = selected_items[sel_idx]; + + selected_items.swap_remove(sel_idx); + selected_items.push(new_item); + + let new_item_weight = challenge.weights[new_item]; + let remove_item_weight = challenge.weights[remove_item]; + + let current_pos = unsel_idx; + let mut target_pos = current_pos; + if new_item_weight != remove_item_weight { + target_pos = unselected_items + .binary_search_by(|&probe| { + challenge.weights[probe].cmp(&remove_item_weight) + }) + .unwrap_or_else(|e| e); + } + if current_pos != target_pos { + unsafe { + let ptr = unselected_items.as_mut_ptr(); + if target_pos < current_pos { + std::ptr::copy( + ptr.add(target_pos), + ptr.add(target_pos + 1), + current_pos - target_pos, + ); + } else { + target_pos = target_pos - 1; + std::ptr::copy( + ptr.add(current_pos + 1), + ptr.add(current_pos), + target_pos - current_pos, + ); + } + } + } + unselected_items[target_pos] = remove_item; + + total_value += contribution_list[new_item] + - contribution_list[remove_item] + - challenge.interaction_values[new_item][remove_item]; + total_weight = + total_weight + challenge.weights[new_item] - challenge.weights[remove_item]; + improved = true; + + unsafe { + for x in 0..challenge.num_items { + *contribution_list.get_unchecked_mut(x) += *challenge + .interaction_values + .get_unchecked(x) + .get_unchecked(new_item) + - *challenge + .interaction_values + .get_unchecked(x) + .get_unchecked(remove_item); + } + } + } + feasible_swaps.clear(); + + if !improved { + break; + } + } + + if selected_items.is_empty() { + Ok(None) + } else { + Ok(Some(( + SubSolution { + items: selected_items, + }, + total_value, + ))) + } + } + + pub fn solve_challenge(challenge: &Challenge) -> Result> { + let mut solution = Solution { + sub_solutions: Vec::with_capacity(challenge.sub_instances.len()), + }; + + for _ in 0..challenge.sub_instances.len() { + solution + .sub_solutions + .push(SubSolution { items: Vec::new() }); + } + + let mut ratio_indices: Vec<(f64, usize)> = Vec::new(); + + for (index, sub_instance) in challenge.sub_instances.iter().enumerate() { + match solve_sub_instance(sub_instance, 1)? { + Some((sub_solution, best_value)) => { + let upper_ratio = best_value as f64 + * (1.0 + 0.011 * lookup_threshold(challenge.num_items) as f64) + / sub_instance.baseline_value as f64; + + ratio_indices.push((upper_ratio, index)); + } + None => return Ok(None), + } + } + + ratio_indices.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal)); + + let mut ratio_threshold = (1.0 + challenge.difficulty.better_than_baseline as f64 / 1000.0); + let average_ratio_sqr = 16.0 * ratio_threshold * ratio_threshold; + let mut sum_of_ratios_sqr = 0.0; + let mut instance_i = 0; + + for &(upper_ratio, index) in &ratio_indices { + let ratio_threshold_sqr = + (average_ratio_sqr - sum_of_ratios_sqr) / (16.0 - instance_i as f64); + ratio_threshold = ratio_threshold_sqr.sqrt(); + + if upper_ratio < ratio_threshold { + //return Ok(None); + } + + let sub_instance = &challenge.sub_instances[index]; + + match solve_sub_instance(sub_instance, 20)? { + Some((sub_solution, best_value)) => { + let ratio = best_value as f64 / sub_instance.baseline_value as f64; + let ratio_sqr = ratio * ratio; + sum_of_ratios_sqr += ratio_sqr; + + solution.sub_solutions[index] = sub_solution; + } + None => return Ok(None), + } + + instance_i += 1; + } + + Ok(Some(solution)) + } + + fn solve_sub_instance( + challenge: &SubInstance, + num_iterations: i32, + ) -> Result> { + let mut rng = + StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap())); + + let mut best_solution: Option = None; + let mut best_value = 0; + + for _outer_iter in 0..num_iterations { + let mut unselected_items: Vec = (0..challenge.num_items).collect(); + let mut contribution_list = challenge + .values + .iter() + .map(|&v| v as i32) + .collect::>(); + + let sol_result = compute_solution( + challenge, + &mut contribution_list, + &mut unselected_items, + &mut rng, + )?; + + let (solution, value) = match sol_result { + Some(x) => x, + None => continue, + }; + + if value > best_value { + best_value = value; + best_solution = Some(SubSolution { + items: solution.items.clone(), + }); + } + } + + match best_solution { + Some(solution) => Ok(Some((solution, best_value))), + None => Ok(None), + } + } + + fn lookup_threshold(num_items: usize) -> f32 { + let points = vec![ + (100, 1.071), + (105, 1.015), + (110, 0.973), + (120, 0.882), + (125, 0.791), + (130, 0.770), + (135, 0.760), + (140, 0.749), + (145, 0.700), + (150, 0.616), + (155, 0.574), + (160, 0.532), + (165, 0.511), + (170, 0.494), + (175, 0.485), + (180, 0.476), + (190, 0.448), + (195, 0.434), + (200, 0.427), + (205, 0.420), + (210, 0.420), + (215, 0.385), + (220, 0.350), + (225, 0.347), + (230, 0.343), + (235, 0.343), + (240, 0.338), + (245, 0.334), + (250, 0.329), + ]; + + points + .iter() + .filter(|&&(x, _)| x <= num_items) + .max_by_key(|&&(x, _)| x) + .unwrap() + .1 + } +} + +pub fn help() { + println!("No help information available."); +}