diff --git a/tig-algorithms/lib/hypergraph/freud_opt.tar.gz b/tig-algorithms/lib/hypergraph/freud_opt.tar.gz new file mode 100644 index 00000000..6dc2ab62 Binary files /dev/null and b/tig-algorithms/lib/hypergraph/freud_opt.tar.gz differ diff --git a/tig-algorithms/lib/vehicle_routing/fast_lane_v2.tar.gz b/tig-algorithms/lib/vehicle_routing/fast_lane_v2.tar.gz new file mode 100644 index 00000000..e2b7798e Binary files /dev/null and b/tig-algorithms/lib/vehicle_routing/fast_lane_v2.tar.gz differ diff --git a/tig-algorithms/src/hypergraph/freud_opt/README.md b/tig-algorithms/src/hypergraph/freud_opt/README.md new file mode 100644 index 00000000..8cbcdb7d --- /dev/null +++ b/tig-algorithms/src/hypergraph/freud_opt/README.md @@ -0,0 +1,23 @@ +# TIG Code Submission + +## Submission Details + +* **Challenge Name:** hypergraph +* **Algorithm Name:** freud_opt +* **Copyright:** 2025 ChervovNikita +* **Identity of Submitter:** ChervovNikita +* **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/hypergraph/freud_opt/freud_opt.cu b/tig-algorithms/src/hypergraph/freud_opt/freud_opt.cu new file mode 100644 index 00000000..6c162648 --- /dev/null +++ b/tig-algorithms/src/hypergraph/freud_opt/freud_opt.cu @@ -0,0 +1,483 @@ +#include +#include + +extern "C" __global__ void hyperedge_clustering( + const int num_hyperedges, + const int num_clusters, + const int *hyperedge_offsets, + int *hyperedge_clusters +) { + int hedge = blockIdx.x * blockDim.x + threadIdx.x; + + if (hedge < num_hyperedges) { + int start = hyperedge_offsets[hedge]; + int end = hyperedge_offsets[hedge + 1]; + int hedge_size = end - start; + + int quarter_clusters = num_clusters >> 2; + int cluster_mask = quarter_clusters - 1; + + int bucket = (hedge_size > 8) ? 3 : + (hedge_size > 4) ? 2 : + (hedge_size > 2) ? 1 : 0; + int cluster = bucket * quarter_clusters + (hedge & cluster_mask); + + hyperedge_clusters[hedge] = cluster; + } +} + +extern "C" __global__ void compute_node_preferences( + const int num_nodes, + const int num_parts, + const int num_hedge_clusters, + const int *node_hyperedges, + const int *node_offsets, + const int *hyperedge_clusters, + const int *hyperedge_offsets, + int *pref_parts, + int *pref_priorities +) { + int node = blockIdx.x * blockDim.x + threadIdx.x; + + if (node < num_nodes) { + int start = node_offsets[node]; + int end = node_offsets[node + 1]; + int node_degree = end - start; + + int cluster_votes[64]; + int max_clusters = min(num_hedge_clusters, 64); + for (int i = 0; i < max_clusters; i++) { + cluster_votes[i] = 0; + } + + int max_votes = 0; + int best_cluster = 0; + + for (int j = start; j < end; j++) { + int hyperedge = node_hyperedges[j]; + int cluster = hyperedge_clusters[hyperedge]; + + if (cluster >= 0 && cluster < max_clusters) { + int hedge_start = hyperedge_offsets[hyperedge]; + int hedge_end = hyperedge_offsets[hyperedge + 1]; + int hedge_size = hedge_end - hedge_start; + int weight = (hedge_size <= 2) ? 6 : + (hedge_size <= 4) ? 4 : + (hedge_size <= 8) ? 2 : 1; + + cluster_votes[cluster] += weight; + + if (cluster_votes[cluster] > max_votes || + (cluster_votes[cluster] == max_votes && cluster < best_cluster)) { + max_votes = cluster_votes[cluster]; + best_cluster = cluster; + } + } + } + + int base_part = (num_parts > 0) ? (best_cluster % num_parts) : 0; + int target_partition = base_part; + + pref_parts[node] = target_partition; + int degree_weight = node_degree > 255 ? 255 : node_degree; + pref_priorities[node] = (max_votes << 16) + (degree_weight << 8) + (num_parts - (node % num_parts)); + } +} + +extern "C" __global__ void execute_node_assignments( + const int num_nodes, + const int num_parts, + const int max_part_size, + const int *sorted_nodes, + const int *sorted_parts, + int *partition, + int *nodes_in_part +) { + if (blockIdx.x == 0 && threadIdx.x == 0) { + for (int i = 0; i < num_nodes; i++) { + int node = sorted_nodes[i]; + int preferred_part = sorted_parts[i]; + + if (node >= 0 && node < num_nodes && preferred_part >= 0 && preferred_part < num_parts) { + bool assigned = false; + for (int attempt = 0; attempt < num_parts; attempt++) { + int try_part = (preferred_part + attempt) % num_parts; + if (nodes_in_part[try_part] < max_part_size) { + partition[node] = try_part; + nodes_in_part[try_part]++; + assigned = true; + break; + } + } + + if (!assigned) { + int fallback_part = node % num_parts; + partition[node] = fallback_part; + nodes_in_part[fallback_part]++; + } + } + } + } +} + +extern "C" __global__ void precompute_edge_flags( + const int num_hyperedges, + const int num_nodes, + const int *hyperedge_nodes, + const int *hyperedge_offsets, + const int *partition, + unsigned long long *edge_flags_all, + unsigned long long *edge_flags_double +) { + int hedge = blockIdx.x * blockDim.x + threadIdx.x; + + if (hedge < num_hyperedges) { + int start = hyperedge_offsets[hedge]; + int end = hyperedge_offsets[hedge + 1]; + + unsigned long long flags_all = 0; + unsigned long long flags_double = 0; + + for (int k = start; k < end; k++) { + int node = hyperedge_nodes[k]; + if (node >= 0 && node < num_nodes) { + int part = partition[node]; + if (part >= 0 && part < 64) { + unsigned long long bit = 1ULL << part; + flags_double |= (flags_all & bit); + flags_all |= bit; + } + } + } + + edge_flags_all[hedge] = flags_all; + edge_flags_double[hedge] = flags_double; + } +} + +extern "C" __global__ void compute_refinement_moves( + const int num_nodes, + const int num_parts, + const int max_part_size, + const int *node_hyperedges, + const int *node_offsets, + const int *partition, + const int *nodes_in_part, + const unsigned long long *edge_flags_all, + const unsigned long long *edge_flags_double, + int *move_parts, + int *move_priorities, + int *num_valid_moves, + unsigned long long *global_edge_flags +) { + int node = blockIdx.x * blockDim.x + threadIdx.x; + + if (node < num_nodes) { + move_parts[node] = partition[node]; + move_priorities[node] = 0; + + int current_part = partition[node]; + if (current_part < 0 || current_part >= num_parts || nodes_in_part[current_part] <= 1) return; + + int start = node_offsets[node]; + int end = node_offsets[node + 1]; + int node_degree = end - start; + int degree_weight = node_degree > 255 ? 255 : node_degree; + int used_degree = node_degree > 1024 ? 1024 : node_degree; + + unsigned long long *edge_flags = &global_edge_flags[node * 1024]; + unsigned long long cur_node_bit = 1ULL << current_part; + + for (int j = 0; j < used_degree; j++) { + int rel = (int)(((long long)j * node_degree) / used_degree); + int hyperedge = node_hyperedges[start + rel]; + + unsigned long long flags_all = edge_flags_all[hyperedge]; + unsigned long long flags_double = edge_flags_double[hyperedge]; + + edge_flags[j] = (flags_all & ~cur_node_bit) | (flags_double & cur_node_bit); + } + + int original_cost = 0; + for (int j = 0; j < used_degree; j++) { + int lambda = __popcll(edge_flags[j] | cur_node_bit); + if (lambda > 1) { + original_cost += (lambda - 1); + } + } + + int candidates[64]; + int num_candidates = 0; + bool seen[64] = {false}; + + for (int j = 0; j < used_degree; j++) { + unsigned long long flags = edge_flags[j]; + + while (flags) { + int bit = __ffsll(flags) - 1; + flags &= ~(1ULL << bit); + if (bit != current_part && !seen[bit] && num_candidates < 64) { + candidates[num_candidates++] = bit; + seen[bit] = true; + } + } + } + + int best_gain = 0; + int best_target = current_part; + + for (int i = 0; i < num_candidates; i++) { + int target_part = candidates[i]; + if (target_part < 0 || target_part >= num_parts) continue; + if (nodes_in_part[target_part] >= max_part_size) continue; + + int new_cost = 0; + for (int j = 0; j < used_degree; j++) { + int lambda = __popcll(edge_flags[j] | (1ULL << target_part)); + if (lambda > 1) { + new_cost += (lambda - 1); + } + } + + int basic_gain = original_cost - new_cost; + + int current_size = nodes_in_part[current_part]; + int target_size = nodes_in_part[target_part]; + int balance_bonus = 0; + + if (current_size > target_size + 1) { + balance_bonus = 4; + } + + int total_gain = basic_gain + balance_bonus; + + if (total_gain > best_gain || + (total_gain == best_gain && target_part < best_target)) { + best_gain = total_gain; + best_target = target_part; + } + } + + if (best_gain > 0 && best_target != current_part) { + move_parts[node] = best_target; + move_priorities[node] = (best_gain << 16) + (degree_weight << 8) + (num_parts - (node % num_parts)); + atomicAdd(num_valid_moves, 1); + } + } +} + +extern "C" __global__ void execute_refinement_moves( + const int num_valid_moves, + const int *sorted_nodes, + const int *sorted_parts, + const int max_part_size, + int *partition, + int *nodes_in_part, + int *moves_executed +) { + if (blockIdx.x == 0 && threadIdx.x == 0) { + for (int i = 0; i < num_valid_moves; i++) { + int node = sorted_nodes[i]; + int target_part = sorted_parts[i]; + + if (node >= 0 && target_part >= 0) { + int current_part = partition[node]; + + if (current_part >= 0 && + nodes_in_part[target_part] < max_part_size && + nodes_in_part[current_part] > 1 && + partition[node] == current_part) { + + partition[node] = target_part; + nodes_in_part[current_part]--; + nodes_in_part[target_part]++; + (*moves_executed)++; + } + } + } + } +} + +extern "C" __global__ void radix_histogram_chunked( + const int n, + const int num_chunks, + const int *keys, + const int shift, + int *chunk_histograms +) { + int chunk = blockIdx.x; + if (chunk >= num_chunks) return; + + __shared__ int local_hist[256]; + + for (int i = threadIdx.x; i < 256; i += blockDim.x) { + local_hist[i] = 0; + } + __syncthreads(); + + int chunk_start = chunk * 256; + int chunk_end = min(chunk_start + 256, n); + + for (int i = chunk_start + threadIdx.x; i < chunk_end; i += blockDim.x) { + int digit = (keys[i] >> shift) & 0xFF; + atomicAdd(&local_hist[digit], 1); + } + __syncthreads(); + + for (int d = threadIdx.x; d < 256; d += blockDim.x) { + chunk_histograms[chunk * 256 + d] = local_hist[d]; + } +} + +extern "C" __global__ void radix_prefix_and_scatter( + const int n, + const int num_chunks, + const int *keys_in, + const int *vals_in, + const int shift, + const int *chunk_histograms, + int *chunk_offsets, + int *keys_out, + int *vals_out, + int *ready_flag +) { + if (blockIdx.x == 0 && threadIdx.x == 0) { + int digit_totals[256]; + for (int d = 0; d < 256; d++) { + digit_totals[d] = 0; + for (int c = 0; c < num_chunks; c++) { + digit_totals[d] += chunk_histograms[c * 256 + d]; + } + } + + int digit_starts[256]; + int sum = 0; + for (int d = 0; d < 256; d++) { + digit_starts[d] = sum; + sum += digit_totals[d]; + } + + int running[256]; + for (int d = 0; d < 256; d++) running[d] = digit_starts[d]; + + for (int c = 0; c < num_chunks; c++) { + for (int d = 0; d < 256; d++) { + chunk_offsets[c * 256 + d] = running[d]; + running[d] += chunk_histograms[c * 256 + d]; + } + } + + __threadfence(); + atomicExch(ready_flag, 1); + } + + if (threadIdx.x == 0) { + while (atomicAdd(ready_flag, 0) == 0) {} + } + __syncthreads(); + + int chunk = blockIdx.x; + if (chunk >= num_chunks) return; + + __shared__ int offsets[256]; + + for (int d = threadIdx.x; d < 256; d += blockDim.x) { + offsets[d] = chunk_offsets[chunk * 256 + d]; + } + __syncthreads(); + + int chunk_start = chunk * 256; + int chunk_end = min(chunk_start + 256, n); + + if (threadIdx.x == 0) { + for (int i = chunk_start; i < chunk_end; i++) { + int key = keys_in[i]; + int digit = (key >> shift) & 0xFF; + int pos = offsets[digit]++; + keys_out[pos] = key; + vals_out[pos] = vals_in[i]; + } + } +} + +extern "C" __global__ void init_indices( + const int n, + int *indices +) { + for (int i = blockIdx.x * blockDim.x + threadIdx.x; i < n; i += blockDim.x * gridDim.x) { + indices[i] = i; + } +} + +extern "C" __global__ void invert_keys( + const int n, + const int max_key, + const int *keys_in, + int *keys_out +) { + for (int i = blockIdx.x * blockDim.x + threadIdx.x; i < n; i += blockDim.x * gridDim.x) { + keys_out[i] = max_key - keys_in[i]; + } +} + +extern "C" __global__ void gather_sorted( + const int n, + const int *sorted_indices, + const int *src, + int *dst +) { + for (int i = blockIdx.x * blockDim.x + threadIdx.x; i < n; i += blockDim.x * gridDim.x) { + dst[i] = src[sorted_indices[i]]; + } +} + +extern "C" __global__ void balance_final( + const int num_nodes, + const int num_parts, + const int min_part_size, + const int max_part_size, + int *partition, + int *nodes_in_part +) { + if (blockIdx.x == 0 && threadIdx.x == 0) { + for (int part = 0; part < num_parts; part++) { + while (nodes_in_part[part] < min_part_size) { + bool moved = false; + for (int other_part = 0; other_part < num_parts && !moved; other_part++) { + if (other_part != part && nodes_in_part[other_part] > min_part_size) { + for (int node = 0; node < num_nodes; node++) { + if (partition[node] == other_part) { + partition[node] = part; + nodes_in_part[other_part]--; + nodes_in_part[part]++; + moved = true; + break; + } + } + } + } + if (!moved) break; + } + } + + for (int part = 0; part < num_parts; part++) { + while (nodes_in_part[part] > max_part_size) { + bool moved = false; + for (int other_part = 0; other_part < num_parts && !moved; other_part++) { + if (other_part != part && nodes_in_part[other_part] < max_part_size) { + for (int node = 0; node < num_nodes; node++) { + if (partition[node] == part) { + partition[node] = other_part; + nodes_in_part[part]--; + nodes_in_part[other_part]++; + moved = true; + break; + } + } + } + } + if (!moved) break; + } + } + } +} diff --git a/tig-algorithms/src/hypergraph/freud_opt/mod.rs b/tig-algorithms/src/hypergraph/freud_opt/mod.rs new file mode 100644 index 00000000..721e991d --- /dev/null +++ b/tig-algorithms/src/hypergraph/freud_opt/mod.rs @@ -0,0 +1,654 @@ +use cudarc::{ + driver::{safe::LaunchConfig, CudaModule, CudaStream, PushKernelArg}, + runtime::sys::cudaDeviceProp, +}; +use std::sync::Arc; +use std::time::Instant; +use serde_json::{Map, Value}; +use tig_challenges::hypergraph::*; + +pub fn help() { + println!("Hypergraph Partitioning Algorithm"); + println!("Adaptive clustering with GPU-accelerated refinement"); + println!(); + println!("Hyperparameters:"); + println!(" refinement - Number of refinement rounds (default: 500, range: 50-5000)"); + println!(); + println!("Usage:"); + println!(" Set the 'refinement' parameter in your benchmarker config"); + println!(" to balance between solution quality and runtime."); +} + +pub fn solve_challenge( + challenge: &Challenge, + save_solution: &dyn Fn(&Solution) -> anyhow::Result<()>, + hyperparameters: &Option>, + module: Arc, + stream: Arc, + prop: &cudaDeviceProp, +) -> anyhow::Result<()> { + println!(">>> solve_challenge START"); + let total_start = Instant::now(); + + let dummy_partition: Vec = (0..challenge.num_nodes as u32) + .map(|i| i % challenge.num_parts as u32) + .collect(); + save_solution(&Solution { partition: dummy_partition })?; + + let block_size = std::cmp::min(128, prop.maxThreadsPerBlock as u32); + + let t_load = Instant::now(); + let hyperedge_cluster_kernel = module.load_function("hyperedge_clustering")?; + let compute_preferences_kernel = module.load_function("compute_node_preferences")?; + let execute_assignments_kernel = module.load_function("execute_node_assignments")?; + let precompute_edge_flags_kernel = module.load_function("precompute_edge_flags")?; + let compute_moves_kernel = module.load_function("compute_refinement_moves")?; + let execute_moves_kernel = module.load_function("execute_refinement_moves")?; + let balance_kernel = module.load_function("balance_final")?; + let radix_hist_kernel = module.load_function("radix_histogram_chunked")?; + let radix_prefix_scatter_kernel = module.load_function("radix_prefix_and_scatter")?; + let init_indices_kernel = module.load_function("init_indices")?; + let invert_keys_kernel = module.load_function("invert_keys")?; + let gather_sorted_kernel = module.load_function("gather_sorted")?; + let t_load_elapsed = t_load.elapsed(); + + let cfg = LaunchConfig { + grid_dim: ((challenge.num_nodes as u32 + block_size - 1) / block_size, 1, 1), + block_dim: (block_size, 1, 1), + shared_mem_bytes: 0, + }; + + let one_thread_cfg = LaunchConfig { + grid_dim: (1, 1, 1), + block_dim: (1, 1, 1), + shared_mem_bytes: 0, + }; + + let hedge_cfg = LaunchConfig { + grid_dim: ((challenge.num_hyperedges as u32 + block_size - 1) / block_size, 1, 1), + block_dim: (block_size, 1, 1), + shared_mem_bytes: 0, + }; + + let mut num_hedge_clusters = 64; + + let t_alloc = Instant::now(); + let mut d_hyperedge_clusters = stream.alloc_zeros::(challenge.num_hyperedges as usize)?; + let mut d_partition = stream.alloc_zeros::(challenge.num_nodes as usize)?; + let mut d_nodes_in_part = stream.alloc_zeros::(challenge.num_parts as usize)?; + + let mut d_pref_parts = stream.alloc_zeros::(challenge.num_nodes as usize)?; + let mut d_pref_priorities = stream.alloc_zeros::(challenge.num_nodes as usize)?; + + let mut d_move_parts = stream.alloc_zeros::(challenge.num_nodes as usize)?; + let mut d_move_priorities = stream.alloc_zeros::(challenge.num_nodes as usize)?; + + let buffer_size = (challenge.num_nodes as usize) * 1024; + let mut d_global_edge_flags = stream.alloc_zeros::(buffer_size)?; + + let mut d_edge_flags_all = stream.alloc_zeros::(challenge.num_hyperedges as usize)?; + let mut d_edge_flags_double = stream.alloc_zeros::(challenge.num_hyperedges as usize)?; + + let n = challenge.num_nodes as usize; + let mut d_sort_keys_a = stream.alloc_zeros::(n)?; + let mut d_sort_keys_b = stream.alloc_zeros::(n)?; + let mut d_sort_vals_a = stream.alloc_zeros::(n)?; + let mut d_sort_vals_b = stream.alloc_zeros::(n)?; + let mut d_sorted_move_parts = stream.alloc_zeros::(n)?; + + let num_chunks: i32 = ((n + 255) / 256) as i32; + let mut d_chunk_histograms = stream.alloc_zeros::((num_chunks as usize) * 256)?; + let mut d_chunk_offsets = stream.alloc_zeros::((num_chunks as usize) * 256)?; + let mut d_ready_flag = stream.alloc_zeros::(1)?; + let t_alloc_elapsed = t_alloc.elapsed(); + + let radix_cfg = LaunchConfig { + grid_dim: (num_chunks as u32, 1, 1), + block_dim: (256, 1, 1), + shared_mem_bytes: 0, + }; + + let mut sorted_move_nodes: Vec = Vec::with_capacity(n); + let mut sorted_move_parts_cpu: Vec = Vec::with_capacity(n); + let mut valid_indices: Vec = Vec::with_capacity(n); + + let default_refinement = if challenge.num_hyperedges < 20_000 { + 400usize + } else { + 500usize + }; + + println!("refinement: {:?}", hyperparameters.as_ref().and_then(|p| p.get("refinement"))); + + let refinement_rounds = if let Some(params) = hyperparameters { + params.get("refinement") + .and_then(|v| v.as_i64()) + .map(|v| v.clamp(50, 5000) as usize) + .unwrap_or(default_refinement) + } else { + default_refinement + }; + + let t_init = Instant::now(); + unsafe { + stream.launch_builder(&hyperedge_cluster_kernel) + .arg(&(challenge.num_hyperedges as i32)) + .arg(&(num_hedge_clusters as i32)) + .arg(&challenge.d_hyperedge_offsets) + .arg(&mut d_hyperedge_clusters) + .launch(LaunchConfig { + grid_dim: ((challenge.num_hyperedges as u32 + block_size - 1) / block_size, 1, 1), + block_dim: (block_size, 1, 1), + shared_mem_bytes: 0, + })?; + } + + unsafe { + stream.launch_builder(&compute_preferences_kernel) + .arg(&(challenge.num_nodes as i32)) + .arg(&(challenge.num_parts as i32)) + .arg(&(num_hedge_clusters as i32)) + .arg(&challenge.d_node_hyperedges) + .arg(&challenge.d_node_offsets) + .arg(&d_hyperedge_clusters) + .arg(&challenge.d_hyperedge_offsets) + .arg(&mut d_pref_parts) + .arg(&mut d_pref_priorities) + .launch(cfg.clone())?; + } + stream.synchronize()?; + + let pref_parts = stream.memcpy_dtov(&d_pref_parts)?; + let pref_priorities = stream.memcpy_dtov(&d_pref_priorities)?; + + let mut indices: Vec = (0..challenge.num_nodes as usize).collect(); + indices.sort_unstable_by(|&a, &b| pref_priorities[b].cmp(&pref_priorities[a])); + + let sorted_nodes: Vec = indices.iter().map(|&i| i as i32).collect(); + let sorted_parts: Vec = indices.iter().map(|&i| pref_parts[i]).collect(); + + let d_sorted_nodes = stream.memcpy_stod(&sorted_nodes)?; + let d_sorted_parts = stream.memcpy_stod(&sorted_parts)?; + + unsafe { + stream.launch_builder(&execute_assignments_kernel) + .arg(&(challenge.num_nodes as i32)) + .arg(&(challenge.num_parts as i32)) + .arg(&(challenge.max_part_size as i32)) + .arg(&d_sorted_nodes) + .arg(&d_sorted_parts) + .arg(&mut d_partition) + .arg(&mut d_nodes_in_part) + .launch(one_thread_cfg.clone())?; + } + stream.synchronize()?; + let t_init_elapsed = t_init.elapsed(); + + let mut stagnant_rounds = 0; + let early_exit_round = if challenge.num_hyperedges < 20_000 { 90 } else { 70 }; + let max_stagnant_rounds = if challenge.num_hyperedges < 20_000 { 30 } else { 20 }; + + let t_refine1 = Instant::now(); + let mut t_gpu_kernels = 0u128; + let mut t_gpu_sort = 0u128; + let mut t_cpu_sort = 0u128; + let mut t_execute = 0u128; + let mut actual_rounds = 0usize; + let mut gpu_sort_count = 0usize; + let mut cpu_sort_count = 0usize; + + for round in 0..refinement_rounds { + actual_rounds = round + 1; + let zero = vec![0i32]; + let mut d_num_valid_moves = stream.memcpy_stod(&zero)?; + + let t0 = Instant::now(); + unsafe { + stream.launch_builder(&precompute_edge_flags_kernel) + .arg(&(challenge.num_hyperedges as i32)) + .arg(&(challenge.num_nodes as i32)) + .arg(&challenge.d_hyperedge_nodes) + .arg(&challenge.d_hyperedge_offsets) + .arg(&d_partition) + .arg(&mut d_edge_flags_all) + .arg(&mut d_edge_flags_double) + .launch(hedge_cfg.clone())?; + } + + unsafe { + stream.launch_builder(&compute_moves_kernel) + .arg(&(challenge.num_nodes as i32)) + .arg(&(challenge.num_parts as i32)) + .arg(&(challenge.max_part_size as i32)) + .arg(&challenge.d_node_hyperedges) + .arg(&challenge.d_node_offsets) + .arg(&d_partition) + .arg(&d_nodes_in_part) + .arg(&d_edge_flags_all) + .arg(&d_edge_flags_double) + .arg(&mut d_move_parts) + .arg(&mut d_move_priorities) + .arg(&mut d_num_valid_moves) + .arg(&mut d_global_edge_flags) + .launch(cfg.clone())?; + } + stream.synchronize()?; + t_gpu_kernels += t0.elapsed().as_micros(); + + let num_valid_moves = stream.memcpy_dtov(&d_num_valid_moves)?[0]; + if num_valid_moves == 0 { + break; + } + + let t2 = Instant::now(); + let move_priorities_vec = stream.memcpy_dtov(&d_move_priorities)?; + let max_priority = move_priorities_vec.iter().copied().max().unwrap_or(0); + + let num_passes = if max_priority == 0 { + 0 + } else if max_priority < 256 { + 1 + } else if max_priority < 65536 { + 2 + } else if max_priority < 16777216 { + 3 + } else { + 4 + }; + + let use_gpu_sort = num_passes > 0 && num_passes <= 3; + + let (d_sorted_nodes_ref, d_sorted_parts_ref): (&cudarc::driver::CudaSlice, &cudarc::driver::CudaSlice); + let d_sorted_nodes_tmp: cudarc::driver::CudaSlice; + let d_sorted_parts_tmp: cudarc::driver::CudaSlice; + let num_to_process: i32; + + if use_gpu_sort { + unsafe { + stream.launch_builder(&invert_keys_kernel) + .arg(&(n as i32)) + .arg(&max_priority) + .arg(&d_move_priorities) + .arg(&mut d_sort_keys_a) + .launch(cfg.clone())?; + + stream.launch_builder(&init_indices_kernel) + .arg(&(n as i32)) + .arg(&mut d_sort_vals_a) + .launch(cfg.clone())?; + } + + for pass in 0..num_passes { + let shift = pass * 8; + + stream.memset_zeros(&mut d_ready_flag)?; + + if pass % 2 == 0 { + unsafe { + stream.launch_builder(&radix_hist_kernel) + .arg(&(n as i32)) + .arg(&num_chunks) + .arg(&d_sort_keys_a) + .arg(&shift) + .arg(&mut d_chunk_histograms) + .launch(radix_cfg.clone())?; + + stream.launch_builder(&radix_prefix_scatter_kernel) + .arg(&(n as i32)) + .arg(&num_chunks) + .arg(&d_sort_keys_a) + .arg(&d_sort_vals_a) + .arg(&shift) + .arg(&d_chunk_histograms) + .arg(&mut d_chunk_offsets) + .arg(&mut d_sort_keys_b) + .arg(&mut d_sort_vals_b) + .arg(&mut d_ready_flag) + .launch(radix_cfg.clone())?; + } + } else { + unsafe { + stream.launch_builder(&radix_hist_kernel) + .arg(&(n as i32)) + .arg(&num_chunks) + .arg(&d_sort_keys_b) + .arg(&shift) + .arg(&mut d_chunk_histograms) + .launch(radix_cfg.clone())?; + + stream.launch_builder(&radix_prefix_scatter_kernel) + .arg(&(n as i32)) + .arg(&num_chunks) + .arg(&d_sort_keys_b) + .arg(&d_sort_vals_b) + .arg(&shift) + .arg(&d_chunk_histograms) + .arg(&mut d_chunk_offsets) + .arg(&mut d_sort_keys_a) + .arg(&mut d_sort_vals_a) + .arg(&mut d_ready_flag) + .launch(radix_cfg.clone())?; + } + } + } + + let sorted_vals = if num_passes % 2 == 0 { &d_sort_vals_a } else { &d_sort_vals_b }; + + unsafe { + stream.launch_builder(&gather_sorted_kernel) + .arg(&(n as i32)) + .arg(sorted_vals) + .arg(&d_move_parts) + .arg(&mut d_sorted_move_parts) + .launch(cfg.clone())?; + } + stream.synchronize()?; + + d_sorted_nodes_ref = sorted_vals; + d_sorted_parts_ref = &d_sorted_move_parts; + num_to_process = n as i32; + t_gpu_sort += t2.elapsed().as_micros(); + gpu_sort_count += 1; + } else { + let t_cpu = Instant::now(); + let move_parts = stream.memcpy_dtov(&d_move_parts)?; + + valid_indices.clear(); + valid_indices.extend( + move_priorities_vec + .iter() + .enumerate() + .filter(|(_, &priority)| priority > 0) + .map(|(i, _)| i), + ); + + if valid_indices.is_empty() { + break; + } + + valid_indices.sort_unstable_by(|&a, &b| move_priorities_vec[b].cmp(&move_priorities_vec[a])); + + sorted_move_nodes.clear(); + sorted_move_parts_cpu.clear(); + sorted_move_nodes.extend(valid_indices.iter().map(|&i| i as i32)); + sorted_move_parts_cpu.extend(valid_indices.iter().map(|&i| move_parts[i])); + + d_sorted_nodes_tmp = stream.memcpy_stod(&sorted_move_nodes)?; + d_sorted_parts_tmp = stream.memcpy_stod(&sorted_move_parts_cpu)?; + d_sorted_nodes_ref = &d_sorted_nodes_tmp; + d_sorted_parts_ref = &d_sorted_parts_tmp; + num_to_process = sorted_move_nodes.len() as i32; + t_cpu_sort += t_cpu.elapsed().as_micros(); + cpu_sort_count += 1; + } + + let mut d_moves_executed = stream.alloc_zeros::(1)?; + + let t4 = Instant::now(); + unsafe { + stream.launch_builder(&execute_moves_kernel) + .arg(&num_to_process) + .arg(d_sorted_nodes_ref) + .arg(d_sorted_parts_ref) + .arg(&(challenge.max_part_size as i32)) + .arg(&mut d_partition) + .arg(&mut d_nodes_in_part) + .arg(&mut d_moves_executed) + .launch(one_thread_cfg.clone())?; + } + stream.synchronize()?; + t_execute += t4.elapsed().as_micros(); + + let moves_executed = stream.memcpy_dtov(&d_moves_executed)?[0]; + if moves_executed == 0 { + break; + } + + if moves_executed == 1 && round > early_exit_round { + stagnant_rounds += 1; + if stagnant_rounds > max_stagnant_rounds { + break; + } + } else { + stagnant_rounds = 0; + } + } + + let t_refine1_elapsed = t_refine1.elapsed(); + + let t_balance = Instant::now(); + unsafe { + stream.launch_builder(&balance_kernel) + .arg(&(challenge.num_nodes as i32)) + .arg(&(challenge.num_parts as i32)) + .arg(&1) + .arg(&(challenge.max_part_size as i32)) + .arg(&mut d_partition) + .arg(&mut d_nodes_in_part) + .launch(one_thread_cfg.clone())?; + } + stream.synchronize()?; + let t_balance_elapsed = t_balance.elapsed(); + + let t_refine2 = Instant::now(); + for _ in 0..24 { + let zero = vec![0i32]; + let mut d_num_valid_moves = stream.memcpy_stod(&zero)?; + + unsafe { + stream.launch_builder(&precompute_edge_flags_kernel) + .arg(&(challenge.num_hyperedges as i32)) + .arg(&(challenge.num_nodes as i32)) + .arg(&challenge.d_hyperedge_nodes) + .arg(&challenge.d_hyperedge_offsets) + .arg(&d_partition) + .arg(&mut d_edge_flags_all) + .arg(&mut d_edge_flags_double) + .launch(hedge_cfg.clone())?; + } + + unsafe { + stream.launch_builder(&compute_moves_kernel) + .arg(&(challenge.num_nodes as i32)) + .arg(&(challenge.num_parts as i32)) + .arg(&(challenge.max_part_size as i32)) + .arg(&challenge.d_node_hyperedges) + .arg(&challenge.d_node_offsets) + .arg(&d_partition) + .arg(&d_nodes_in_part) + .arg(&d_edge_flags_all) + .arg(&d_edge_flags_double) + .arg(&mut d_move_parts) + .arg(&mut d_move_priorities) + .arg(&mut d_num_valid_moves) + .arg(&mut d_global_edge_flags) + .launch(cfg.clone())?; + } + stream.synchronize()?; + + let num_valid_moves = stream.memcpy_dtov(&d_num_valid_moves)?[0]; + if num_valid_moves == 0 { + break; + } + + let move_priorities_vec2 = stream.memcpy_dtov(&d_move_priorities)?; + let max_priority2 = move_priorities_vec2.iter().copied().max().unwrap_or(0); + + let num_passes2 = if max_priority2 == 0 { + 0 + } else if max_priority2 < 256 { + 1 + } else if max_priority2 < 65536 { + 2 + } else if max_priority2 < 16777216 { + 3 + } else { + 4 + }; + + let use_gpu_sort = num_passes2 > 0 && num_passes2 <= 3; + + let d_sorted_nodes_ref2: &cudarc::driver::CudaSlice; + let d_sorted_parts_ref2: &cudarc::driver::CudaSlice; + let d_sorted_nodes_tmp2: cudarc::driver::CudaSlice; + let d_sorted_parts_tmp2: cudarc::driver::CudaSlice; + let num_to_process2: i32; + + if use_gpu_sort { + unsafe { + stream.launch_builder(&invert_keys_kernel) + .arg(&(n as i32)) + .arg(&max_priority2) + .arg(&d_move_priorities) + .arg(&mut d_sort_keys_a) + .launch(cfg.clone())?; + + stream.launch_builder(&init_indices_kernel) + .arg(&(n as i32)) + .arg(&mut d_sort_vals_a) + .launch(cfg.clone())?; + } + + for pass in 0..num_passes2 { + let shift = pass * 8; + + stream.memset_zeros(&mut d_ready_flag)?; + + if pass % 2 == 0 { + unsafe { + stream.launch_builder(&radix_hist_kernel) + .arg(&(n as i32)) + .arg(&num_chunks) + .arg(&d_sort_keys_a) + .arg(&shift) + .arg(&mut d_chunk_histograms) + .launch(radix_cfg.clone())?; + + stream.launch_builder(&radix_prefix_scatter_kernel) + .arg(&(n as i32)) + .arg(&num_chunks) + .arg(&d_sort_keys_a) + .arg(&d_sort_vals_a) + .arg(&shift) + .arg(&d_chunk_histograms) + .arg(&mut d_chunk_offsets) + .arg(&mut d_sort_keys_b) + .arg(&mut d_sort_vals_b) + .arg(&mut d_ready_flag) + .launch(radix_cfg.clone())?; + } + } else { + unsafe { + stream.launch_builder(&radix_hist_kernel) + .arg(&(n as i32)) + .arg(&num_chunks) + .arg(&d_sort_keys_b) + .arg(&shift) + .arg(&mut d_chunk_histograms) + .launch(radix_cfg.clone())?; + + stream.launch_builder(&radix_prefix_scatter_kernel) + .arg(&(n as i32)) + .arg(&num_chunks) + .arg(&d_sort_keys_b) + .arg(&d_sort_vals_b) + .arg(&shift) + .arg(&d_chunk_histograms) + .arg(&mut d_chunk_offsets) + .arg(&mut d_sort_keys_a) + .arg(&mut d_sort_vals_a) + .arg(&mut d_ready_flag) + .launch(radix_cfg.clone())?; + } + } + } + + let sorted_vals2 = if num_passes2 % 2 == 0 { &d_sort_vals_a } else { &d_sort_vals_b }; + + unsafe { + stream.launch_builder(&gather_sorted_kernel) + .arg(&(n as i32)) + .arg(sorted_vals2) + .arg(&d_move_parts) + .arg(&mut d_sorted_move_parts) + .launch(cfg.clone())?; + } + stream.synchronize()?; + + d_sorted_nodes_ref2 = sorted_vals2; + d_sorted_parts_ref2 = &d_sorted_move_parts; + num_to_process2 = n as i32; + } else { + let move_parts = stream.memcpy_dtov(&d_move_parts)?; + + valid_indices.clear(); + valid_indices.extend( + move_priorities_vec2 + .iter() + .enumerate() + .filter(|(_, &priority)| priority > 0) + .map(|(i, _)| i), + ); + + if valid_indices.is_empty() { + break; + } + + valid_indices.sort_unstable_by(|&a, &b| move_priorities_vec2[b].cmp(&move_priorities_vec2[a])); + + sorted_move_nodes.clear(); + sorted_move_parts_cpu.clear(); + sorted_move_nodes.extend(valid_indices.iter().map(|&i| i as i32)); + sorted_move_parts_cpu.extend(valid_indices.iter().map(|&i| move_parts[i])); + + d_sorted_nodes_tmp2 = stream.memcpy_stod(&sorted_move_nodes)?; + d_sorted_parts_tmp2 = stream.memcpy_stod(&sorted_move_parts_cpu)?; + d_sorted_nodes_ref2 = &d_sorted_nodes_tmp2; + d_sorted_parts_ref2 = &d_sorted_parts_tmp2; + num_to_process2 = sorted_move_nodes.len() as i32; + } + + let mut d_moves_executed = stream.alloc_zeros::(1)?; + + unsafe { + stream.launch_builder(&execute_moves_kernel) + .arg(&num_to_process2) + .arg(d_sorted_nodes_ref2) + .arg(d_sorted_parts_ref2) + .arg(&(challenge.max_part_size as i32)) + .arg(&mut d_partition) + .arg(&mut d_nodes_in_part) + .arg(&mut d_moves_executed) + .launch(one_thread_cfg.clone())?; + } + stream.synchronize()?; + + let moves_executed = stream.memcpy_dtov(&d_moves_executed)?[0]; + if moves_executed == 0 { + break; + } + } + let t_refine2_elapsed = t_refine2.elapsed(); + + let partition = stream.memcpy_dtov(&d_partition)?; + let partition_u32: Vec = partition.iter().map(|&x| x as u32).collect(); + + save_solution(&Solution { partition: partition_u32 })?; + + let total_elapsed = total_start.elapsed(); + println!("=== FULL PROFILING ==="); + println!("load_function: {:.2}ms", t_load_elapsed.as_micros() as f64 / 1000.0); + println!("alloc_zeros: {:.2}ms", t_alloc_elapsed.as_micros() as f64 / 1000.0); + println!("init (cluster+assign): {:.2}ms", t_init_elapsed.as_micros() as f64 / 1000.0); + println!("refine1 ({} rounds): {:.2}ms", actual_rounds, t_refine1_elapsed.as_micros() as f64 / 1000.0); + println!(" - GPU kernels: {:.2}ms", t_gpu_kernels as f64 / 1000.0); + println!(" - GPU sort: {:.2}ms ({} times)", t_gpu_sort as f64 / 1000.0, gpu_sort_count); + println!(" - CPU sort: {:.2}ms ({} times)", t_cpu_sort as f64 / 1000.0, cpu_sort_count); + println!(" - execute_moves: {:.2}ms", t_execute as f64 / 1000.0); + println!("balance: {:.2}ms", t_balance_elapsed.as_micros() as f64 / 1000.0); + println!("refine2 (24 rounds): {:.2}ms", t_refine2_elapsed.as_micros() as f64 / 1000.0); + println!("TOTAL: {:.2}ms", total_elapsed.as_micros() as f64 / 1000.0); + println!(">>> solve_challenge END"); + + Ok(()) +} diff --git a/tig-algorithms/src/hypergraph/mod.rs b/tig-algorithms/src/hypergraph/mod.rs index 92cc677d..5a55a2ee 100644 --- a/tig-algorithms/src/hypergraph/mod.rs +++ b/tig-algorithms/src/hypergraph/mod.rs @@ -24,7 +24,8 @@ pub use sigma_freud as c005_a010; // c005_a011 -// c005_a012 +pub mod freud_opt; +pub use freud_opt as c005_a012; // c005_a013 diff --git a/tig-algorithms/src/vehicle_routing/fast_lane_v2/README.md b/tig-algorithms/src/vehicle_routing/fast_lane_v2/README.md new file mode 100644 index 00000000..b847283c --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/fast_lane_v2/README.md @@ -0,0 +1,23 @@ +# TIG Code Submission + +## Submission Details + +* **Challenge Name:** vehicle_routing +* **Algorithm Name:** fast_lane_v2 +* **Copyright:** 2026 testing +* **Identity of Submitter:** testing +* **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/fast_lane_v2/builder.rs b/tig-algorithms/src/vehicle_routing/fast_lane_v2/builder.rs new file mode 100644 index 00000000..c876c413 --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/fast_lane_v2/builder.rs @@ -0,0 +1,112 @@ +use super::instance::Instance; +use rand::rngs::SmallRng; +use rand::Rng; + +pub struct Builder; + +impl Builder { + pub fn build_routes(data: &Instance, rng: &mut SmallRng, randomize: bool) -> Vec> { + let mut routes = Vec::new(); + let mut nodes: Vec = (1..data.nb_nodes).collect(); + let n = nodes.len(); + nodes.sort_by(|&a, &b| data.dm(0, a).cmp(&data.dm(0, b))); + + if randomize { + let window = if n < 1000 { 10 } else { 5 }; + for i in 0..(n - 1) { + nodes.swap(i, rng.gen_range(i + 1..=(i + window).min(n - 1))); + } + } + + let mut available = vec![true; data.nb_nodes]; + available[0] = false; + + while let Some(node) = nodes.pop() { + if !available[node] { + continue; + } + available[node] = false; + let mut route = vec![0, node, 0]; + let mut route_demand = data.demands[node]; + + while let Some((best_node, best_pos)) = + Self::find_best_insertion(&route, &nodes, &available, route_demand, data) + { + available[best_node] = false; + route_demand += data.demands[best_node]; + route.insert(best_pos, best_node); + } + + routes.push(route); + } + + routes + } + + fn find_best_insertion( + route: &Vec, + nodes: &Vec, + available: &Vec, + route_demand: i32, + data: &Instance, + ) -> Option<(usize, usize)> { + let mut best_c2 = None; + let mut best = None; + for &insert_node in nodes.iter() { + if !available[insert_node] || route_demand + data.demands[insert_node] > data.max_capacity { + continue; + } + + let mut curr_time = 0; + let mut curr_node = 0; + + for pos in 1..route.len() { + let next_node = route[pos]; + let new_arrival_time_insert_node = + data.start_tw[insert_node].max(curr_time + data.dm(curr_node, insert_node)); + if new_arrival_time_insert_node > data.end_tw[insert_node] { + break; + } + + let c11 = data.dm(curr_node, insert_node) + data.dm(insert_node, next_node) - data.dm(curr_node, next_node); + let c2 = data.dm(0, insert_node) - c11; + + let c2_is_better = match best_c2 { + None => true, + Some(x) => c2 > x, + }; + + if c2_is_better + && Self::is_feasible( + route, + insert_node, + new_arrival_time_insert_node + data.service_times[insert_node], + pos, + data, + ) + { + best_c2 = Some(c2); + best = Some((insert_node, pos)); + } + + curr_time = + data.start_tw[next_node].max(curr_time + data.dm(curr_node, next_node)) + data.service_times[next_node]; + curr_node = next_node; + } + } + best + } + + fn is_feasible(route: &Vec, mut curr_node: usize, mut curr_time: i32, start_pos: usize, data: &Instance) -> bool { + for pos in start_pos..route.len() { + let next_node = route[pos]; + curr_time += data.dm(curr_node, next_node); + if curr_time > data.end_tw[route[pos]] { + return false; + } + curr_time = curr_time.max(data.start_tw[next_node]) + data.service_times[next_node]; + curr_node = next_node; + } + true + } +} diff --git a/tig-algorithms/src/vehicle_routing/fast_lane_v2/config.rs b/tig-algorithms/src/vehicle_routing/fast_lane_v2/config.rs new file mode 100644 index 00000000..01a6d887 --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/fast_lane_v2/config.rs @@ -0,0 +1,208 @@ +use serde::{Deserialize, Serialize}; +use serde_json::{Map, Value}; + +#[derive(Serialize, Deserialize, Clone, Copy)] +pub struct Config { + pub exploration_level: usize, + pub allow_swap3: bool, + pub granularity: usize, + pub granularity2: usize, + pub penalty_tw: usize, + pub penalty_capa: usize, + pub target_ratio: f64, + pub max_it_noimprov: usize, + pub max_it_total: usize, + pub nb_it_adapt_penalties: usize, + pub nb_it_traces: usize, + pub mu: usize, + pub mu_start: usize, + pub lambda: usize, + pub nb_close: usize, + pub nb_elite: usize, +} + +impl Config { + fn preset(exploration_level: usize, nb_nodes: usize) -> Self { + let p = if nb_nodes <= 700 { + 20 + } else if nb_nodes <= 1000 { + 30 + } else if nb_nodes <= 1200 { + 50 + } else if nb_nodes <= 1500 { + 80 + } else if nb_nodes <= 2000 { + 150 + } else if nb_nodes <= 3000 { + 200 + } else { + 500 + }; + + match exploration_level { + 0 => Self { + exploration_level: 0, + allow_swap3: true, + granularity: 40, + granularity2: 20, + penalty_tw: p, + penalty_capa: p, + target_ratio: 0.2, + max_it_noimprov: 0, + max_it_total: 0, + nb_it_adapt_penalties: 100, + nb_it_traces: 100, + mu: 2, + mu_start: 1, + lambda: 1, + nb_close: 1, + nb_elite: 1, + }, + 1 => Self { + exploration_level: 0, + allow_swap3: true, + granularity: 40, + granularity2: 20, + penalty_tw: p, + penalty_capa: p, + target_ratio: 0.2, + max_it_noimprov: 0, + max_it_total: 0, + nb_it_adapt_penalties: 100, + nb_it_traces: 100, + mu: 2, + mu_start: 5, + lambda: 1, + nb_close: 1, + nb_elite: 1, + }, + 2 => Self { + exploration_level: 1, + allow_swap3: true, + granularity: 40, + granularity2: 20, + penalty_tw: p, + penalty_capa: p, + target_ratio: 0.2, + max_it_noimprov: 10, + max_it_total: 50, + nb_it_adapt_penalties: 100, + nb_it_traces: 100, + mu: 3, + mu_start: 6, + lambda: 3, + nb_close: 1, + nb_elite: 1, + }, + 3 => Self { + exploration_level: 2, + allow_swap3: true, + granularity: 40, + granularity2: 20, + penalty_tw: p, + penalty_capa: p, + target_ratio: 0.2, + max_it_noimprov: 100, + max_it_total: 500, + nb_it_adapt_penalties: 20, + nb_it_traces: 20, + mu: 5, + mu_start: 10, + lambda: 5, + nb_close: 2, + nb_elite: 2, + }, + 4 => Self { + exploration_level: 3, + allow_swap3: false, + granularity: 30, + granularity2: 20, + penalty_tw: p, + penalty_capa: p, + target_ratio: 0.2, + max_it_noimprov: 500, + max_it_total: 5_000, + nb_it_adapt_penalties: 20, + nb_it_traces: 100, + mu: 10, + mu_start: 20, + lambda: 10, + nb_close: 2, + nb_elite: 3, + }, + 5 => Self { + exploration_level: 4, + allow_swap3: false, + granularity: 30, + granularity2: 20, + penalty_tw: p, + penalty_capa: p, + target_ratio: 0.2, + max_it_noimprov: 5_000, + max_it_total: 50_000, + nb_it_adapt_penalties: 50, + nb_it_traces: 200, + mu: 12, + mu_start: 24, + lambda: 20, + nb_close: 3, + nb_elite: 4, + }, + 6 => Self { + exploration_level: 5, + allow_swap3: false, + granularity: 30, + granularity2: 20, + penalty_tw: p, + penalty_capa: p, + target_ratio: 0.2, + max_it_noimprov: 10_000, + max_it_total: 200_000, + nb_it_adapt_penalties: 50, + nb_it_traces: 500, + mu: 25, + mu_start: 50, + lambda: 40, + nb_close: 3, + nb_elite: 8, + }, + _ => Self::defaults(nb_nodes), + } + } + + pub fn defaults(nb_nodes: usize) -> Self { + Self::preset(0, nb_nodes) + } + + pub fn initialize(hyperparameters: &Option>, nb_nodes: usize) -> Self { + let mut base_params = Self::defaults(nb_nodes); + + if let Some(v) = hyperparameters.as_ref().and_then(|m| m.get("exploration_level")) { + match v { + Value::Number(n) => { + if let Some(u) = n.as_u64() { + base_params = Self::preset(u as usize, nb_nodes); + } + } + Value::String(s) => { + if let Ok(u) = s.parse::() { + base_params = Self::preset(u, nb_nodes); + } + } + _ => {} + } + } + + let mut merged_params = serde_json::to_value(base_params).expect("Config serializable"); + if let (Value::Object(ref mut obj), Some(map)) = (&mut merged_params, hyperparameters) { + for (k, v) in map { + if k == "exploration_level" { + continue; + } + obj.insert(k.clone(), v.clone()); + } + } + + serde_json::from_value(merged_params).unwrap_or_else(|_| Self::defaults(nb_nodes)) + } +} diff --git a/tig-algorithms/src/vehicle_routing/fast_lane_v2/evolution.rs b/tig-algorithms/src/vehicle_routing/fast_lane_v2/evolution.rs new file mode 100644 index 00000000..fa7db83f --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/fast_lane_v2/evolution.rs @@ -0,0 +1,350 @@ +use super::instance::Instance; +use super::config::Config; +use super::solution::Individual; +use super::gene_pool::{GenePool, Metric}; +use super::builder::Builder; +use super::operators::LocalOps; +use super::route_eval::RouteEval; +use rand::rngs::SmallRng; +use rand::Rng; +use rand::seq::SliceRandom; +use tig_challenges::vehicle_routing::*; +use anyhow::Result; +use std::time::Instant; + +pub struct Evolution<'a> { + pub data: &'a Instance, + pub params: Config, + pub population: GenePool<'a>, +} + +impl<'a> Evolution<'a> { + pub fn new(data: &'a Instance, params: Config) -> Self { + let population = GenePool::new(data); + Self { data, params, population } + } + + fn repair_and_maybe_add(&mut self, ls: &mut LocalOps, rng: &mut SmallRng) { + let mut repaired_routes1: Vec> = Vec::new(); + ls.runls(&mut repaired_routes1, rng, &self.params, true, 100); + let repaired1 = Individual::new_from_routes(self.data, &self.params, repaired_routes1); + + if repaired1.load_excess == 0 && repaired1.tw_violation == 0 { + self.population.add(repaired1, &self.params); + } + } + + pub fn generate_initial_individual(&mut self, rng: &mut SmallRng, ls: &mut LocalOps, randomize: bool) { + let mut routes: Vec> = Builder::build_routes(self.data, rng, randomize); + ls.runls(&mut routes, rng, &self.params, false, 0); + let ind = Individual::new_from_routes(self.data, &self.params, routes); + let is_capa_feasible = ind.load_excess == 0; + let is_tw_feasible = ind.tw_violation == 0; + + self.population.add(ind, &self.params); + self.population.record_and_adapt(is_capa_feasible, is_tw_feasible, &mut self.params); + if !is_capa_feasible || !is_tw_feasible { + self.repair_and_maybe_add(ls, rng); + } + } + + pub fn generate_crossover_individual(&mut self, rng: &mut SmallRng, ls: &mut LocalOps) { + let p1 = self.population.get_binary_tournament(rng); + let mut p2 = self.population.get_binary_tournament(rng); + while std::ptr::eq(p1, p2) { + p2 = self.population.get_binary_tournament(rng); + } + let t2 = self.extract_giant_tour(&p2.routes); + let extra = if rng.gen_ratio(1, 10) { 1 } else { 0 }; + let target_routes = (p1.nb_routes + extra).clamp(self.data.lb_vehicles, self.data.nb_vehicles); + + let mut child_tour = self.crossover_rbx(p1, &t2, rng); + self.mutate_tour(&mut child_tour, rng); + + let mut child_routes = self.split(&child_tour, target_routes); + ls.runls(&mut child_routes, rng, &self.params, false, 0); + let child = Individual::new_from_routes(self.data, &self.params, child_routes); + let is_capa_feasible = child.load_excess == 0; + let is_tw_feasible = child.tw_violation == 0; + + self.population.add(child, &self.params); + self.population.record_and_adapt(is_capa_feasible, is_tw_feasible, &mut self.params); + if !is_capa_feasible || !is_tw_feasible { + self.repair_and_maybe_add(ls, rng); + } + } + + pub fn run( + &mut self, + rng: &mut SmallRng, + t0: &Instant, + save_solution: Option<&dyn Fn(&Solution) -> Result<()>>, + ) -> Option<(Vec>, i32)> { + if let Some(save) = save_solution { + let dummy_routes: Vec> = (1..self.data.nb_nodes).map(|i| vec![0, i, 0]).collect(); + let _ = save(&Solution { routes: dummy_routes }); + } + + let mut ls = LocalOps::new(self.data, self.params); + + let diversity_boost = if self.data.nb_nodes < 1000 { 3 } else { 1 }; + for it in 0..(self.params.mu_start + diversity_boost) { + self.generate_initial_individual(rng, &mut ls, it > 0); + } + + let mut best_metric: Metric = self.population.best_metric(); + let mut it_noimprov: usize = 0; + let mut it_total: usize = 0; + while it_noimprov < self.params.max_it_noimprov && it_total < self.params.max_it_total { + self.generate_crossover_individual(rng, &mut ls); + + if it_total % self.params.nb_it_traces == 0 { + self.population + .print_trace(it_total, it_noimprov, t0.elapsed().as_secs_f64(), &self.params); + } + + let cur = self.population.best_metric(); + if cur.better_than(best_metric) { + best_metric = cur; + it_noimprov = 0; + + if let Some(best) = self.population.best_feasible() { + if let Some(save) = save_solution { + let _ = save(&Solution { routes: best.routes }); + } + } + } else { + it_noimprov += 1; + } + it_total += 1; + } + + if let Some(best) = self.population.best_feasible() { + let mut best_routes = best.routes.clone(); + ls.runls(&mut best_routes, rng, &self.params, false, 0); + let best_after = Individual::new_from_routes(self.data, &self.params, best_routes); + let chosen = + if best_after.tw_violation == 0 && best_after.load_excess == 0 && best_after.distance < best.distance { + best_after + } else { + best + }; + if let Some(save) = save_solution { + let _ = save(&Solution { routes: chosen.routes.clone() }); + } + Some((chosen.routes, chosen.cost as i32)) + } else { + None + } + } + + fn mutate_tour(&self, tour: &mut Vec, rng: &mut SmallRng) { + let n = tour.len(); + if n < 4 { + return; + } + if rng.gen_ratio(1, 5) { + let i = rng.gen_range(0..n - 1); + let j = rng.gen_range(i + 1..n); + tour[i..=j].reverse(); + } + if rng.gen_ratio(1, 6) { + let i = rng.gen_range(0..n); + let mut j = rng.gen_range(0..n); + if j == i { + j = (j + 1) % n; + } + let node = tour.remove(i); + let pos = if j <= tour.len() { j } else { tour.len() }; + tour.insert(pos, node); + } + if n >= 8 && rng.gen_ratio(1, 8) { + let mut cuts = [0usize; 4]; + for c in cuts.iter_mut() { + *c = rng.gen_range(1..n); + } + cuts.sort_unstable(); + let a = cuts[0]; + let b = cuts[1]; + let c = cuts[2]; + let d = cuts[3]; + let mut new_tour = Vec::with_capacity(n); + new_tour.extend_from_slice(&tour[0..a]); + new_tour.extend_from_slice(&tour[c..d]); + new_tour.extend_from_slice(&tour[b..c]); + new_tour.extend_from_slice(&tour[a..b]); + new_tour.extend_from_slice(&tour[d..n]); + *tour = new_tour; + } + } + + pub fn split(&self, giant: &Vec, target_routes: usize) -> Vec> { + let n = giant.len(); + if n == 0 { + return Vec::new(); + } + + let k = target_routes.max(1); + let inf = i64::MAX / 4; + + let mut dp = vec![vec![inf; n + 1]; k + 1]; + let mut pred = vec![vec![0usize; n + 1]; k + 1]; + dp[0][0] = 0; + + let factor_split: f32 = 1.5; + let cap_limit: i32 = (factor_split * (self.data.max_capacity as f32)) as i32; + let depot = RouteEval::singleton(self.data, 0); + + for kk in 1..=k { + for i in (kk - 1)..n { + let base = dp[kk - 1][i]; + if base >= inf { + continue; + } + + let mut acc = RouteEval::join2(self.data, &depot, &RouteEval::singleton(self.data, giant[i])); + for j in (i + 1)..=n { + let cost = RouteEval::eval2(self.data, &self.params, &acc, &depot); + let cand = base + cost; + if cand < dp[kk][j] { + dp[kk][j] = cand; + pred[kk][j] = i; + } + if acc.load > cap_limit { + break; + } + if j < n { + let next = RouteEval::singleton(self.data, giant[j]); + acc = RouteEval::join2(self.data, &acc, &next); + } + } + } + } + + let mut best_k = 1usize; + let mut best_val = dp[1][n]; + for kk in 2..=k { + let val = dp[kk][n]; + if val < best_val { + best_val = val; + best_k = kk; + } + } + + if best_val >= inf { + let mut routes: Vec> = Vec::with_capacity(n); + for &id in giant { + routes.push(vec![0, id, 0]); + } + return routes; + } + + let mut routes: Vec> = Vec::with_capacity(best_k); + let mut j = n; + for kk in (1..=best_k).rev() { + let i = pred[kk][j]; + let mut r: Vec = Vec::with_capacity((j - i) + 2); + r.push(0); + for p in i..j { + r.push(giant[p]); + } + r.push(0); + routes.push(r); + j = i; + } + routes.reverse(); + routes + } + + pub fn extract_giant_tour(&self, routes: &[Vec]) -> Vec { + let (x0, y0) = (self.data.node_positions[0].0 as f64, self.data.node_positions[0].1 as f64); + let mut route_angles: Vec<(f64, usize)> = Vec::new(); + + for (r_idx, r) in routes.iter().enumerate() { + if r.len() <= 2 { + continue; + } + let mut sum_x = 0.0; + let mut sum_y = 0.0; + let mut cnt = 0usize; + for &id in r.iter().skip(1).take(r.len().saturating_sub(2)) { + sum_x += self.data.node_positions[id].0 as f64; + sum_y += self.data.node_positions[id].1 as f64; + cnt += 1; + } + let bx = sum_x / (cnt as f64); + let by = sum_y / (cnt as f64); + let angle = (by - y0).atan2(bx - x0); + route_angles.push((angle, r_idx)); + } + + route_angles.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal)); + + let mut tour = Vec::with_capacity(self.data.nb_nodes - 1); + for &(_, r_idx) in &route_angles { + let r = &routes[r_idx]; + for &id in r.iter().skip(1).take(r.len().saturating_sub(2)) { + if id != 0 { + tour.push(id); + } + } + } + tour + } + + fn crossover_rbx(&self, p1: &Individual, t2: &Vec, rng: &mut SmallRng) -> Vec { + let n = self.data.nb_nodes - 1; + if n == 0 { + return Vec::new(); + } + + let mut cand: Vec = Vec::new(); + for (idx, r) in p1.routes.iter().enumerate() { + if r.len() > 2 { + cand.push(idx); + } + } + if cand.is_empty() { + return t2.clone(); + } + + cand.shuffle(rng); + let keep = rng.gen_range(1..=cand.len().min(3)); + cand.truncate(keep); + + let mut used = vec![false; self.data.nb_nodes]; + let mut child: Vec = Vec::with_capacity(n); + + for &ri in &cand { + let r = &p1.routes[ri]; + for &id in r.iter().skip(1).take(r.len() - 2) { + if !used[id] { + used[id] = true; + child.push(id); + } + } + } + + for &id in t2 { + if !used[id] { + used[id] = true; + child.push(id); + } + } + + if child.len() < n { + for id in 1..self.data.nb_nodes { + if !used[id] { + used[id] = true; + child.push(id); + if child.len() == n { + break; + } + } + } + } else if child.len() > n { + child.truncate(n); + } + child + } +} diff --git a/tig-algorithms/src/vehicle_routing/fast_lane_v2/gene_pool.rs b/tig-algorithms/src/vehicle_routing/fast_lane_v2/gene_pool.rs new file mode 100644 index 00000000..308d6c6f --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/fast_lane_v2/gene_pool.rs @@ -0,0 +1,324 @@ +use super::instance::Instance; +use super::config::Config; +use super::solution::Individual; +use rand::rngs::SmallRng; +use rand::Rng; +use std::collections::VecDeque; + +#[derive(Default)] +pub struct Subpopulation { + pub indivs: Vec, + pub prox: Vec>, + pub biased_fitness: Vec, + pub order_cost: Vec, +} + +pub struct GenePool<'a> { + pub data: &'a Instance, + pub feasible: Subpopulation, + pub infeasible: Subpopulation, + cap_window: VecDeque, + tw_window: VecDeque, + since_last_adapt: usize, +} + +impl<'a> GenePool<'a> { + pub fn new(data: &'a Instance) -> Self { + Self { + data, + feasible: Subpopulation::default(), + infeasible: Subpopulation::default(), + cap_window: VecDeque::new(), + tw_window: VecDeque::new(), + since_last_adapt: 0, + } + } + + pub fn survivors_selection(sub: &mut Subpopulation, params: &Config) { + while sub.indivs.len() > params.mu { + let idx = Self::worst_index_biased_with_clone_priority(sub); + Self::remove_at_index(sub, idx); + Self::order_cost_rebuild(sub); + Self::update_biased_fitnesses(sub, params); + } + } + + pub fn add(&mut self, ind: Individual, params: &Config) { + let is_feasible = ind.load_excess == 0 && ind.tw_violation == 0; + let sub = if is_feasible { &mut self.feasible } else { &mut self.infeasible }; + + let new_idx = sub.indivs.len(); + sub.indivs.push(ind); + Self::prox_add(sub, self.data, new_idx); + + Self::order_cost_rebuild(sub); + Self::update_biased_fitnesses(sub, params); + + if sub.indivs.len() > params.mu + params.lambda { + Self::survivors_selection(sub, params); + } + } + + pub fn record_and_adapt(&mut self, cap_feasible: bool, tw_feasible: bool, params: &mut Config) { + let period = params.nb_it_adapt_penalties; + self.cap_window.push_back(cap_feasible); + self.tw_window.push_back(tw_feasible); + if self.cap_window.len() > period { + self.cap_window.pop_front(); + } + if self.tw_window.len() > period { + self.tw_window.pop_front(); + } + self.since_last_adapt += 1; + + if self.since_last_adapt == period { + let cap_ok = self.cap_window.iter().rev().take(period).filter(|&&b| b).count(); + let tw_ok = self.tw_window.iter().rev().take(period).filter(|&&b| b).count(); + let frac_cap = (cap_ok as f64) / (period as f64); + let frac_tw = (tw_ok as f64) / (period as f64); + + if frac_cap < params.target_ratio { + params.penalty_capa = (((params.penalty_capa as f64) * 1.3).ceil()).clamp(1.0, 10_000.0) as usize; + } else { + params.penalty_capa = (((params.penalty_capa as f64) * 0.7).floor()).clamp(1.0, 10_000.0) as usize; + } + if frac_tw < params.target_ratio { + params.penalty_tw = (((params.penalty_tw as f64) * 1.3).ceil()).clamp(1.0, 10_000.0) as usize; + } else { + params.penalty_tw = (((params.penalty_tw as f64) * 0.7).floor()).clamp(1.0, 10_000.0) as usize; + } + + self.since_last_adapt = 0; + self.recompute_costs(params); + } + } + + pub fn recompute_costs(&mut self, params: &Config) { + for ind in self.feasible.indivs.iter_mut() { + ind.recompute_cost(params); + } + for ind in self.infeasible.indivs.iter_mut() { + ind.recompute_cost(params); + } + + Self::order_cost_rebuild(&mut self.feasible); + Self::order_cost_rebuild(&mut self.infeasible); + + Self::update_biased_fitnesses(&mut self.feasible, params); + Self::update_biased_fitnesses(&mut self.infeasible, params); + } + + pub fn best_feasible(&self) -> Option { + if !self.feasible.indivs.is_empty() { + return Some(self.feasible.indivs[self.feasible.order_cost[0]].clone()); + } + None + } + + pub fn get_binary_tournament<'b>(&'b self, rng: &mut SmallRng) -> &'b Individual { + let feas_n = self.feasible.indivs.len(); + let inf_n = self.infeasible.indivs.len(); + + let pick = |rng: &mut SmallRng| -> (bool, usize, f64) { + if feas_n > 0 && (inf_n == 0 || rng.gen_ratio(3, 4)) { + let i = rng.gen_range(0..feas_n); + (true, i, self.feasible.biased_fitness[i]) + } else { + let i = rng.gen_range(0..inf_n); + (false, i, self.infeasible.biased_fitness[i]) + } + }; + + let (f1, i1, b1) = pick(rng); + let (f2, i2, b2) = pick(rng); + + if b1 <= b2 { + if f1 { &self.feasible.indivs[i1] } else { &self.infeasible.indivs[i1] } + } else if f2 { + &self.feasible.indivs[i2] + } else { + &self.infeasible.indivs[i2] + } + } + + pub fn best_metric(&self) -> Metric { + if !self.feasible.indivs.is_empty() { + let mut best_d = i32::MAX; + for ind in &self.feasible.indivs { + if ind.distance < best_d { + best_d = ind.distance; + } + } + return Metric { + feasible: true, + distance: best_d, + infeas_sum: 0, + }; + } + let mut best_sum = i32::MAX; + let mut best_dist = i32::MAX; + for ind in &self.infeasible.indivs { + let s = ind.load_excess + ind.tw_violation; + if s < best_sum || (s == best_sum && ind.distance < best_dist) { + best_sum = s; + best_dist = ind.distance; + } + } + Metric { + feasible: false, + distance: best_dist, + infeas_sum: best_sum, + } + } + + pub fn print_trace(&self, _it_total: usize, _it_no_improve: usize, _elapsed_sec: f64, _params: &Config) {} + + fn worst_index_biased_with_clone_priority(sub: &Subpopulation) -> usize { + const CLONE_EPS: f64 = 1e-6; + let mut worst_idx = 0usize; + let mut worst_is_clone = (sub.prox[0][0].0 <= CLONE_EPS) as u8; + let mut worst_fit = sub.biased_fitness[0]; + + for i in 1..sub.indivs.len() { + let is_clone = (sub.prox[i][0].0 <= CLONE_EPS) as u8; + let bf = sub.biased_fitness[i]; + if is_clone > worst_is_clone || (is_clone == worst_is_clone && bf > worst_fit) { + worst_is_clone = is_clone; + worst_fit = bf; + worst_idx = i; + } + } + worst_idx + } + + fn prox_add(sub: &mut Subpopulation, data: &Instance, new_idx: usize) { + let m = sub.indivs.len(); + sub.prox.push(Vec::with_capacity(m.saturating_sub(1))); + + for i in 0..new_idx { + let d = Self::broken_pairs_distance(data, &sub.indivs[new_idx], &sub.indivs[i]); + let vec_i = &mut sub.prox[i]; + let pos = vec_i.partition_point(|(dd, _)| *dd <= d); + vec_i.insert(pos, (d, new_idx)); + sub.prox[new_idx].push((d, i)); + } + sub.prox[new_idx].sort_unstable_by(|a, b| a.0.partial_cmp(&b.0).unwrap()); + } + + fn remove_at_index(sub: &mut Subpopulation, idx: usize) { + let last = sub.indivs.len() - 1; + sub.indivs.swap_remove(idx); + sub.prox.swap_remove(idx); + sub.biased_fitness.swap_remove(idx); + + for list in sub.prox.iter_mut() { + list.retain(|&(_, j)| j != idx); + if last != idx { + for pair in list.iter_mut() { + if pair.1 == last { + pair.1 = idx; + } + } + } + } + } + + fn order_cost_rebuild(sub: &mut Subpopulation) { + sub.order_cost.clear(); + sub.order_cost.extend(0..sub.indivs.len()); + sub.order_cost.sort_unstable_by_key(|&i| sub.indivs[i].cost); + } + + fn update_biased_fitnesses(sub: &mut Subpopulation, params: &Config) { + let n = sub.indivs.len(); + if n == 0 { + return; + } + sub.biased_fitness.resize(n, 0.0); + if n == 1 { + sub.biased_fitness[0] = 0.0; + return; + } + + let nb_close = params.nb_close.min(n - 1); + + let mut avg_closest = vec![0.0; n]; + for i in 0..n { + let neighbors = &sub.prox[i]; + let mut sum = 0.0; + for t in 0..nb_close { + sum += neighbors[t].0; + } + avg_closest[i] = sum / (nb_close as f64); + } + + let mut div_pairs: Vec<(f64, usize)> = (0..n).map(|i| (-avg_closest[i], i)).collect(); + div_pairs.sort_unstable_by(|a, b| a.0.partial_cmp(&b.0).unwrap()); + + let denom = (n - 1) as f64; + let mut div_rank = vec![0.0; n]; + for (pos, &(_, idx)) in div_pairs.iter().enumerate() { + div_rank[idx] = (pos as f64) / denom; + } + + let mut cost_pos = vec![0usize; n]; + for (pos, &idx) in sub.order_cost.iter().enumerate() { + cost_pos[idx] = pos; + } + let fit_rank: Vec = cost_pos.iter().map(|&p| (p as f64) / denom).collect(); + + let scale = 1.0 - (params.nb_elite as f64) / (n as f64); + for i in 0..n { + if cost_pos[i] < params.nb_elite { + sub.biased_fitness[i] = fit_rank[i]; + } else { + sub.biased_fitness[i] = fit_rank[i] + scale * div_rank[i]; + } + } + } + + fn broken_pairs_distance(data: &Instance, indiv_a: &Individual, indiv_b: &Individual) -> f64 { + let pred_a = &indiv_a.pred; + let succ_a = &indiv_a.succ; + let pred_b = &indiv_b.pred; + let succ_b = &indiv_b.succ; + + let n_clients = data.nb_nodes - 1; + let mut differences = 0usize; + for j in 1..=n_clients { + if succ_a[j] != succ_b[j] && succ_a[j] != pred_b[j] { + differences += 1; + } + if pred_a[j] == 0 && pred_b[j] != 0 && succ_b[j] != 0 { + differences += 1; + } + } + (differences as f64) / (n_clients as f64) + } +} + +#[derive(Copy, Clone, PartialEq, Eq)] +pub struct Metric { + pub feasible: bool, + pub distance: i32, + pub infeas_sum: i32, +} + +impl Metric { + #[inline] + pub fn better_than(self, other: Metric) -> bool { + if self.feasible && !other.feasible { + return true; + } + if !self.feasible && other.feasible { + return false; + } + if self.feasible { + self.distance < other.distance + } else if self.infeas_sum != other.infeas_sum { + self.infeas_sum < other.infeas_sum + } else { + self.distance < other.distance + } + } +} diff --git a/tig-algorithms/src/vehicle_routing/fast_lane_v2/instance.rs b/tig-algorithms/src/vehicle_routing/fast_lane_v2/instance.rs new file mode 100644 index 00000000..33a9d365 --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/fast_lane_v2/instance.rs @@ -0,0 +1,20 @@ +pub struct Instance { + pub seed: [u8; 32], + pub nb_nodes: usize, + pub nb_vehicles: usize, + pub lb_vehicles: usize, + pub demands: Vec, + pub max_capacity: i32, + pub distance_matrix: Vec>, + pub node_positions: Vec<(i32, i32)>, + pub service_times: Vec, + pub start_tw: Vec, + pub end_tw: Vec, +} + +impl Instance { + #[inline(always)] + pub fn dm(&self, i: usize, j: usize) -> i32 { + unsafe { *self.distance_matrix.get_unchecked(i).get_unchecked(j) } + } +} diff --git a/tig-algorithms/src/vehicle_routing/fast_lane_v2/mod.rs b/tig-algorithms/src/vehicle_routing/fast_lane_v2/mod.rs new file mode 100644 index 00000000..aeb5d087 --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/fast_lane_v2/mod.rs @@ -0,0 +1,54 @@ +mod instance; +mod config; +mod route_eval; +mod solution; +mod builder; +mod operators; +mod gene_pool; +mod evolution; +mod runner; + +pub use runner::Solver; + +use anyhow::Result; +use serde_json::{Map, Value}; +use tig_challenges::vehicle_routing::*; + +#[allow(dead_code)] +pub fn solve_challenge( + challenge: &Challenge, + save_solution: &dyn Fn(&Solution) -> Result<()>, + hyperparameters: &Option>, +) -> Result<()> { + match Solver::solve_challenge_instance(challenge, hyperparameters, Some(save_solution))? { + Some(solution) => { + let _ = save_solution(&solution); + Ok(()) + } + None => Ok(()), + } +} + +pub fn help() { + println!("Fast Lane v2: Hybrid Genetic Algorithm with Route-Based Crossover"); + println!(""); + println!("RECOMMENDED SETTINGS:"); + println!(""); + println!("For best quality: {{\"exploration_level\": 3}}"); + println!("For balanced quality: {{\"exploration_level\": 1}}"); + println!("For fastest runtime: {{\"exploration_level\": 0}} or null"); + println!(""); + println!("EXPLORATION LEVELS (0-6):"); + println!(" 0: Minimal iterations, fastest (~40s total)"); + println!(" 1: More initial diversity, slightly slower"); + println!(" 2: Light exploration (50 iterations)"); + println!(" 3: Balanced (500 iterations, recommended)"); + println!(" 4: Deep search (5,000 iterations)"); + println!(" 5: Very deep (50,000 iterations)"); + println!(" 6: Maximum quality (200,000 iterations)"); + println!(""); + println!("KEY ALGORITHMIC IMPROVEMENTS:"); + println!(" • Route-Based Crossover (RBX): Preserves good route structures"); + println!(" • Or-Opt moves: Advanced local search with 2-3 block relocations"); + println!(" • Diversity boosting: Extra randomized initial solutions"); +} diff --git a/tig-algorithms/src/vehicle_routing/fast_lane_v2/operators.rs b/tig-algorithms/src/vehicle_routing/fast_lane_v2/operators.rs new file mode 100644 index 00000000..0f2148cf --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/fast_lane_v2/operators.rs @@ -0,0 +1,902 @@ +use super::instance::Instance; +use super::config::Config; +use super::route_eval::RouteEval; +use rand::rngs::SmallRng; +use rand::Rng; +use rand::seq::SliceRandom; +use std::cmp::{max, min}; + +#[derive(Clone, Default)] +pub struct Node { + id: usize, + seq0_i: RouteEval, + seqi_n: RouteEval, + seq1: RouteEval, + seq12: RouteEval, + seq21: RouteEval, + seq123: RouteEval, +} + +impl Node { + #[inline] + fn new(id: usize) -> Self { + Self { id, ..Default::default() } + } +} + +#[derive(Clone, Default)] +pub struct Route { + cost: i64, + distance: i32, + load: i32, + tw: i32, + nodes: Vec, +} + +impl Route { + #[inline] + fn new(ids: &[usize]) -> Self { + Self { + nodes: ids.iter().copied().map(Node::new).collect(), + ..Default::default() + } + } +} + +pub struct LocalOps<'a> { + pub data: &'a Instance, + pub neighbors_before: Vec>, + pub neighbors_capacity_swap: Vec>, + pub params: Config, + pub cost: i64, + pub routes: Vec, + pub node_route: Vec, + pub node_pos: Vec, + pub empty_routes: Vec, + pub when_last_modified: Vec, + pub when_last_tested: Vec, + pub nb_moves: usize, +} + +impl<'a> LocalOps<'a> { + pub fn new(data: &'a Instance, params: Config) -> Self { + let n = data.nb_nodes; + let cap = n.saturating_sub(2); + let keep = min(params.granularity as usize, cap); + let mut neighbors_before: Vec> = vec![Vec::new(); n]; + + for i in 1..n { + let mut prox: Vec<(i32, usize)> = Vec::with_capacity(cap); + for j in 1..n { + if j == i { + continue; + } + let tji = data.dm(j, i); + let wait = (data.start_tw[i] - tji - data.service_times[j] - data.end_tw[j]).max(0); + let late = (data.start_tw[j] + data.service_times[j] + tji - data.end_tw[i]).max(0); + let proxy10 = 10 * tji + 2 * wait + 10 * late; + prox.push((proxy10, j)); + } + prox.sort_by_key(|&(p, _)| p); + neighbors_before[i] = prox[..keep].iter().map(|&(_, j)| j).collect(); + } + + let mut neighbors_capacity_swap: Vec> = vec![Vec::new(); n]; + let diff_limit = max(4, data.max_capacity / 20); + for i in 1..n { + let di = data.demands[i]; + let mut prox: Vec<(i32, usize)> = Vec::with_capacity(n.saturating_sub(1)); + for j in 1..n { + if j == i { + continue; + } + if (data.demands[j] - di).abs() <= diff_limit { + let dij = data.dm(i, j); + prox.push((dij, j)); + } + } + prox.sort_by_key(|&(d, _)| d); + let m = prox.len().min(params.granularity2 as usize); + neighbors_capacity_swap[i] = prox[..m].iter().map(|&(_, j)| j).collect(); + } + + Self { + data, + neighbors_before, + neighbors_capacity_swap, + params, + cost: 0, + routes: Vec::new(), + node_route: Vec::new(), + node_pos: Vec::new(), + empty_routes: Vec::new(), + when_last_modified: Vec::new(), + when_last_tested: vec![0; n], + nb_moves: 0, + } + } + + fn load_from_routes(&mut self, routes: &Vec>) { + let n = self.data.nb_nodes; + let fleet = self.data.nb_vehicles; + + let mut src: Vec> = Vec::new(); + + if routes.len() <= fleet { + src.extend(routes.iter().cloned()); + } else { + let keep = fleet.saturating_sub(1); + src.extend(routes.iter().take(keep).cloned()); + let mut merged = routes[keep].clone(); + merged.pop(); + for r in routes.iter().skip(fleet) { + if r.len() > 2 { + merged.extend_from_slice(&r[1..r.len() - 1]); + } + } + merged.push(0); + src.push(merged); + } + + while src.len() < fleet { + src.push(vec![0, 0]); + } + + let all_routes: Vec = src.iter().map(|r| Route::new(r)).collect(); + self.node_route = vec![0; n]; + self.node_pos = vec![0; n]; + self.empty_routes.clear(); + self.routes = all_routes; + + self.when_last_modified = vec![0; self.routes.len()]; + self.when_last_tested = vec![0; n]; + self.nb_moves = 1; + + for rid in 0..self.routes.len() { + self.update_route(rid); + } + self.cost = self.routes.iter().map(|r| r.cost).sum(); + } + + fn write_back_to_routes(&self, out: &mut Vec>) { + out.clear(); + out.extend( + self.routes + .iter() + .filter(|r| r.nodes.len() > 2) + .map(|r| r.nodes.iter().map(|n| n.id).collect::>()), + ); + } + + fn update_route(&mut self, rid: usize) { + let data = self.data; + let r = &mut self.routes[rid]; + let len = r.nodes.len(); + + let mut acc_fwd = RouteEval::singleton(data, r.nodes[0].id); + r.nodes[0].seq0_i = acc_fwd; + for pos in 1..len { + let id = r.nodes[pos].id; + acc_fwd = RouteEval::join2(data, &acc_fwd, &RouteEval::singleton(data, id)); + r.nodes[pos].seq0_i = acc_fwd; + } + + let mut acc_bwd = RouteEval::singleton(data, r.nodes[len - 1].id); + r.nodes[len - 1].seqi_n = acc_bwd; + for pos in (0..len - 1).rev() { + let id = r.nodes[pos].id; + acc_bwd = RouteEval::join2(data, &RouteEval::singleton(data, id), &acc_bwd); + r.nodes[pos].seqi_n = acc_bwd; + } + + for pos in 0..len { + let id = r.nodes[pos].id; + r.nodes[pos].seq1 = RouteEval::singleton(data, id); + if pos + 1 < len { + let id_next = r.nodes[pos + 1].id; + r.nodes[pos].seq12 = + RouteEval::join2(data, &RouteEval::singleton(data, id), &RouteEval::singleton(data, id_next)); + r.nodes[pos].seq21 = + RouteEval::join2(data, &RouteEval::singleton(data, id_next), &RouteEval::singleton(data, id)); + if pos + 2 < len { + let id_next2 = r.nodes[pos + 2].id; + r.nodes[pos].seq123 = RouteEval::join2(data, &r.nodes[pos].seq12, &RouteEval::singleton(data, id_next2)); + } + } + } + + let end = r.nodes[len - 1].seq0_i; + r.load = end.load; + r.tw = end.tw; + r.distance = end.distance; + r.cost = end.eval(data, &self.params); + + for (pos, node) in self.routes[rid].nodes.iter().enumerate() { + self.node_route[node.id] = rid; + self.node_pos[node.id] = pos; + } + + let is_empty = self.routes[rid].nodes.len() == 2; + let pos = self.empty_routes.iter().position(|&eid| eid == rid); + match (is_empty, pos) { + (true, None) => self.empty_routes.push(rid), + (false, Some(i)) => { + self.empty_routes.swap_remove(i); + } + _ => {} + } + self.when_last_modified[rid] = self.nb_moves; + } + + pub fn run_intra_route_relocate(&mut self, r1: usize, pos1: usize) -> bool { + let route = &self.routes[r1]; + let len = route.nodes.len(); + if len < pos1 + 4 { + return false; + } + + let mut left_excl: Vec = vec![RouteEval::default(); len]; + let mut acc_left = route.nodes[0].seq0_i; + for p in 1..len { + left_excl[p] = acc_left; + if p != pos1 { + acc_left = RouteEval::join2(self.data, &acc_left, &route.nodes[p].seq1); + } + } + + let mut right_excl: Vec = vec![RouteEval::default(); len]; + let mut acc_right = route.nodes[len - 1].seq1; + right_excl[len - 1] = acc_right; + for p in (1..len - 1).rev() { + if p != pos1 { + acc_right = RouteEval::join2(self.data, &route.nodes[p].seq1, &acc_right); + } + right_excl[p] = acc_right; + } + + let old_cost = route.cost; + let mut best_cost = old_cost; + let mut best_pos: Option = None; + + for t in 1..len { + if t == pos1 || t == pos1 + 1 { + continue; + } + let new_cost = RouteEval::eval3(self.data, &self.params, &left_excl[t], &route.nodes[pos1].seq1, &right_excl[t]); + if new_cost < best_cost { + best_cost = new_cost; + best_pos = Some(t); + } + } + + if let Some(mypos) = best_pos { + let insert_pos = if mypos > pos1 { mypos - 1 } else { mypos }; + let elem = self.routes[r1].nodes.remove(pos1); + self.routes[r1].nodes.insert(insert_pos, elem); + self.nb_moves += 1; + self.update_route(r1); + self.cost += self.routes[r1].cost - old_cost; + true + } else { + false + } + } + + pub fn run_intra_route_swap_right(&mut self, r1: usize, pos1: usize) -> bool { + let route = &self.routes[r1]; + let len = route.nodes.len(); + if len < pos1 + 4 { + return false; + } + + let old_cost = route.cost; + let mut best_cost = old_cost; + let mut best_pos: Option = None; + + let mut acc_mid = route.nodes[pos1 + 1].seq1; + for pos2 in (pos1 + 2)..(len - 1) { + let new_cost = RouteEval::eval_n( + self.data, + &self.params, + &[ + route.nodes[pos1 - 1].seq0_i, + route.nodes[pos2].seq1, + acc_mid, + route.nodes[pos1].seq1, + route.nodes[pos2 + 1].seqi_n, + ], + ); + if new_cost < best_cost { + best_cost = new_cost; + best_pos = Some(pos2); + } + acc_mid = RouteEval::join2(self.data, &acc_mid, &route.nodes[pos2].seq1); + } + + if let Some(mypos) = best_pos { + self.routes[r1].nodes.swap(pos1, mypos); + self.nb_moves += 1; + self.update_route(r1); + self.cost += self.routes[r1].cost - old_cost; + true + } else { + false + } + } + + pub fn run_2optstar(&mut self, r1: usize, pos1: usize, r2: usize, pos2: usize) -> bool { + let route1 = &self.routes[r1]; + let route2 = &self.routes[r2]; + + let new1 = RouteEval::eval2(self.data, &self.params, &route1.nodes[pos1 - 1].seq0_i, &route2.nodes[pos2].seqi_n); + let new2 = RouteEval::eval2(self.data, &self.params, &route2.nodes[pos2 - 1].seq0_i, &route1.nodes[pos1].seqi_n); + + let old_cost = route1.cost + route2.cost; + let new_cost = new1 + new2; + + if new_cost < old_cost { + let mut suffix1 = self.routes[r1].nodes.split_off(pos1); + let mut suffix2 = self.routes[r2].nodes.split_off(pos2); + self.routes[r1].nodes.append(&mut suffix2); + self.routes[r2].nodes.append(&mut suffix1); + self.nb_moves += 1; + self.update_route(r1); + self.update_route(r2); + self.cost += new_cost - old_cost; + true + } else { + false + } + } + + pub fn run_2opt(&mut self, r1: usize, pos1: usize) -> bool { + let route = &self.routes[r1]; + let len = route.nodes.len(); + if len < pos1 + 3 { + return false; + } + + let old_cost = route.cost; + let mut best_cost = old_cost; + let mut best_pos: Option = None; + + let mut mid_rev = route.nodes[pos1].seq21; + for pos2 in (pos1 + 1)..(len - 1) { + let new_cost = RouteEval::eval3( + self.data, + &self.params, + &route.nodes[pos1 - 1].seq0_i, + &mid_rev, + &route.nodes[pos2 + 1].seqi_n, + ); + if new_cost < best_cost { + best_cost = new_cost; + best_pos = Some(pos2); + } + if pos2 + 1 < len - 1 { + mid_rev = RouteEval::join2(self.data, &route.nodes[pos2 + 1].seq1, &mid_rev); + } + } + + if let Some(mypos) = best_pos { + self.routes[r1].nodes[pos1..=mypos].reverse(); + self.nb_moves += 1; + self.update_route(r1); + self.cost += self.routes[r1].cost - old_cost; + true + } else { + false + } + } + + pub fn run_intra_route_oropt(&mut self, r1: usize, pos1: usize, l: usize) -> bool { + if l < 2 || l > 3 { + return false; + } + let old_cost = self.routes[r1].cost; + + let applied = { + let route = &self.routes[r1]; + let len = route.nodes.len(); + if pos1 == 0 || pos1 >= len - 1 { + return false; + } + if pos1 + l >= len { + return false; + } + if l == 3 && pos1 + 2 >= len { + return false; + } + + let block_seq = if l == 2 { route.nodes[pos1].seq12 } else { route.nodes[pos1].seq123 }; + + let mut best_cost = old_cost; + let mut best_dir = 0i32; + let mut best_t = 0usize; + + let suffix_start = pos1 + l; + let suffix_fixed = route.nodes[suffix_start].seqi_n; + + if pos1 > 1 { + let mut mid_seq = route.nodes[pos1 - 1].seq1; + for t in (1..pos1).rev() { + let prefix_seq = route.nodes[t - 1].seq0_i; + let cand = RouteEval::eval_n(self.data, &self.params, &[prefix_seq, block_seq, mid_seq, suffix_fixed]); + if cand < best_cost { + best_cost = cand; + best_dir = -1; + best_t = t; + } + if t > 1 { + mid_seq = RouteEval::join2(self.data, &route.nodes[t - 1].seq1, &mid_seq); + } + } + } + + if pos1 + l < len - 1 { + let prefix_seq = route.nodes[pos1 - 1].seq0_i; + let mut mid_seq = route.nodes[pos1 + l].seq1; + for t in (pos1 + l + 1)..len { + let suffix_seq = route.nodes[t].seqi_n; + let cand = RouteEval::eval_n(self.data, &self.params, &[prefix_seq, mid_seq, block_seq, suffix_seq]); + if cand < best_cost { + best_cost = cand; + best_dir = 1; + best_t = t; + } + if t < len - 1 { + mid_seq = RouteEval::join2(self.data, &mid_seq, &route.nodes[t].seq1); + } + } + } + + if best_dir == 0 { + return false; + } + + let insert_pos = if best_dir > 0 { best_t - l } else { best_t }; + let mut blk: Vec = Vec::with_capacity(l); + for _ in 0..l { + blk.push(self.routes[r1].nodes.remove(pos1)); + } + for (k, node) in blk.into_iter().enumerate() { + self.routes[r1].nodes.insert(insert_pos + k, node); + } + + true + }; + + if !applied { + return false; + } + + self.nb_moves += 1; + self.update_route(r1); + self.cost += self.routes[r1].cost - old_cost; + true + } + + pub fn run_inter_route(&mut self, r1: usize, pos1: usize, r2: usize, pos2: usize) -> bool { + let data = self.data; + let ru = &self.routes[r1]; + let rv = &self.routes[r2]; + let u = &ru.nodes[pos1]; + let v = &rv.nodes[pos2]; + let u_pred = &ru.nodes[pos1 - 1]; + let v_pred = &rv.nodes[pos2 - 1]; + let x = &ru.nodes[pos1 + 1]; + + let old_total = ru.cost + rv.cost; + let mut best_i = 0usize; + let mut best_j = 0usize; + let mut best_cost = old_total; + + let mut update_best = |i: usize, j: usize, cand: i64| { + if cand < best_cost { + best_cost = cand; + best_i = i; + best_j = j; + } + }; + + let result10 = RouteEval::eval2(data, &self.params, &u_pred.seq0_i, &x.seqi_n) + + RouteEval::eval3(data, &self.params, &v_pred.seq0_i, &u.seq1, &v.seqi_n); + update_best(1, 0, result10); + + if v.id != 0 { + let result11 = RouteEval::eval3(data, &self.params, &u_pred.seq0_i, &v.seq1, &x.seqi_n) + + RouteEval::eval3(data, &self.params, &v_pred.seq0_i, &u.seq1, &rv.nodes[pos2 + 1].seqi_n); + update_best(1, 1, result11); + } + + if x.id != 0 { + let x_next = &ru.nodes[pos1 + 2]; + let mut result20 = RouteEval::eval2(data, &self.params, &u_pred.seq0_i, &x_next.seqi_n); + let mut result30 = result20; + result20 += RouteEval::eval3(data, &self.params, &v_pred.seq0_i, &u.seq12, &v.seqi_n); + result30 += RouteEval::eval3(data, &self.params, &v_pred.seq0_i, &u.seq21, &v.seqi_n); + update_best(2, 0, result20); + update_best(3, 0, result30); + + if v.id != 0 { + let y = &rv.nodes[pos2 + 1]; + let mut result21 = RouteEval::eval3(data, &self.params, &u_pred.seq0_i, &v.seq1, &x_next.seqi_n); + let mut result31 = result21; + result21 += RouteEval::eval3(data, &self.params, &v_pred.seq0_i, &u.seq12, &y.seqi_n); + result31 += RouteEval::eval3(data, &self.params, &v_pred.seq0_i, &u.seq21, &y.seqi_n); + update_best(2, 1, result21); + update_best(3, 1, result31); + + if y.id != 0 { + let mut result22 = RouteEval::eval3(data, &self.params, &u_pred.seq0_i, &v.seq12, &x_next.seqi_n); + let mut result23 = RouteEval::eval3(data, &self.params, &u_pred.seq0_i, &v.seq21, &x_next.seqi_n); + let mut result32 = result22; + let mut result33 = result23; + + let y_next = &rv.nodes[pos2 + 2]; + let tmp = RouteEval::eval3(data, &self.params, &v_pred.seq0_i, &u.seq12, &y_next.seqi_n); + let tmp2 = RouteEval::eval3(data, &self.params, &v_pred.seq0_i, &u.seq21, &y_next.seqi_n); + result22 += tmp; + result23 += tmp; + result32 += tmp2; + result33 += tmp2; + update_best(2, 2, result22); + update_best(3, 2, result32); + update_best(2, 3, result23); + update_best(3, 3, result33); + } + } + + if x_next.id != 0 && self.params.allow_swap3 { + let x2_next = &ru.nodes[pos1 + 3]; + let result40 = RouteEval::eval2(data, &self.params, &u_pred.seq0_i, &x2_next.seqi_n) + + RouteEval::eval3(data, &self.params, &v_pred.seq0_i, &u.seq123, &v.seqi_n); + update_best(4, 0, result40); + + if v.id != 0 { + let y = &rv.nodes[pos2 + 1]; + let result41 = RouteEval::eval3(data, &self.params, &u_pred.seq0_i, &v.seq1, &x2_next.seqi_n) + + RouteEval::eval3(data, &self.params, &v_pred.seq0_i, &u.seq123, &y.seqi_n); + update_best(4, 1, result41); + + if y.id != 0 { + let y_next = &rv.nodes[pos2 + 2]; + let result42 = RouteEval::eval3(data, &self.params, &u_pred.seq0_i, &v.seq12, &x2_next.seqi_n) + + RouteEval::eval3(data, &self.params, &v_pred.seq0_i, &u.seq123, &y_next.seqi_n); + let result43 = RouteEval::eval3(data, &self.params, &u_pred.seq0_i, &v.seq21, &x2_next.seqi_n) + + RouteEval::eval3(data, &self.params, &v_pred.seq0_i, &u.seq123, &y_next.seqi_n); + update_best(4, 2, result42); + update_best(4, 3, result43); + + if y_next.id != 0 { + let y2_next = &rv.nodes[pos2 + 3]; + let result44 = RouteEval::eval3(data, &self.params, &u_pred.seq0_i, &v.seq123, &x2_next.seqi_n) + + RouteEval::eval3(data, &self.params, &v_pred.seq0_i, &u.seq123, &y2_next.seqi_n); + update_best(4, 4, result44); + } + } + } + } + } + + if best_i == 0 && best_j == 0 { + return false; + } + + let mut take_block = |route_idx: usize, pos: usize, kind: usize| -> Vec { + let nodes = &mut self.routes[route_idx].nodes; + match kind { + 0 => vec![], + 1 => { + let n1 = nodes.remove(pos); + vec![n1] + } + 2 => { + let n1 = nodes.remove(pos); + let n2 = nodes.remove(pos); + vec![n1, n2] + } + 3 => { + let n1 = nodes.remove(pos); + let n2 = nodes.remove(pos); + vec![n2, n1] + } + 4 => { + let n1 = nodes.remove(pos); + let n2 = nodes.remove(pos); + let n3 = nodes.remove(pos); + vec![n1, n2, n3] + } + _ => vec![], + } + }; + + let blk_from_r1 = take_block(r1, pos1, best_i); + let blk_from_r2 = take_block(r2, pos2, best_j); + + let nodes1 = &mut self.routes[r1].nodes; + for (k, node) in blk_from_r2.into_iter().enumerate() { + nodes1.insert(pos1 + k, node); + } + let nodes2 = &mut self.routes[r2].nodes; + for (k, node) in blk_from_r1.into_iter().enumerate() { + nodes2.insert(pos2 + k, node); + } + + self.nb_moves += 1; + self.update_route(r1); + self.update_route(r2); + + let new_total = self.routes[r1].cost + self.routes[r2].cost; + self.cost += new_total - old_total; + true + } + + pub fn run_swapstar(&mut self, r1: usize, pos1: usize, r2: usize, pos2: usize) -> bool { + let route1_len = self.routes[r1].nodes.len(); + let route2_len = self.routes[r2].nodes.len(); + let u = self.routes[r1].nodes[pos1].id; + let v = self.routes[r2].nodes[pos2].id; + let (pu, nu) = (self.routes[r1].nodes[pos1 - 1].id, self.routes[r1].nodes[pos1 + 1].id); + let (pv, nv) = (self.routes[r2].nodes[pos2 - 1].id, self.routes[r2].nodes[pos2 + 1].id); + + let dr1 = self.data.dm(pu, nu) - self.data.dm(pu, u) - self.data.dm(u, nu); + let dr2 = self.data.dm(pv, nv) - self.data.dm(pv, v) - self.data.dm(v, nv); + let delta_demand = self.data.demands[v] - self.data.demands[u]; + let new_load1 = self.routes[r1].load + delta_demand; + let new_load2 = self.routes[r2].load - delta_demand; + let new_pen1 = ((new_load1 - self.data.max_capacity).max(0) as i64) * self.params.penalty_capa as i64; + let new_pen2 = ((new_load2 - self.data.max_capacity).max(0) as i64) * self.params.penalty_capa as i64; + let cost_lb_r1_after_removal = (self.routes[r1].distance + dr1) as i64 + new_pen1; + let cost_lb_r2_after_removal = (self.routes[r2].distance + dr2) as i64 + new_pen2; + let mut lb_new_total = cost_lb_r1_after_removal + cost_lb_r2_after_removal; + let old_total = self.routes[r1].cost + self.routes[r2].cost; + if lb_new_total > old_total { + return false; + } + + let hole_v = self.data.dm(pu, v) + self.data.dm(v, nu) - self.data.dm(pu, nu); + let mut best_ins_v = hole_v; + for t in 1..route1_len { + let a_id = self.routes[r1].nodes[t - 1].id; + let b_id = self.routes[r1].nodes[t].id; + if a_id == u || b_id == u { + continue; + } + let delta = self.data.dm(a_id, v) + self.data.dm(v, b_id) - self.data.dm(a_id, b_id); + if delta < best_ins_v { + best_ins_v = delta; + } + } + + let hole_u = self.data.dm(pv, u) + self.data.dm(u, nv) - self.data.dm(pv, nv); + let mut best_ins_u = hole_u; + for t in 1..route2_len { + let a_id = self.routes[r2].nodes[t - 1].id; + let b_id = self.routes[r2].nodes[t].id; + if a_id == v || b_id == v { + continue; + } + let delta = self.data.dm(a_id, u) + self.data.dm(u, b_id) - self.data.dm(a_id, b_id); + if delta < best_ins_u { + best_ins_u = delta; + } + } + + lb_new_total += (best_ins_v + best_ins_u) as i64; + if lb_new_total > old_total { + return false; + } + + let mut left_excl1: Vec = vec![RouteEval::default(); route1_len]; + let mut right_excl1: Vec = vec![RouteEval::default(); route1_len]; + { + let r = &self.routes[r1]; + let mut acc_left = r.nodes[0].seq0_i; + for p in 1..route1_len { + left_excl1[p] = acc_left; + if p != pos1 { + acc_left = RouteEval::join2(self.data, &acc_left, &r.nodes[p].seq1); + } + } + let mut acc_right = r.nodes[route1_len - 1].seq1; + right_excl1[route1_len - 1] = acc_right; + for p in (1..route1_len - 1).rev() { + if p != pos1 { + acc_right = RouteEval::join2(self.data, &r.nodes[p].seq1, &acc_right); + } + right_excl1[p] = acc_right; + } + } + + let mut left_excl2: Vec = vec![RouteEval::default(); route2_len]; + let mut right_excl2: Vec = vec![RouteEval::default(); route2_len]; + { + let r = &self.routes[r2]; + let mut acc_left = r.nodes[0].seq0_i; + for p in 1..route2_len { + left_excl2[p] = acc_left; + if p != pos2 { + acc_left = RouteEval::join2(self.data, &acc_left, &r.nodes[p].seq1); + } + } + let mut acc_right = r.nodes[route2_len - 1].seq1; + right_excl2[route2_len - 1] = acc_right; + for p in (1..route2_len - 1).rev() { + if p != pos2 { + acc_right = RouteEval::join2(self.data, &r.nodes[p].seq1, &acc_right); + } + right_excl2[p] = acc_right; + } + } + + let v_seq1 = self.routes[r2].nodes[pos2].seq1; + let mut best_cost1 = i64::MAX / 4; + let mut best_t1: usize = 1; + for t in 1..route1_len { + let cand = RouteEval::eval3(self.data, &self.params, &left_excl1[t], &v_seq1, &right_excl1[t]); + if cand < best_cost1 { + best_cost1 = cand; + best_t1 = t; + } + } + + let u_seq1 = self.routes[r1].nodes[pos1].seq1; + let mut best_cost2 = i64::MAX / 4; + let mut best_t2: usize = 1; + for t in 1..route2_len { + let cand = RouteEval::eval3(self.data, &self.params, &left_excl2[t], &u_seq1, &right_excl2[t]); + if cand < best_cost2 { + best_cost2 = cand; + best_t2 = t; + } + } + + if best_cost1 + best_cost2 >= old_total { + return false; + } + + let node_u = self.routes[r1].nodes[pos1].clone(); + let node_v = self.routes[r2].nodes[pos2].clone(); + + self.routes[r1].nodes.remove(pos1); + self.routes[r2].nodes.remove(pos2); + + let ins1 = if best_t1 > pos1 { best_t1 - 1 } else { best_t1 }; + let ins2 = if best_t2 > pos2 { best_t2 - 1 } else { best_t2 }; + + self.routes[r1].nodes.insert(ins1, node_v); + self.routes[r2].nodes.insert(ins2, node_u); + + self.nb_moves += 1; + self.update_route(r1); + self.update_route(r2); + let new_total = self.routes[r1].cost + self.routes[r2].cost; + self.cost += new_total - old_total; + true + } + + pub fn runls( + &mut self, + routes: &mut Vec>, + rng: &mut SmallRng, + params: &Config, + is_repair: bool, + factor: usize, + ) { + self.params = *params; + + if !is_repair { + self.load_from_routes(routes); + } else { + self.params.penalty_tw = (factor * self.params.penalty_tw).min(10_000); + self.params.penalty_capa = (factor * self.params.penalty_capa).min(10_000); + self.nb_moves += 1; + for rid in 0..self.routes.len() { + let r = &self.routes[rid]; + if r.load > self.data.max_capacity || r.tw > 0 { + self.update_route(rid); + } + } + } + + let mut improved = true; + let mut loop_id = 0; + let mut c1_order: Vec = (1..self.data.nb_nodes).collect(); + while improved { + improved = false; + loop_id += 1; + c1_order.shuffle(rng); + for &c1 in &c1_order { + let last_tested = self.when_last_tested[c1]; + self.when_last_tested[c1] = self.nb_moves; + let r1 = self.node_route[c1]; + let pos1 = self.node_pos[c1]; + + let neigh_len = self.neighbors_before[c1].len(); + let start = rng.gen_range(0..neigh_len); + for off in 0..neigh_len { + let c2 = self.neighbors_before[c1][(start + off) % neigh_len]; + let r2 = self.node_route[c2]; + let pos2 = self.node_pos[c2]; + if r1 == r2 { + continue; + } + + if self.when_last_modified[r1].max(self.when_last_modified[r2]) <= last_tested { + continue; + } + + if self.run_inter_route(r1, pos1, r2, pos2 + 1) { + improved = true; + break; + } + + if pos1 == 1 && self.run_inter_route(r2, pos2, r1, pos1) { + improved = true; + break; + } + + if self.run_2optstar(r1, pos1, r2, pos2 + 1) { + improved = true; + break; + } + } + + let r1 = self.node_route[c1]; + let pos1 = self.node_pos[c1]; + let swap_len = self.neighbors_capacity_swap[c1].len(); + if swap_len > 0 { + let start_s = rng.gen_range(0..swap_len); + for off in 0..swap_len { + let c2 = self.neighbors_capacity_swap[c1][(start_s + off) % swap_len]; + let r2 = self.node_route[c2]; + if r1 == r2 { + continue; + } + + if c1 < c2 || self.when_last_modified[r1].max(self.when_last_modified[r2]) <= last_tested { + continue; + } + + let pos2 = self.node_pos[c2]; + if self.run_swapstar(r1, pos1, r2, pos2) { + improved = true; + break; + } + } + } + + let r1 = self.node_route[c1]; + let pos1 = self.node_pos[c1]; + if loop_id > 1 && (loop_id == 2 || self.when_last_modified[r1] > last_tested) { + if let Some(&r2) = self.empty_routes.first() { + let pos2 = 1; + + if self.run_2optstar(r1, pos1, r2, pos2) { + improved = true; + break; + } + + if self.run_inter_route(r1, pos1, r2, pos2) { + improved = true; + break; + } + } + } + + let r1 = self.node_route[c1]; + if self.when_last_modified[r1] > last_tested { + improved |= self.run_intra_route_relocate(self.node_route[c1], self.node_pos[c1]); + improved |= self.run_intra_route_swap_right(self.node_route[c1], self.node_pos[c1]); + improved |= self.run_2opt(self.node_route[c1], self.node_pos[c1]); + improved |= self.run_intra_route_oropt(self.node_route[c1], self.node_pos[c1], 2); + if self.params.allow_swap3 { + improved |= self.run_intra_route_oropt(self.node_route[c1], self.node_pos[c1], 3); + } + } + } + } + self.write_back_to_routes(routes); + } +} diff --git a/tig-algorithms/src/vehicle_routing/fast_lane_v2/route_eval.rs b/tig-algorithms/src/vehicle_routing/fast_lane_v2/route_eval.rs new file mode 100644 index 00000000..7d6db6db --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/fast_lane_v2/route_eval.rs @@ -0,0 +1,126 @@ +use super::instance::Instance; +use super::config::Config; +use std::cmp::{max, min}; + +#[derive(Copy, Clone, Default)] +pub struct RouteEval { + pub tau_minus: i32, + pub tau_plus: i32, + pub tmin: i32, + pub tw: i32, + pub total_service_duration: i32, + pub load: i32, + pub distance: i32, + pub first_node: usize, + pub last_node: usize, +} + +impl RouteEval { + #[inline(always)] + pub fn initialize(&mut self, data: &Instance, node: usize) { + let st = data.start_tw[node]; + let et = data.end_tw[node]; + let svc = data.service_times[node]; + let ld = data.demands[node]; + self.tau_minus = st; + self.tau_plus = et; + self.tmin = svc; + self.tw = 0; + self.total_service_duration = svc; + self.load = ld; + self.distance = 0; + self.first_node = node; + self.last_node = node; + } + + #[inline(always)] + pub fn join2(data: &Instance, s1: &RouteEval, s2: &RouteEval) -> RouteEval { + let travel = data.dm(s1.last_node, s2.first_node); + let distance = s1.distance + s2.distance + travel; + let temp = travel + s1.tmin - s1.tw; + + let wtij = max(s2.tau_minus - temp - s1.tau_plus, 0); + let twij = max(temp + s1.tau_minus - s2.tau_plus, 0); + let tw = s1.tw + s2.tw + twij; + let tmin = temp + s1.tw + s2.tmin + wtij; + let tau_minus = max(s2.tau_minus - temp - wtij, s1.tau_minus); + let tau_plus = min(s2.tau_plus - temp + twij, s1.tau_plus); + let load = s1.load + s2.load; + + RouteEval { + tau_minus, + tau_plus, + tmin, + tw, + total_service_duration: s1.total_service_duration + s2.total_service_duration, + load, + distance, + first_node: s1.first_node, + last_node: s2.last_node, + } + } + + #[inline(always)] + pub fn singleton(data: &Instance, node: usize) -> RouteEval { + let mut s = RouteEval::default(); + s.initialize(data, node); + s + } + + #[inline(always)] + pub fn eval(&self, data: &Instance, params: &Config) -> i64 { + let ptw = params.penalty_tw as i64; + let pcap = params.penalty_capa as i64; + let load_excess = (self.load - data.max_capacity).max(0) as i64; + (self.distance as i64) + load_excess * pcap + (self.tw as i64) * ptw + } + + #[inline(always)] + pub fn eval2(data: &Instance, params: &Config, s1: &RouteEval, s2: &RouteEval) -> i64 { + let ptw = params.penalty_tw as i64; + let pcap = params.penalty_capa as i64; + let travel = data.dm(s1.last_node, s2.first_node); + let distance = s1.distance + s2.distance + travel; + let temp = s1.tmin - s1.tw + travel; + let tw_viol = s1.tw + s2.tw + max(s1.tau_minus - s2.tau_plus + temp, 0); + let load = s1.load + s2.load; + let load_excess = (load - data.max_capacity).max(0) as i64; + (distance as i64) + load_excess * pcap + (tw_viol as i64) * ptw + } + + #[inline(always)] + pub fn eval3(data: &Instance, params: &Config, s1: &RouteEval, s2: &RouteEval, s3: &RouteEval) -> i64 { + let ptw = params.penalty_tw as i64; + let pcap = params.penalty_capa as i64; + + let travel12 = data.dm(s1.last_node, s2.first_node); + let distance12 = s1.distance + s2.distance + travel12; + let temp = travel12 + s1.tmin - s1.tw; + + let wtij = max(s2.tau_minus - temp - s1.tau_plus, 0); + let twij = max(temp + s1.tau_minus - s2.tau_plus, 0); + let tw_viol12 = s1.tw + s2.tw + twij; + let tmin12 = temp + s1.tw + s2.tmin + wtij; + let tau_m12 = max(s2.tau_minus - temp - wtij, s1.tau_minus); + + let travel23 = data.dm(s2.last_node, s3.first_node); + let distance = distance12 + s3.distance + travel23; + let temp2 = travel23 + tmin12 - tw_viol12; + + let tw_viol = tw_viol12 + s3.tw + max(tau_m12 - s3.tau_plus + temp2, 0); + let load = s1.load + s2.load + s3.load; + + let load_excess = (load - data.max_capacity).max(0) as i64; + (distance as i64) + load_excess * pcap + (tw_viol as i64) * ptw + } + + #[inline(always)] + pub fn eval_n(data: &Instance, params: &Config, chain: &[RouteEval]) -> i64 { + let mut agg = chain[0]; + for s in &chain[1..chain.len() - 1] { + agg = RouteEval::join2(data, &agg, s); + } + let last = &chain[chain.len() - 1]; + RouteEval::eval2(data, params, &agg, last) + } +} diff --git a/tig-algorithms/src/vehicle_routing/fast_lane_v2/runner.rs b/tig-algorithms/src/vehicle_routing/fast_lane_v2/runner.rs new file mode 100644 index 00000000..53008538 --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/fast_lane_v2/runner.rs @@ -0,0 +1,70 @@ +use super::instance::Instance; +use super::config::Config; +use super::evolution::Evolution; +use anyhow::Result; +use tig_challenges::vehicle_routing::*; +use serde_json::{Map, Value}; +use rand::{rngs::SmallRng, SeedableRng}; +use std::time::Instant; + +pub struct TigLoader; + +impl TigLoader { + pub fn load(challenge: &Challenge) -> Instance { + let nb_nodes = challenge.num_nodes; + let nb_vehicles = challenge.fleet_size; + + let mut service_times = vec![challenge.service_time; nb_nodes]; + service_times[0] = 0; + + let total_demand: f64 = challenge.demands.iter().map(|&d| d as f64).sum(); + let ratio = total_demand / challenge.max_capacity as f64; + let lb_vehicles = ratio.ceil() as usize; + + Instance { + seed: challenge.seed, + nb_nodes, + nb_vehicles, + lb_vehicles, + demands: challenge.demands.clone(), + node_positions: challenge.node_positions.clone(), + max_capacity: challenge.max_capacity, + distance_matrix: challenge.distance_matrix.clone(), + service_times, + start_tw: challenge.ready_times.clone(), + end_tw: challenge.due_times.clone(), + } + } +} + +pub struct Solver; + +impl Solver { + fn solve( + data: Instance, + params: Config, + t0: &Instant, + save_solution: Option<&dyn Fn(&Solution) -> Result<()>>, + ) -> Result> { + let mut rng = SmallRng::from_seed(data.seed); + let mut ga = Evolution::new(&data, params); + Ok(ga.run(&mut rng, t0, save_solution).map(|(routes, cost)| { + (Solution { routes: routes.clone() }, cost, routes.len()) + })) + } + + pub fn solve_challenge_instance( + challenge: &Challenge, + hyperparameters: &Option>, + save_solution: Option<&dyn Fn(&Solution) -> Result<()>>, + ) -> Result> { + let t0 = Instant::now(); + let data = TigLoader::load(challenge); + let params = Config::initialize(hyperparameters, data.nb_nodes); + match Self::solve(data, params, &t0, save_solution) { + Ok(Some((solution, _cost, _routes))) => Ok(Some(solution)), + Ok(None) => Ok(None), + Err(_) => Ok(None), + } + } +} diff --git a/tig-algorithms/src/vehicle_routing/fast_lane_v2/solution.rs b/tig-algorithms/src/vehicle_routing/fast_lane_v2/solution.rs new file mode 100644 index 00000000..301dbccb --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/fast_lane_v2/solution.rs @@ -0,0 +1,88 @@ +use super::instance::Instance; +use super::config::Config; +use super::route_eval::RouteEval; + +#[derive(Clone)] +pub struct Individual { + pub routes: Vec>, + pub nb_routes: usize, + pub distance: i32, + pub tw_violation: i32, + pub load_excess: i32, + pub cost: i64, + pub pred: Vec, + pub succ: Vec, +} + +impl Individual { + pub fn new_from_routes(data: &Instance, params: &Config, routes: Vec>) -> Self { + let (distance, tw_violation, load_excess) = Self::evaluate_routes(data, &routes); + let cost = Self::compute_penalized_cost(distance, tw_violation, load_excess, params); + let (pred, succ, nb_routes) = Self::build_pred_succ_and_count(data, &routes); + Self { + routes, + nb_routes, + distance, + tw_violation, + load_excess, + cost, + pred, + succ, + } + } + + pub fn evaluate_routes(data: &Instance, routes: &Vec>) -> (i32, i32, i32) { + let mut dist: i32 = 0; + let mut tw: i32 = 0; + let mut loadx: i32 = 0; + for r in routes { + if r.is_empty() { + continue; + } + let mut acc = RouteEval::singleton(data, r[0]); + for idx in 1..r.len() { + let next = RouteEval::singleton(data, r[idx]); + acc = RouteEval::join2(data, &acc, &next); + } + dist += acc.distance; + tw += acc.tw; + let ex = (acc.load - data.max_capacity).max(0); + loadx += ex; + } + (dist, tw, loadx) + } + + #[inline] + pub fn compute_penalized_cost(distance: i32, tw_violation: i32, load_excess: i32, params: &Config) -> i64 { + (distance as i64) + + (params.penalty_tw as i64) * (tw_violation as i64) + + (params.penalty_capa as i64) * (load_excess as i64) + } + + #[inline] + pub fn recompute_cost(&mut self, params: &Config) { + self.cost = Self::compute_penalized_cost(self.distance, self.tw_violation, self.load_excess, params); + } + + fn build_pred_succ_and_count(data: &Instance, routes: &Vec>) -> (Vec, Vec, usize) { + let n_all = data.nb_nodes; + let mut pred = vec![0usize; n_all]; + let mut succ = vec![0usize; n_all]; + let mut nb_routes: usize = 0; + + for r in routes { + if r.len() > 2 { + nb_routes += 1; + } + if r.len() < 2 { + continue; + } + for p in 1..r.len() - 1 { + let id = r[p]; + pred[id] = r[p - 1]; + succ[id] = r[p + 1]; + } + } + (pred, succ, nb_routes) + } +} diff --git a/tig-algorithms/src/vehicle_routing/mod.rs b/tig-algorithms/src/vehicle_routing/mod.rs index 71c80897..6a39f35b 100644 --- a/tig-algorithms/src/vehicle_routing/mod.rs +++ b/tig-algorithms/src/vehicle_routing/mod.rs @@ -209,7 +209,8 @@ pub use fast_lane as c002_a092; // c002_a094 -// c002_a095 +pub mod fast_lane_v2; +pub use fast_lane_v2 as c002_a095; // c002_a096