mirror of
https://github.com/tig-foundation/tig-monorepo.git
synced 2026-02-21 10:27:49 +08:00
Submitted knapsack/relative_raw_ultra
This commit is contained in:
parent
bdc6ed6794
commit
35fc5e53c5
@ -174,7 +174,8 @@
|
||||
|
||||
// c003_a088
|
||||
|
||||
// c003_a089
|
||||
pub mod relative_raw_ultra;
|
||||
pub use relative_raw_ultra as c003_a089;
|
||||
|
||||
// c003_a090
|
||||
|
||||
|
||||
23
tig-algorithms/src/knapsack/relative_raw_ultra/README.md
Normal file
23
tig-algorithms/src/knapsack/relative_raw_ultra/README.md
Normal file
@ -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
|
||||
444
tig-algorithms/src/knapsack/relative_raw_ultra/mod.rs
Normal file
444
tig-algorithms/src/knapsack/relative_raw_ultra/mod.rs
Normal file
@ -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<Map<String, Value>>,
|
||||
) -> 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<usize>,
|
||||
rng: &mut StdRng,
|
||||
) -> Result<Option<(SubSolution, i32)>> {
|
||||
let mut selected_items = Vec::new();
|
||||
let mut total_weight = 0;
|
||||
let mut total_value = 0;
|
||||
|
||||
let inv_weights: Vec<f32> = challenge.weights.iter().map(|&w| 1.0 / w as f32).collect();
|
||||
|
||||
const RCL_MAX: usize = 10;
|
||||
|
||||
let probs: Vec<f32> = (0..RCL_MAX)
|
||||
.map(|rank| 1.0 / ((rank + 1) as f32).exp())
|
||||
.collect();
|
||||
|
||||
let mut acc_probs: Vec<f32> = 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<Option<Solution>> {
|
||||
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<Option<(SubSolution, i32)>> {
|
||||
let mut rng =
|
||||
StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()));
|
||||
|
||||
let mut best_solution: Option<SubSolution> = None;
|
||||
let mut best_value = 0;
|
||||
|
||||
for _outer_iter in 0..num_iterations {
|
||||
let mut unselected_items: Vec<usize> = (0..challenge.num_items).collect();
|
||||
let mut contribution_list = challenge
|
||||
.values
|
||||
.iter()
|
||||
.map(|&v| v as i32)
|
||||
.collect::<Vec<i32>>();
|
||||
|
||||
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.");
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user