diff --git a/tig-algorithms/src/vehicle_routing/mod.rs b/tig-algorithms/src/vehicle_routing/mod.rs index 930b4fb..42ddab4 100644 --- a/tig-algorithms/src/vehicle_routing/mod.rs +++ b/tig-algorithms/src/vehicle_routing/mod.rs @@ -126,7 +126,8 @@ // c002_a064 -// c002_a065 +pub mod new_enhanced_cw_low; +pub use new_enhanced_cw_low as c002_a065; // c002_a066 diff --git a/tig-algorithms/src/vehicle_routing/new_enhanced_cw_low/README.md b/tig-algorithms/src/vehicle_routing/new_enhanced_cw_low/README.md new file mode 100644 index 0000000..9f92804 --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/new_enhanced_cw_low/README.md @@ -0,0 +1,23 @@ +# TIG Code Submission + + ## Submission Details + + * **Challenge Name:** vehicle_routing + * **Submission Name:** new_enhanced_cw_low + * **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/vehicle_routing/new_enhanced_cw_low/mod.rs b/tig-algorithms/src/vehicle_routing/new_enhanced_cw_low/mod.rs new file mode 100644 index 0000000..01627c6 --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/new_enhanced_cw_low/mod.rs @@ -0,0 +1,644 @@ +use anyhow::{anyhow, Result}; +use serde_json::{Map, Value}; +use tig_challenges::vehicle_routing::*; + + +pub fn solve_challenge( + challenge: &Challenge, + save_solution: &dyn Fn(&Solution) -> Result<()>, + hyperparameters: &Option>, +) -> Result<()> { + Err(anyhow!("This algorithm is no longer compatible.")) +} + +// Old code that is no longer compatible +#[cfg(none)] +mod dead_code { + use rand::{rngs::{SmallRng, StdRng}, Rng, SeedableRng}; + use tig_challenges::vehicle_routing::*; + + + pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut solution = Solution { + sub_solutions: Vec::new(), + }; + for sub_instance in &challenge.sub_instances { + match solve_sub_instance(sub_instance)? { + Some(sub_solution) => solution.sub_solutions.push(sub_solution), + None => return Ok(None), + } + } + Ok(Some(solution)) + } + + pub fn solve_sub_instance(challenge: &SubInstance) -> anyhow::Result> { + let mut global_best_solution: Option = None; + let mut global_best_cost = std::i32::MAX; + + const OUTER_ITERATIONS: usize = 1; + let iterations_per_outer = [5000, 10000, 10000]; + let inner_iters_per_outer = [4, 1, 1]; + let num_nodes = challenge.difficulty.num_nodes; + + let mut rng = SmallRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap())); + + let max_dist: f32 = challenge.distance_matrix[0].iter().sum::() as f32; + let p = challenge.baseline_total_distance as f32 / max_dist; + if p < 0.55 { + return Ok(None) + } + + let mut promising = false; + let mut iteration_results: Vec<(Vec, i32)> = Vec::new(); + + let mut best_outer_params : Vec = Vec::new(); + + for outer_iter in 0..OUTER_ITERATIONS { + let num_iterations = iterations_per_outer[outer_iter]; + let inner_iterations = inner_iters_per_outer[outer_iter]; + + for inner_iter in 0..inner_iterations { + let mut savings = Savings::new(challenge); + savings.sort_stable(); + + let mut current_params = vec![25; num_nodes]; + + let mut current_solution = create_solution(challenge, &savings.stable_list); + let mut current_cost = calculate_solution_cost(¤t_solution, &challenge.distance_matrix); + + if current_cost <= challenge.baseline_total_distance { + return Ok(Some(current_solution)); + } + + if (current_cost as f32 * 0.95) > challenge.baseline_total_distance as f32 && !promising { + return Ok(None); + } + else { + promising = true; + } + + savings.build_supplementary_structs(challenge); + + let mut best_solution = Some(SubSolution { routes: current_solution.routes.clone() }); + let mut best_cost = current_cost; + + for i in 0..num_iterations { + let (mut neighbor_params, mut modified_indices) = generate_neighbor( + ¤t_params, + best_solution.as_ref().unwrap(), + &challenge, + &mut rng, + i, + num_iterations + ); + + if outer_iter > 0 && i == 0 { + neighbor_params = best_outer_params.clone(); + modified_indices = (0..num_nodes).collect(); + }; + + if !savings.recompute_savings(&neighbor_params, &modified_indices) { + continue; + } + + let mut neighbor_solution = create_solution(challenge, &savings.unstable_list); + postprocess_solution( + &mut neighbor_solution, + &challenge.distance_matrix, + &challenge.demands, + challenge.max_capacity, + ); + + let neighbor_cost = calculate_solution_cost(&neighbor_solution, &challenge.distance_matrix); + + let delta = neighbor_cost - current_cost; + if delta <= 0 { + current_params = neighbor_params; + current_cost = neighbor_cost; + current_solution = neighbor_solution; + savings.apply_unstable_list(); + + if current_cost < best_cost { + best_cost = current_cost; + best_solution = Some(SubSolution { + routes: current_solution.routes.clone(), + }); + } + if best_cost <= challenge.baseline_total_distance { + return Ok(best_solution); + } + } + } + + iteration_results.push(( + current_params, + best_cost + )); + + if best_cost < global_best_cost { + global_best_cost = best_cost; + global_best_solution = best_solution; + } + } + + if outer_iter < OUTER_ITERATIONS - 1 { + let (best_params, best_cost) = iteration_results.iter() + .min_by_key(|&(_, cost)| cost) + .map(|(params, cost)| (params.clone(), *cost)) + .unwrap(); + + iteration_results.clear(); + best_outer_params = best_params; + } + } + + Ok(global_best_solution) + } + + pub struct Savings { + pub stable_list: Vec<(u32, u16, u16)>, + + raw_savings: Vec>, + pub pair_map: Vec>, + pub unstable_list: Vec<(u32, u16, u16)>, + } + + impl Savings { + pub fn new(challenge: &SubInstance) -> Self { + let stable_list = Self::create_initial_savings_list(challenge); + + Self { + stable_list, + raw_savings: Vec::new(), + pair_map: Vec::new(), + unstable_list: Vec::new(), + } + } + + fn create_initial_savings_list(challenge: &SubInstance) -> Vec<(u32, u16, u16)> { + let num_nodes = challenge.difficulty.num_nodes; + + let max_distance = challenge + .distance_matrix + .iter() + .flat_map(|row| row.iter()) + .cloned() + .max() + .unwrap_or(0); + let threshold = max_distance / 3; + + let capacity = ((num_nodes - 1) * (num_nodes - 2)) / 2; + let mut savings = Vec::with_capacity(capacity); + + for i in 1..num_nodes { + for j in (i + 1)..num_nodes { + let dist_ij = challenge.distance_matrix[i][j]; + if dist_ij <= threshold { + let saving = challenge.distance_matrix[0][i] + challenge.distance_matrix[j][0] - dist_ij; + if saving > 0 { + savings.push((!(saving as u32), i as u16, j as u16)); + } + } + } + } + savings + } + + pub fn build_supplementary_structs(&mut self, challenge: &SubInstance) { + let num_nodes = challenge.difficulty.num_nodes; + let mask_size = (num_nodes + 63) / 64; // Calculate number of u64 chunks needed + + // Initialize pair_map as a bitmask with 64-bit chunks + self.pair_map = vec![vec![0u64; mask_size]; num_nodes]; + self.raw_savings = vec![vec![0; num_nodes]; num_nodes]; + + for &(_, i16, j16) in &self.stable_list { + let (i, j) = (i16 as usize, j16 as usize); + let saving = challenge.distance_matrix[0][i] + + challenge.distance_matrix[j][0] + - challenge.distance_matrix[i][j]; + + self.raw_savings[i][j] = saving as u32; + self.raw_savings[j][i] = saving as u32; + + let (idx, bit) = (j / 64, j % 64); + self.pair_map[i][idx] |= 1u64 << bit; + + let (idx, bit) = (i / 64, i % 64); + self.pair_map[j][idx] |= 1u64 << bit; + } + + self.unstable_list = Vec::with_capacity(self.stable_list.len()); + self.unstable_list.resize(self.stable_list.len(), (0, 0, 0)); + } + + fn radix_sort(savings_list: &mut [(u32, u16, u16)]) { + unsafe { + // 1. Use usize for counts to prevent overflow with large arrays + let mut counts_low = [0u32; 512]; + let mut counts_high = [0u32; 512]; + let mut buf = Vec::with_capacity(savings_list.len()); + buf.set_len(savings_list.len()); + + let savings_ptr : *mut (u32, u16, u16) = savings_list.as_mut_ptr(); + let buf_ptr : *mut (u32, u16, u16) = buf.as_mut_ptr(); + + let mut ptr = savings_ptr; + for _ in 0..savings_list.len() { + let bits = (*ptr).0; + counts_low[(bits & 511) as usize] += 1; + counts_high[((bits >> 9) & 511) as usize] += 1; + ptr = ptr.add(1); + } + + let mut total_low = 0; + let mut total_high = 0; + for i in 0..512 { + let cl = counts_low[i]; + let ch = counts_high[i]; + counts_low[i] = total_low; + counts_high[i] = total_high; + total_low += cl; + total_high += ch; + } + + let mut src = savings_ptr; + let mut dst = buf_ptr; + for _ in 0..savings_list.len() { + let item = *src; + let byte = (item.0 & 511) as usize; + let pos = counts_low[byte] as usize; + *dst.add(pos) = item; + counts_low[byte] += 1; + src = src.add(1); + } + + let mut src = buf_ptr; + let mut dst = savings_ptr; + for _ in 0..savings_list.len() { + let item = *src; + let byte = ((item.0 >> 9) & 511) as usize; + let pos = counts_high[byte] as usize; + *dst.add(pos) = item; + counts_high[byte] += 1; + src = src.add(1); + } + } + } + + pub fn recompute_savings(&mut self, params: &[u32], modified_indices: &[usize]) -> bool { + let num_nodes = params.len(); + let mut reduced_savings = Vec::with_capacity(modified_indices.len() * num_nodes / 10); + let mut modified = vec![false; num_nodes]; + + let mut mask_len = (num_nodes + 63) / 64; + let mut visited = vec![0u64; mask_len]; + + unsafe { + for &i in modified_indices { + for k in 0..mask_len { + let chunk = *self.pair_map.get_unchecked(i).get_unchecked(k); + let mut unvisited_pairs_mask = chunk & !visited[k]; + + while unvisited_pairs_mask != 0 { + let bit_pos = unvisited_pairs_mask.trailing_zeros() as usize; + let j = bit_pos + 64 * k; + + let base_saving = *self.raw_savings.get_unchecked(i).get_unchecked(j); + let new_score = (*params.get_unchecked(i) + *params.get_unchecked(j)) * base_saving; + reduced_savings.push((!new_score, i as u16, j as u16)); + + unvisited_pairs_mask &= unvisited_pairs_mask - 1; + } + } + let (idx, bit) = (i / 64, i % 64); + *visited.get_unchecked_mut(idx) |= 1 << bit; + modified[i] = true; + } + } + + if reduced_savings.len() == 0 { + return false; + } + Self::radix_sort(&mut reduced_savings); + + let mut stable_idx = 0; + let mut reduced_idx = 0; + let mut k = 0; + + while stable_idx < self.stable_list.len() + && (modified[self.stable_list[stable_idx].1 as usize] + || modified[self.stable_list[stable_idx].2 as usize]) { + stable_idx += 1; + } + + while stable_idx < self.stable_list.len() && reduced_idx < reduced_savings.len() { + let stable_entry = self.stable_list[stable_idx]; + let reduced_entry = reduced_savings[reduced_idx]; + + if stable_entry.0 < reduced_entry.0 { + self.unstable_list[k] = stable_entry; + + stable_idx += 1; + while stable_idx < self.stable_list.len() + && (modified[self.stable_list[stable_idx].1 as usize] + || modified[self.stable_list[stable_idx].2 as usize]) { + stable_idx += 1; + } + } else { + self.unstable_list[k] = reduced_entry; + reduced_idx += 1; + } + k += 1; + } + + while stable_idx < self.stable_list.len() { + let entry = self.stable_list[stable_idx]; + + if !modified[entry.1 as usize] && !modified[entry.2 as usize] { + self.unstable_list[k] = entry; + k += 1; + } + stable_idx += 1; + } + + while reduced_idx < reduced_savings.len() { + self.unstable_list[k] = reduced_savings[reduced_idx]; + k += 1; + reduced_idx += 1; + } + return true; + } + + pub fn apply_unstable_list(&mut self) { + std::mem::swap(&mut self.stable_list, &mut self.unstable_list); + } + + pub fn sort_stable(&mut self) { + Self::radix_sort(&mut self.stable_list); + } + } + + fn generate_neighbor( + current: &[u32], + best_solution: &SubSolution, + challenge : &SubInstance, + rng: &mut R, + iteration: usize, + max_iterations: usize, + ) -> (Vec, Vec) { + let progress = iteration as f32 / max_iterations as f32; + let base_prob = 0.5 * (-5.0 * progress).exp() + 0.04; + let max_steps = 2; + + let mut result = current.to_vec(); + let mut modified_indices = Vec::with_capacity(challenge.difficulty.num_nodes / 2); + + while modified_indices.is_empty() { + for (i, ¶m) in current.iter().enumerate() { + if rng.gen_bool(base_prob as f64) { + let steps = rng.gen_range(1..=max_steps); + + let sign = if rng.gen_bool(0.5) { 1 } else { -1 }; + result[i] = (param as i32 + sign * steps).clamp(25, 50) as u32; + modified_indices.push(i); + } + } + } + + let mut pairs = Vec::with_capacity(3 * challenge.difficulty.num_nodes); + for route in &best_solution.routes { + for i in 1..route.len()-1 { + for j in i+1..route.len()-1 { + pairs.push((route[i], route[j])); + } + } + } + + if pairs.len() > 0{ + let pair_idx = rng.gen_range(0..pairs.len()); + let idx1 = pairs[pair_idx].0; + let idx2 = pairs[pair_idx].1; + + result.swap(idx1, idx2); + + if !modified_indices.contains(&idx1) { + modified_indices.push(idx1); + } + if !modified_indices.contains(&idx2) { + modified_indices.push(idx2); + } + } + + (result, modified_indices) + } + + #[inline] + fn calculate_solution_cost(solution: &SubSolution, distance_matrix: &Vec>) -> i32 { + solution + .routes + .iter() + .map(|route| { + route.windows(2).map(|pair| distance_matrix[pair[0]][pair[1]]).sum::() + }) + .sum() + } + + #[inline(never)] + fn create_solution( + challenge: &SubInstance, + savings_list: &[(u32, u16, u16)] + ) -> SubSolution { + let num_nodes = challenge.difficulty.num_nodes; + let demands = &challenge.demands; + let max_capacity = challenge.max_capacity; + + let mut node_links: Vec<[Option; 2]> = vec![[None, None]; num_nodes]; + let mut route : Vec<(usize, usize)> = (0..num_nodes) + .map(|i| (i, i)) + .collect(); + + let mut route_demands = demands.clone(); + + for &(_, i16, j16) in savings_list { + let (i, j) = (i16 as usize, j16 as usize); + + let route_demands_left = route_demands[i]; + let route_demands_right = route_demands[j]; + if route_demands_left + route_demands_right > max_capacity { + continue; + } + + let route_i_start = route[i].0; + let route_j_start = route[j].0; + let route_i_end = route[i].1; + let route_j_end = route[j].1; + if route_i_start == route_j_start || route_i_start == route_j_end + || route_i_end == route_j_start || route_i_end == route_j_end { + continue; + } + + let node_i_left = node_links[i][0]; + let node_i_right = node_links[i][1]; + + let node_j_left = node_links[j][0]; + let node_j_right = node_links[j][1]; + + let mut dir_i = usize::from(node_i_left.is_some()); + let mut dir_j = usize::from(node_j_left.is_some()); + + node_links[i][dir_i] = Some(j); + node_links[j][dir_j] = Some(i); + + if node_i_left.is_some() || node_i_right.is_some() { + route_demands[i] = max_capacity + 1; + } + if node_j_left.is_some() || node_j_right.is_some() { + route_demands[j] = max_capacity + 1; + } + + let opposite_i = if route_i_start == i { + route_i_end + } else { + route_i_start + }; + + let opposite_j = if route_j_start == j { + route_j_end + } else { + route_j_start + }; + + let new_route = (opposite_i, opposite_j); + route[opposite_i] = new_route; + route[opposite_j] = new_route; + + let combined_demand = route_demands_left + route_demands_right; + route_demands[opposite_i] = combined_demand; + route_demands[opposite_j] = combined_demand; + } + + let final_routes = extract_routes( + num_nodes, + &route_demands, + max_capacity, + &route, + &node_links + ); + + SubSolution { routes: final_routes } + } + + #[inline(never)] + fn extract_routes( + num_nodes: usize, + route_demands: &[i32], + max_capacity: i32, + route: &[(usize, usize)], + node_links: &[[Option; 2]], + ) -> Vec> { + let mut visited = vec![false; num_nodes]; + + let mut all_nodes = Vec::with_capacity(3 * num_nodes); + let mut final_routes = Vec::with_capacity(num_nodes / 2); + + for i in 1..num_nodes { + if route_demands[i] > max_capacity || route[i].0 != i { + continue; + } + let route_start_idx = all_nodes.len(); + + let mut route_iter = i; + let route_end = route[i].1; + + all_nodes.push(0); + all_nodes.push(route_iter); + visited[route_iter] = true; + + while route_iter != route_end { + let links = node_links[route_iter]; + let next = match links { + [Some(left), Some(right)] => { + if !visited[left] { left } else { right } + }, + [Some(next), None] | [None, Some(next)] => next, + _ => break, + }; + route_iter = next; + all_nodes.push(route_iter); + visited[route_iter] = true; + } + all_nodes.push(0); + + let route_len = all_nodes.len() - route_start_idx; + final_routes.push(all_nodes[route_start_idx..all_nodes.len()].to_vec()); + } + + final_routes + } + + + #[inline(never)] + pub fn postprocess_solution( + solution: &mut SubSolution, + distance_matrix: &Vec>, + _demands: &Vec, + _max_capacity: i32, + ) { + for route in solution.routes.iter_mut() { + unsafe { two_opt_best_unsafe(route, distance_matrix) }; + } + } + + #[inline] + unsafe fn two_opt_best_unsafe(route: &mut Vec, distance_matrix: &Vec>) -> bool { + let n = route.len(); + if n < 4 { + return false; + } + + let mut any_improvement = false; + let route_slice = route.as_mut_slice(); + + loop { + let mut improved = false; + + for i in 1..(n - 2) { + let mut best_gain = 0; + let mut best_j = 0; + + let i_range = i..(n - 1); + for j in i_range.skip(1) { + let ri_m1 = *route_slice.get_unchecked(i - 1); + let ri = *route_slice.get_unchecked(i); + let rj = *route_slice.get_unchecked(j); + let rj_p1 = *route_slice.get_unchecked(j + 1); + + let current = distance_matrix.get_unchecked(ri_m1).get_unchecked(ri); + let candidate = distance_matrix.get_unchecked(rj).get_unchecked(rj_p1); + let new_connection = distance_matrix.get_unchecked(ri_m1).get_unchecked(rj); + let broken_connection = distance_matrix.get_unchecked(ri).get_unchecked(rj_p1); + + let gain = current + candidate - new_connection - broken_connection; + + if gain > best_gain { + best_gain = gain; + best_j = j; + } + } + + if best_gain > 0 { + route_slice[i..=best_j].reverse(); + improved = true; + any_improvement = true; + } + } + + if !improved { + break; + } + } + + any_improvement + } +} \ No newline at end of file