mirror of
https://github.com/tig-foundation/tig-monorepo.git
synced 2026-02-21 02:17:48 +08:00
Merge remote-tracking branches 'public/hypergraph/freud_opt' and 'public/vehicle_routing/fast_lane_v2'
Some checks failed
Test Workspace / Test Workspace (push) Has been cancelled
Some checks failed
Test Workspace / Test Workspace (push) Has been cancelled
This commit is contained in:
commit
ceec60d0c5
BIN
tig-algorithms/lib/hypergraph/freud_opt.tar.gz
Normal file
BIN
tig-algorithms/lib/hypergraph/freud_opt.tar.gz
Normal file
Binary file not shown.
BIN
tig-algorithms/lib/vehicle_routing/fast_lane_v2.tar.gz
Normal file
BIN
tig-algorithms/lib/vehicle_routing/fast_lane_v2.tar.gz
Normal file
Binary file not shown.
23
tig-algorithms/src/hypergraph/freud_opt/README.md
Normal file
23
tig-algorithms/src/hypergraph/freud_opt/README.md
Normal file
@ -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
|
||||
483
tig-algorithms/src/hypergraph/freud_opt/freud_opt.cu
Normal file
483
tig-algorithms/src/hypergraph/freud_opt/freud_opt.cu
Normal file
@ -0,0 +1,483 @@
|
||||
#include <stdint.h>
|
||||
#include <cuda_runtime.h>
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
654
tig-algorithms/src/hypergraph/freud_opt/mod.rs
Normal file
654
tig-algorithms/src/hypergraph/freud_opt/mod.rs
Normal file
@ -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<Map<String, Value>>,
|
||||
module: Arc<CudaModule>,
|
||||
stream: Arc<CudaStream>,
|
||||
prop: &cudaDeviceProp,
|
||||
) -> anyhow::Result<()> {
|
||||
println!(">>> solve_challenge START");
|
||||
let total_start = Instant::now();
|
||||
|
||||
let dummy_partition: Vec<u32> = (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::<i32>(challenge.num_hyperedges as usize)?;
|
||||
let mut d_partition = stream.alloc_zeros::<i32>(challenge.num_nodes as usize)?;
|
||||
let mut d_nodes_in_part = stream.alloc_zeros::<i32>(challenge.num_parts as usize)?;
|
||||
|
||||
let mut d_pref_parts = stream.alloc_zeros::<i32>(challenge.num_nodes as usize)?;
|
||||
let mut d_pref_priorities = stream.alloc_zeros::<i32>(challenge.num_nodes as usize)?;
|
||||
|
||||
let mut d_move_parts = stream.alloc_zeros::<i32>(challenge.num_nodes as usize)?;
|
||||
let mut d_move_priorities = stream.alloc_zeros::<i32>(challenge.num_nodes as usize)?;
|
||||
|
||||
let buffer_size = (challenge.num_nodes as usize) * 1024;
|
||||
let mut d_global_edge_flags = stream.alloc_zeros::<u64>(buffer_size)?;
|
||||
|
||||
let mut d_edge_flags_all = stream.alloc_zeros::<u64>(challenge.num_hyperedges as usize)?;
|
||||
let mut d_edge_flags_double = stream.alloc_zeros::<u64>(challenge.num_hyperedges as usize)?;
|
||||
|
||||
let n = challenge.num_nodes as usize;
|
||||
let mut d_sort_keys_a = stream.alloc_zeros::<i32>(n)?;
|
||||
let mut d_sort_keys_b = stream.alloc_zeros::<i32>(n)?;
|
||||
let mut d_sort_vals_a = stream.alloc_zeros::<i32>(n)?;
|
||||
let mut d_sort_vals_b = stream.alloc_zeros::<i32>(n)?;
|
||||
let mut d_sorted_move_parts = stream.alloc_zeros::<i32>(n)?;
|
||||
|
||||
let num_chunks: i32 = ((n + 255) / 256) as i32;
|
||||
let mut d_chunk_histograms = stream.alloc_zeros::<i32>((num_chunks as usize) * 256)?;
|
||||
let mut d_chunk_offsets = stream.alloc_zeros::<i32>((num_chunks as usize) * 256)?;
|
||||
let mut d_ready_flag = stream.alloc_zeros::<i32>(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<i32> = Vec::with_capacity(n);
|
||||
let mut sorted_move_parts_cpu: Vec<i32> = Vec::with_capacity(n);
|
||||
let mut valid_indices: Vec<usize> = 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<usize> = (0..challenge.num_nodes as usize).collect();
|
||||
indices.sort_unstable_by(|&a, &b| pref_priorities[b].cmp(&pref_priorities[a]));
|
||||
|
||||
let sorted_nodes: Vec<i32> = indices.iter().map(|&i| i as i32).collect();
|
||||
let sorted_parts: Vec<i32> = 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<i32>, &cudarc::driver::CudaSlice<i32>);
|
||||
let d_sorted_nodes_tmp: cudarc::driver::CudaSlice<i32>;
|
||||
let d_sorted_parts_tmp: cudarc::driver::CudaSlice<i32>;
|
||||
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::<i32>(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<i32>;
|
||||
let d_sorted_parts_ref2: &cudarc::driver::CudaSlice<i32>;
|
||||
let d_sorted_nodes_tmp2: cudarc::driver::CudaSlice<i32>;
|
||||
let d_sorted_parts_tmp2: cudarc::driver::CudaSlice<i32>;
|
||||
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::<i32>(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<u32> = 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(())
|
||||
}
|
||||
@ -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
|
||||
|
||||
|
||||
23
tig-algorithms/src/vehicle_routing/fast_lane_v2/README.md
Normal file
23
tig-algorithms/src/vehicle_routing/fast_lane_v2/README.md
Normal file
@ -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
|
||||
112
tig-algorithms/src/vehicle_routing/fast_lane_v2/builder.rs
Normal file
112
tig-algorithms/src/vehicle_routing/fast_lane_v2/builder.rs
Normal file
@ -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<Vec<usize>> {
|
||||
let mut routes = Vec::new();
|
||||
let mut nodes: Vec<usize> = (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<usize>,
|
||||
nodes: &Vec<usize>,
|
||||
available: &Vec<bool>,
|
||||
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<usize>, 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
|
||||
}
|
||||
}
|
||||
208
tig-algorithms/src/vehicle_routing/fast_lane_v2/config.rs
Normal file
208
tig-algorithms/src/vehicle_routing/fast_lane_v2/config.rs
Normal file
@ -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<Map<String, Value>>, 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::<usize>() {
|
||||
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))
|
||||
}
|
||||
}
|
||||
350
tig-algorithms/src/vehicle_routing/fast_lane_v2/evolution.rs
Normal file
350
tig-algorithms/src/vehicle_routing/fast_lane_v2/evolution.rs
Normal file
@ -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<usize>> = 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<Vec<usize>> = 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<Vec<usize>>, i32)> {
|
||||
if let Some(save) = save_solution {
|
||||
let dummy_routes: Vec<Vec<usize>> = (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<usize>, 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<usize>, target_routes: usize) -> Vec<Vec<usize>> {
|
||||
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<usize>> = Vec::with_capacity(n);
|
||||
for &id in giant {
|
||||
routes.push(vec![0, id, 0]);
|
||||
}
|
||||
return routes;
|
||||
}
|
||||
|
||||
let mut routes: Vec<Vec<usize>> = 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<usize> = 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<usize>]) -> Vec<usize> {
|
||||
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<usize>, rng: &mut SmallRng) -> Vec<usize> {
|
||||
let n = self.data.nb_nodes - 1;
|
||||
if n == 0 {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let mut cand: Vec<usize> = 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<usize> = 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
|
||||
}
|
||||
}
|
||||
324
tig-algorithms/src/vehicle_routing/fast_lane_v2/gene_pool.rs
Normal file
324
tig-algorithms/src/vehicle_routing/fast_lane_v2/gene_pool.rs
Normal file
@ -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<Individual>,
|
||||
pub prox: Vec<Vec<(f64, usize)>>,
|
||||
pub biased_fitness: Vec<f64>,
|
||||
pub order_cost: Vec<usize>,
|
||||
}
|
||||
|
||||
pub struct GenePool<'a> {
|
||||
pub data: &'a Instance,
|
||||
pub feasible: Subpopulation,
|
||||
pub infeasible: Subpopulation,
|
||||
cap_window: VecDeque<bool>,
|
||||
tw_window: VecDeque<bool>,
|
||||
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<Individual> {
|
||||
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<f64> = 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
|
||||
}
|
||||
}
|
||||
}
|
||||
20
tig-algorithms/src/vehicle_routing/fast_lane_v2/instance.rs
Normal file
20
tig-algorithms/src/vehicle_routing/fast_lane_v2/instance.rs
Normal file
@ -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<i32>,
|
||||
pub max_capacity: i32,
|
||||
pub distance_matrix: Vec<Vec<i32>>,
|
||||
pub node_positions: Vec<(i32, i32)>,
|
||||
pub service_times: Vec<i32>,
|
||||
pub start_tw: Vec<i32>,
|
||||
pub end_tw: Vec<i32>,
|
||||
}
|
||||
|
||||
impl Instance {
|
||||
#[inline(always)]
|
||||
pub fn dm(&self, i: usize, j: usize) -> i32 {
|
||||
unsafe { *self.distance_matrix.get_unchecked(i).get_unchecked(j) }
|
||||
}
|
||||
}
|
||||
54
tig-algorithms/src/vehicle_routing/fast_lane_v2/mod.rs
Normal file
54
tig-algorithms/src/vehicle_routing/fast_lane_v2/mod.rs
Normal file
@ -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<Map<String, Value>>,
|
||||
) -> 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");
|
||||
}
|
||||
902
tig-algorithms/src/vehicle_routing/fast_lane_v2/operators.rs
Normal file
902
tig-algorithms/src/vehicle_routing/fast_lane_v2/operators.rs
Normal file
@ -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<Node>,
|
||||
}
|
||||
|
||||
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<Vec<usize>>,
|
||||
pub neighbors_capacity_swap: Vec<Vec<usize>>,
|
||||
pub params: Config,
|
||||
pub cost: i64,
|
||||
pub routes: Vec<Route>,
|
||||
pub node_route: Vec<usize>,
|
||||
pub node_pos: Vec<usize>,
|
||||
pub empty_routes: Vec<usize>,
|
||||
pub when_last_modified: Vec<usize>,
|
||||
pub when_last_tested: Vec<usize>,
|
||||
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<usize>> = 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<usize>> = 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<Vec<usize>>) {
|
||||
let n = self.data.nb_nodes;
|
||||
let fleet = self.data.nb_vehicles;
|
||||
|
||||
let mut src: Vec<Vec<usize>> = 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<Route> = 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<Vec<usize>>) {
|
||||
out.clear();
|
||||
out.extend(
|
||||
self.routes
|
||||
.iter()
|
||||
.filter(|r| r.nodes.len() > 2)
|
||||
.map(|r| r.nodes.iter().map(|n| n.id).collect::<Vec<usize>>()),
|
||||
);
|
||||
}
|
||||
|
||||
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<RouteEval> = 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<RouteEval> = 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<usize> = 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<usize> = 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<usize> = 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<Node> = 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<Node> {
|
||||
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<RouteEval> = vec![RouteEval::default(); route1_len];
|
||||
let mut right_excl1: Vec<RouteEval> = 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<RouteEval> = vec![RouteEval::default(); route2_len];
|
||||
let mut right_excl2: Vec<RouteEval> = 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<Vec<usize>>,
|
||||
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<usize> = (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);
|
||||
}
|
||||
}
|
||||
126
tig-algorithms/src/vehicle_routing/fast_lane_v2/route_eval.rs
Normal file
126
tig-algorithms/src/vehicle_routing/fast_lane_v2/route_eval.rs
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
70
tig-algorithms/src/vehicle_routing/fast_lane_v2/runner.rs
Normal file
70
tig-algorithms/src/vehicle_routing/fast_lane_v2/runner.rs
Normal file
@ -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<Option<(Solution, i32, usize)>> {
|
||||
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<Map<String, Value>>,
|
||||
save_solution: Option<&dyn Fn(&Solution) -> Result<()>>,
|
||||
) -> Result<Option<Solution>> {
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
88
tig-algorithms/src/vehicle_routing/fast_lane_v2/solution.rs
Normal file
88
tig-algorithms/src/vehicle_routing/fast_lane_v2/solution.rs
Normal file
@ -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<Vec<usize>>,
|
||||
pub nb_routes: usize,
|
||||
pub distance: i32,
|
||||
pub tw_violation: i32,
|
||||
pub load_excess: i32,
|
||||
pub cost: i64,
|
||||
pub pred: Vec<usize>,
|
||||
pub succ: Vec<usize>,
|
||||
}
|
||||
|
||||
impl Individual {
|
||||
pub fn new_from_routes(data: &Instance, params: &Config, routes: Vec<Vec<usize>>) -> 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<Vec<usize>>) -> (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<usize>>) -> (Vec<usize>, Vec<usize>, 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)
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user