Hypergraph 20k

Created Diff never expires
The two texts are identical
There is no difference to show between these two texts
0 removals
639 lines
0 additions
639 lines
use cudarc::{
use cudarc::{
driver::{safe::LaunchConfig, CudaModule, CudaStream, PushKernelArg},
driver::{safe::LaunchConfig, CudaModule, CudaStream, PushKernelArg},
runtime::sys::cudaDeviceProp,
runtime::sys::cudaDeviceProp,
};
};
use serde_json::{Map, Value};
use serde_json::{Map, Value};
use std::sync::Arc;
use std::sync::Arc;
use tig_challenges::hypergraph::*;
use tig_challenges::hypergraph::*;


pub fn solve(
pub fn solve(
challenge: &Challenge,
challenge: &Challenge,
save_solution: &dyn Fn(&Solution) -> anyhow::Result<()>,
save_solution: &dyn Fn(&Solution) -> anyhow::Result<()>,
hyperparameters: &Option<Map<String, Value>>,
hyperparameters: &Option<Map<String, Value>>,
module: Arc<CudaModule>,
module: Arc<CudaModule>,
stream: Arc<CudaStream>,
stream: Arc<CudaStream>,
prop: &cudaDeviceProp,
prop: &cudaDeviceProp,
) -> anyhow::Result<()> {
) -> anyhow::Result<()> {
let block_size = std::cmp::min(128, prop.maxThreadsPerBlock as u32);
let block_size = std::cmp::min(128, prop.maxThreadsPerBlock as u32);


let hyperedge_cluster_kernel = module.load_function("hyperedge_clustering_20k")?;
let hyperedge_cluster_kernel = module.load_function("hyperedge_clustering_20k")?;
let compute_preferences_kernel = module.load_function("compute_node_preferences_20k")?;
let compute_preferences_kernel = module.load_function("compute_node_preferences_20k")?;
let execute_assignments_kernel = module.load_function("execute_node_assignments_20k")?;
let execute_assignments_kernel = module.load_function("execute_node_assignments_20k")?;
let precompute_edge_flags_kernel = module.load_function("precompute_edge_flags_20k")?;
let precompute_edge_flags_kernel = module.load_function("precompute_edge_flags_20k")?;


let compute_moves_kernel = module.load_function("compute_refinement_moves_optimized_20k")?;
let compute_moves_kernel = module.load_function("compute_refinement_moves_optimized_20k")?;
let balance_kernel = module.load_function("balance_final_20k")?;
let balance_kernel = module.load_function("balance_final_20k")?;
let compute_connectivity_kernel = module.load_function("compute_connectivity_20k")?;
let compute_connectivity_kernel = module.load_function("compute_connectivity_20k")?;
let reduce_connectivity_sum_kernel = module.load_function("reduce_connectivity_sum_20k")?;
let reduce_connectivity_sum_kernel = module.load_function("reduce_connectivity_sum_20k")?;
let compute_swap_gains_kernel = module.load_function("compute_swap_gains_extended_20k")?;
let compute_swap_gains_kernel = module.load_function("compute_swap_gains_extended_20k")?;
let choose_elite_per_hyperedge_kernel = module.load_function("choose_elite_per_hyperedge_20k")?;
let choose_elite_per_hyperedge_kernel = module.load_function("choose_elite_per_hyperedge_20k")?;
let assign_from_elite_votes_kernel = module.load_function("assign_from_elite_votes_20k")?;
let assign_from_elite_votes_kernel = module.load_function("assign_from_elite_votes_20k")?;


let cfg = LaunchConfig {
let cfg = LaunchConfig {
grid_dim: (
grid_dim: (
(challenge.num_nodes as u32 + block_size - 1) / block_size,
(challenge.num_nodes as u32 + block_size - 1) / block_size,
1,
1,
1,
1,
),
),
block_dim: (block_size, 1, 1),
block_dim: (block_size, 1, 1),
shared_mem_bytes: 0,
shared_mem_bytes: 0,
};
};


let one_thread_cfg = LaunchConfig {
let one_thread_cfg = LaunchConfig {
grid_dim: (1, 1, 1),
grid_dim: (1, 1, 1),
block_dim: (1, 1, 1),
block_dim: (1, 1, 1),
shared_mem_bytes: 0,
shared_mem_bytes: 0,
};
};


let hedge_cfg = LaunchConfig {
let hedge_cfg = LaunchConfig {
grid_dim: (
grid_dim: (
(challenge.num_hyperedges as u32 + block_size - 1) / block_size,
(challenge.num_hyperedges as u32 + block_size - 1) / block_size,
1,
1,
1,
1,
),
),
block_dim: (block_size, 1, 1),
block_dim: (block_size, 1, 1),
shared_mem_bytes: 0,
shared_mem_bytes: 0,
};
};


let connectivity_reduce_cfg = LaunchConfig {
let connectivity_reduce_cfg = LaunchConfig {
grid_dim: (
grid_dim: (
(challenge.num_hyperedges as u32 + block_size * 2 - 1) / (block_size * 2),
(challenge.num_hyperedges as u32 + block_size * 2 - 1) / (block_size * 2),
1,
1,
1,
1,
),
),
block_dim: (block_size, 1, 1),
block_dim: (block_size, 1, 1),
shared_mem_bytes: (block_size as usize * std::mem::size_of::<i32>()) as u32,
shared_mem_bytes: (block_size as usize * std::mem::size_of::<i32>()) as u32,
};
};


let mut num_hedge_clusters = if let Some(params) = hyperparameters {
let mut num_hedge_clusters = if let Some(params) = hyperparameters {
params
params
.get("clusters")
.get("clusters")
.and_then(|v| v.as_i64())
.and_then(|v| v.as_i64())
.map(|v| v.clamp(4, 256) as i32)
.map(|v| v.clamp(4, 256) as i32)
.unwrap_or(64)
.unwrap_or(64)
} else {
} else {
64
64
};
};
if num_hedge_clusters % 4 != 0 {
if num_hedge_clusters % 4 != 0 {
num_hedge_clusters += 4 - (num_hedge_clusters % 4);
num_hedge_clusters += 4 - (num_hedge_clusters % 4);
}
}


let mut d_hyperedge_clusters = stream.alloc_zeros::<i32>(challenge.num_hyperedges as usize)?;
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_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_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_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_pref_priorities = stream.alloc_zeros::<i32>(challenge.num_nodes as usize)?;


let mut d_move_priorities = stream.alloc_zeros::<i32>(challenge.num_nodes as usize)?;
let mut d_move_priorities = stream.alloc_zeros::<i32>(challenge.num_nodes as usize)?;
let mut d_edge_flags_all = stream.alloc_zeros::<u64>(challenge.num_hyperedges as usize)?;
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 mut d_edge_flags_double = stream.alloc_zeros::<u64>(challenge.num_hyperedges as usize)?;
let mut d_connectivity = stream.alloc_zeros::<i32>(challenge.num_hyperedges as usize)?;
let mut d_connectivity = stream.alloc_zeros::<i32>(challenge.num_hyperedges as usize)?;
let mut d_total_connectivity = stream.alloc_zeros::<i32>(4096)?;
let mut d_total_connectivity = stream.alloc_zeros::<i32>(4096)?;


let mut d_hedge_choice = stream.alloc_zeros::<i32>(challenge.num_hyperedges as usize)?;
let mut d_hedge_choice = stream.alloc_zeros::<i32>(challenge.num_hyperedges as usize)?;


let swap_buf_size = 3 * challenge.num_nodes as usize;
let swap_buf_size = 3 * challenge.num_nodes as usize;
let mut d_swap_gains = stream.alloc_zeros::<i32>(swap_buf_size)?;
let mut d_swap_gains = stream.alloc_zeros::<i32>(swap_buf_size)?;


let num_parts_usize = challenge.num_parts as usize;
let num_parts_usize = challenge.num_parts as usize;
let is_sparse = (challenge.num_nodes as usize) > 4 * (challenge.num_hyperedges as usize + 1);
let is_sparse = (challenge.num_nodes as usize) > 4 * (challenge.num_hyperedges as usize + 1);


let effort = hyperparameters
let effort = hyperparameters
.as_ref()
.as_ref()
.and_then(|p| p.get("effort").and_then(|v| v.as_i64()))
.and_then(|p| p.get("effort").and_then(|v| v.as_i64()))
.unwrap_or(3);
.unwrap_or(3);


let (base_refine, base_ils, base_ils_quick, base_polish, base_post_balance) = match effort {
let (base_refine, base_ils, base_ils_quick, base_polish, base_post_balance) = match effort {
5 => (7000, 6, 70, 300, 0),
5 => (7000, 6, 70, 300, 0),
4 => (5000, 5, 60, 200, 0),
4 => (5000, 5, 60, 200, 0),
3 => (3000, 5, 50, 150, 64),
3 => (3000, 5, 50, 150, 64),
2 => (2000, 5, 50, 100, 64),
2 => (2000, 5, 50, 100, 64),
1 => (1000, 3, 25, 40, 32),
1 => (1000, 3, 25, 40, 32),
0 => (500, 3, 20, 30, 32),
0 => (500, 3, 20, 30, 32),
_ => (2000, 5, 50, 150, 64),
_ => (2000, 5, 50, 150, 64),
};
};


let refinement_rounds = hyperparameters
let refinement_rounds = hyperparameters
.as_ref()
.as_ref()
.and_then(|p| p.get("refinement").and_then(|v| v.as_i64()))
.and_then(|p| p.get("refinement").and_then(|v| v.as_i64()))
.map(|v| v.clamp(50, 50_000) as usize)
.map(|v| v.clamp(50, 50_000) as usize)
.unwrap_or(base_refine);
.unwrap_or(base_refine);


let ils_iterations = hyperparameters
let ils_iterations = hyperparameters
.as_ref()
.as_ref()
.and_then(|p| p.get("ils_iterations").and_then(|v| v.as_i64()))
.and_then(|p| p.get("ils_iterations").and_then(|v| v.as_i64()))
.map(|v| v.clamp(1, 500) as usize)
.map(|v| v.clamp(1, 500) as usize)
.unwrap_or(base_ils);
.unwrap_or(base_ils);


let ils_quick_refine = hyperparameters
let ils_quick_refine = hyperparameters
.as_ref()
.as_ref()
.and_then(|p| p.get("ils_quick_refine").and_then(|v| v.as_i64()))
.and_then(|p| p.get("ils_quick_refine").and_then(|v| v.as_i64()))
.map(|v| v.clamp(10, 500) as usize)
.map(|v| v.clamp(10, 500) as usize)
.unwrap_or(base_ils_quick);
.unwrap_or(base_ils_quick);


let post_ils_polish = hyperparameters
let post_ils_polish = hyperparameters
.as_ref()
.as_ref()
.and_then(|p| p.get("post_ils_polish").and_then(|v| v.as_i64()))
.and_then(|p| p.get("post_ils_polish").and_then(|v| v.as_i64()))
.map(|v| v.clamp(20, 500) as usize)
.map(|v| v.clamp(20, 500) as usize)
.unwrap_or(base_polish);
.unwrap_or(base_polish);


let tabu_tenure: usize = hyperparameters
let tabu_tenure: usize = hyperparameters
.as_ref()
.as_ref()
.and_then(|p| p.get("tabu_tenure").and_then(|v| v.as_i64()))
.and_then(|p| p.get("tabu_tenure").and_then(|v| v.as_i64()))
.map(|v| v.clamp(1, 30) as usize)
.map(|v| v.clamp(1, 30) as usize)
.unwrap_or(12);
.unwrap_or(12);


let tabu_fail_tenure = 3usize;
let tabu_fail_tenure = 3usize;
let tabu_mark_base = 2048usize;
let tabu_mark_base = 2048usize;
let tabu_mark_mult = 16usize;
let tabu_mark_mult = 16usize;
let tabu_fail_mark_len = 2048usize;
let tabu_fail_mark_len = 2048usize;


let extra_window = 49152usize;
let extra_window = 49152usize;
let slack_early = 8usize;
let slack_early = 8usize;
let slack_mid = 4usize;
let slack_mid = 4usize;
let slack_late = 2usize;
let slack_late = 2usize;


let move_limit: usize = hyperparameters
let move_limit: usize = hyperparameters
.as_ref()
.as_ref()
.and_then(|p| p.get("move_limit").and_then(|v| v.as_i64()))
.and_then(|p| p.get("move_limit").and_then(|v| v.as_i64()))
.map(|v| v.clamp(256, 1_000_000) as usize)
.map(|v| v.clamp(256, 1_000_000) as usize)
.unwrap_or(if is_sparse {
.unwrap_or(if is_sparse {
262_144
262_144
} else if challenge.num_hyperedges as usize >= 150_000
} else if challenge.num_hyperedges as usize >= 150_000
|| challenge.num_nodes as usize >= 250_000
|| challenge.num_nodes as usize >= 250_000
{
{
131_072
131_072
} else {
} else {
200_000
200_000
});
});


let neg_gain_thresh: i32 = 5;
let neg_gain_thresh: i32 = 5;
let scan_limit_swap = 32usize;
let scan_limit_swap = 32usize;
let scan_limit_cycle = 8usize;
let scan_limit_cycle = 8usize;


let mut part_to_part: Vec<Vec<(usize, i32)>> = vec![vec![]; num_parts_usize * num_parts_usize];
let mut part_to_part: Vec<Vec<(usize, i32)>> = vec![vec![]; num_parts_usize * num_parts_usize];
let mut swap_gains_host: Vec<i32> = vec![0i32; swap_buf_size];
let mut swap_gains_host: Vec<i32> = vec![0i32; swap_buf_size];
let mut partition_host_swap: Vec<i32> = vec![0i32; challenge.num_nodes as usize];
let mut partition_host_swap: Vec<i32> = vec![0i32; challenge.num_nodes as usize];
let mut partition_mut_swap: Vec<i32> = vec![0i32; challenge.num_nodes as usize];
let mut partition_mut_swap: Vec<i32> = vec![0i32; challenge.num_nodes as usize];
let mut partition_host_refine: Vec<i32> = vec![0i32; challenge.num_nodes as usize];
let mut partition_host_refine: Vec<i32> = vec![0i32; challenge.num_nodes as usize];
let mut used_ba_buf: Vec<bool> = Vec::with_capacity(1024);
let mut used_ba_buf: Vec<bool> = Vec::with_capacity(1024);
let mut nodes_in_part_host: Vec<i32> = vec![0i32; num_parts_usize];
let mut nodes_in_part_host: Vec<i32> = vec![0i32; num_parts_usize];
let mut move_keys_host: Vec<i32> = vec![0i32; challenge.num_nodes as usize];
let mut move_keys_host: Vec<i32> = vec![0i32; challenge.num_nodes as usize];


let hedge_offsets_host = stream.memcpy_dtov(&challenge.d_hyperedge_offsets)?;
let hedge_offsets_host = stream.memcpy_dtov(&challenge.d_hyperedge_offsets)?;
let hyperedge_nodes_host = stream.memcpy_dtov(&challenge.d_hyperedge_nodes)?;
let hyperedge_nodes_host = stream.memcpy_dtov(&challenge.d_hyperedge_nodes)?;
let mut hedge_sizes_host: Vec<i32> = Vec::with_capacity(challenge.num_hyperedges as usize);
let mut hedge_sizes_host: Vec<i32> = Vec::with_capacity(challenge.num_hyperedges as usize);
for h in 0..(challenge.num_hyperedges as usize) {
for h in 0..(challenge.num_hyperedges as usize) {
let sz = hedge_offsets_host[h + 1] - hedge_offsets_host[h];
let sz = hedge_offsets_host[h + 1] - hedge_offsets_host[h];
hedge_sizes_host.push(sz);
hedge_sizes_host.push(sz);
}
}


let build_high_hedge_ids =
let build_high_hedge_ids =
|connectivity: &[i32], num_high_hedges: usize| -> Vec<i32> {
|connectivity: &[i32], num_high_hedges: usize| -> Vec<i32> {
let mut impact: Vec<(i32, i32, i32)> = connectivity
let mut impact: Vec<(i32, i32, i32)> = connectivity
.iter()
.iter()
.enumerate()
.enumerate()
.map(|(i, &c)| (c, hedge_sizes_host[i], i as i32))
.map(|(i, &c)| (c, hedge_sizes_host[i], i as i32))
.collect();
.collect();
impact.sort_unstable_by(|a, b| {
impact.sort_unstable_by(|a, b| {
b.0.cmp(&a.0)
b.0.cmp(&a.0)
.then_with(|| b.1.cmp(&a.1))
.then_with(|| b.1.cmp(&a.1))
.then_with(|| a.2.cmp(&b.2))
.then_with(|| a.2.cmp(&b.2))
});
});
impact
impact
.into_iter()
.into_iter()
.take(num_high_hedges)
.take(num_high_hedges)
.map(|t| t.2)
.map(|t| t.2)
.collect()
.collect()
};
};


unsafe {
unsafe {
stream
stream
.launch_builder(&hyperedge_cluster_kernel)
.launch_builder(&hyperedge_cluster_kernel)
.arg(&(challenge.num_hyperedges as i32))
.arg(&(challenge.num_hyperedges as i32))
.arg(&(num_hedge_clusters as i32))
.arg(&(num_hedge_clusters as i32))
.arg(&challenge.d_hyperedge_offsets)
.arg(&challenge.d_hyperedge_offsets)
.arg(&challenge.d_hyperedge_nodes)
.arg(&challenge.d_hyperedge_nodes)
.arg(&mut d_hyperedge_clusters)
.arg(&mut d_hyperedge_clusters)
.launch(LaunchConfig {
.launch(LaunchConfig {
grid_dim: (
grid_dim: (
(challenge.num_hyperedges as u32 + block_size - 1) / block_size,
(challenge.num_hyperedges as u32 + block_size - 1) / block_size,
1,
1,
1,
1,
),
),
block_dim: (block_size, 1, 1),
block_dim: (block_size, 1, 1),
shared_mem_bytes: 0,
shared_mem_bytes: 0,
})?;
})?;
}
}


unsafe {
unsafe {
stream
stream
.launch_builder(&compute_preferences_kernel)
.launch_builder(&compute_preferences_kernel)
.arg(&(challenge.num_nodes as i32))
.arg(&(challenge.num_nodes as i32))
.arg(&(challenge.num_parts as i32))
.arg(&(challenge.num_parts as i32))
.arg(&(num_hedge_clusters as i32))
.arg(&(num_hedge_clusters as i32))
.arg(&challenge.d_node_hyperedges)
.arg(&challenge.d_node_hyperedges)
.arg(&challenge.d_node_offsets)
.arg(&challenge.d_node_offsets)
.arg(&d_hyperedge_clusters)
.arg(&d_hyperedge_clusters)
.arg(&challenge.d_hyperedge_offsets)
.arg(&challenge.d_hyperedge_offsets)
.arg(&mut d_pref_parts)
.arg(&mut d_pref_parts)
.arg(&mut d_pref_priorities)
.arg(&mut d_pref_priorities)
.launch(cfg.clone())?;
.launch(cfg.clone())?;
}
}


let pref_parts = stream.memcpy_dtov(&d_pref_parts)?;
let pref_parts = stream.memcpy_dtov(&d_pref_parts)?;
let pref_priorities = stream.memcpy_dtov(&d_pref_priorities)?;
let pref_priorities = stream.memcpy_dtov(&d_pref_priorities)?;


let mut indices: Vec<usize> = (0..challenge.num_nodes as usize).collect();
let mut indices: Vec<usize> = (0..challenge.num_nodes as usize).collect();
indices.sort_unstable_by(|&a, &b| {
indices.sort_unstable_by(|&a, &b| {
pref_priorities[b].cmp(&pref_priorities[a]).then_with(|| a.cmp(&b))
pref_priorities[b].cmp(&pref_priorities[a]).then_with(|| a.cmp(&b))
});
});


let sorted_nodes: Vec<i32> = indices.iter().map(|&i| i as i32).collect();
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 sorted_parts: Vec<i32> = indices.iter().map(|&i| pref_parts[i]).collect();


let d_sorted_nodes = stream.memcpy_stod(&sorted_nodes)?;
let d_sorted_nodes = stream.memcpy_stod(&sorted_nodes)?;
let d_sorted_parts = stream.memcpy_stod(&sorted_parts)?;
let d_sorted_parts = stream.memcpy_stod(&sorted_parts)?;


unsafe {
unsafe {
stream
stream
.launch_builder(&execute_assignments_kernel)
.launch_builder(&execute_assignments_kernel)
.arg(&(challenge.num_nodes as i32))
.arg(&(challenge.num_nodes as i32))
.arg(&(challenge.num_parts as i32))
.arg(&(challenge.num_parts as i32))
.arg(&(challenge.max_part_size as i32))
.arg(&(challenge.max_part_size as i32))
.arg(&d_sorted_nodes)
.arg(&d_sorted_nodes)
.arg(&d_sorted_parts)
.arg(&d_sorted_parts)
.arg(&mut d_partition)
.arg(&mut d_partition)
.arg(&mut d_nodes_in_part)
.arg(&mut d_nodes_in_part)
.launch(one_thread_cfg.clone())?;
.launch(one_thread_cfg.clone())?;
}
}


stream.memcpy_dtoh(&d_partition, &mut partition_host_refine)?;
stream.memcpy_dtoh(&d_partition, &mut partition_host_refine)?;
stream.memcpy_dtoh(&d_nodes_in_part, &mut nodes_in_part_host)?;
stream.memcpy_dtoh(&d_nodes_in_part, &mut nodes_in_part_host)?;


let simulate_execute_moves =
let simulate_execute_moves =
|partition_mirror: &mut [i32],
|partition_mirror: &mut [i32],
nodes_in_part_mirror: &mut [i32],
nodes_in_part_mirror: &mut [i32],
move_nodes: &[i32],
move_nodes: &[i32],
move_parts: &[i32]|
move_parts: &[i32]|
-> i32 {
-> i32 {
let mut moves_executed = 0i32;
let mut moves_executed = 0i32;
for i in 0..move_nodes.len() {
for i in 0..move_nodes.len() {
let node = move_nodes[i];
let node = move_nodes[i];
let target_part = move_parts[i];
let target_part = move_parts[i];
if node < 0 || target_part < 0 {
if node < 0 || target_part < 0 {
continue;
continue;
}
}
let node_usize = node as usize;
let node_usize = node as usize;
if node_usize >= partition_mirror.len() {
if node_usize >= partition_mirror.len() {
continue;
continue;
}
}
let current_part = partition_mirror[node_usize];
let current_part = partition_mirror[node_usize];
if current_part >= 0
if current_part >= 0
&& (current_part as usize) < nodes_in_part_mirror.len()
&& (current_part as usize) < nodes_in_part_mirror.len()
&& (target_part as usize) < nodes_in_part_mirror.len()
&& (target_part as usize) < nodes_in_part_mirror.len()
&& nodes_in_part_mirror[target_part as usize] < challenge.max_part_size as i32
&& nodes_in_part_mirror[target_part as usize] < challenge.max_part_size as i32
&& nodes_in_part_mirror[current_part as usize] > 1
&& nodes_in_part_mirror[current_part as usize] > 1
{
{
partition_mirror[node_usize] = target_part;
partition_mirror[node_usize] = target_part;
nodes_in_part_mirror[current_part as usize] -= 1;
nodes_in_part_mirror[current_part as usize] -= 1;
nodes_in_part_mirror[target_part as usize] += 1;
nodes_in_part_mirror[target_part as usize] += 1;
moves_executed += 1;
moves_executed += 1;
}
}
}
}
moves_executed
moves_executed
};
};


let perturb_on_host =
let perturb_on_host =
|partition_mirror: &mut [i32],
|partition_mirror: &mut [i32],
nodes_in_part_mirror: &mut [i32],
nodes_in_part_mirror: &mut [i32],
perturb_strength: i32,
perturb_strength: i32,
seed: u64| {
seed: u64| {
let mut state = seed;
let mut state = seed;
let mut moves_made = 0i32;
let mut moves_made = 0i32;
let target_moves = (challenge.num_nodes as i32 * perturb_strength) / 100;
let target_moves = (challenge.num_nodes as i32 * perturb_strength) / 100;
for _attempt in 0..(challenge.num_nodes as usize) {
for _attempt in 0..(challenge.num_nodes as usize) {
if moves_made >= target_moves {
if moves_made >= target_moves {
break;
break;
}
}
state = state
state = state
.wrapping_mul(6364136223846793005u64)
.wrapping_mul(6364136223846793005u64)
.wrapping_add(1442695040888963407u64);
.wrapping_add(1442695040888963407u64);
let node = (state % challenge.num_nodes as u64) as usize;
let node = (state % challenge.num_nodes as u64) as usize;
let current_part = partition_mirror[node];
let current_part = partition_mirror[node];
if current_part < 0 || current_part >= challenge.num_parts as i32 {
if current_part < 0 || current_part >= challenge.num_parts as i32 {
continue;
continue;
}
}
if nodes_in_part_mirror[current_part as usize] <= 1 {
if nodes_in_part_mirror[current_part as usize] <= 1 {
continue;
continue;
}
}
state = state
state = state
.wrapping_mul(6364136223846793005u64)
.wrapping_mul(6364136223846793005u64)
.wrapping_add(1442695040888963407u64);
.wrapping_add(1442695040888963407u64);
let target_part = (state % challenge.num_parts as u64) as i32;
let target_part = (state % challenge.num_parts as u64) as i32;
if target_part != current_part
if target_part != current_part
&& nodes_in_part_mirror[target_part as usize] < challenge.max_part_size as i32
&& nodes_in_part_mirror[target_part as usize] < challenge.max_part_size as i32
{
{
partition_mirror[node] = target_part;
partition_mirror[node] = target_part;
nodes_in_part_mirror[current_part as usize] -= 1;
nodes_in_part_mirror[current_part as usize] -= 1;
nodes_in_part_mirror[target_part as usize] += 1;
nodes_in_part_mirror[target_part as usize] += 1;
moves_made += 1;
moves_made += 1;
}
}
}
}
};
};


let perturb_guided_on_host =
let perturb_guided_on_host =
|partition_mirror: &mut [i32],
|partition_mirror: &mut [i32],
nodes_in_part_mirror: &mut [i32],
nodes_in_part_mirror: &mut [i32],
high_hedge_ids_host: &[i32]| {
high_hedge_ids_host: &[i32]| {
let np = std::cmp::min(num_parts_usize, 64usize);
let np = std::cmp::min(num_parts_usize, 64usize);
for &hedge_i32 in high_hedge_ids_host.iter() {
for &hedge_i32 in high_hedge_ids_host.iter() {
if hedge_i32 < 0 {
if hedge_i32 < 0 {
continue;
continue;
}
}
let hedge = hedge_i32 as usize;
let hedge = hedge_i32 as usize;
let start = hedge_offsets_host[hedge] as usize;
let start = hedge_offsets_host[hedge] as usize;
let end = hedge_offsets_host[hedge + 1] as usize;
let end = hedge_offsets_host[hedge + 1] as usize;
let hedge_size = end - start;
let hedge_size = end - start;
if hedge_size <= 1 {
if hedge_size <= 1 {
continue;
continue;
}
}


let mut part_count = [0i32; 64];
let mut part_count = [0i32; 64];
for k in start..end {
for k in start..end {
let node = hyperedge_nodes_host[k] as usize;
let node = hyperedge_nodes_host[k] as usize;
let part = partition_mirror[node];
let part = partition_mirror[node];
if part >= 0 && (part as usize) < np {
if part >= 0 && (part as usize) < np {
part_count[part as usize] += 1;
part_count[part as usize] += 1;
}
}
}
}


let mut majority_part = 0usize;
let mut majority_part = 0usize;
for p in 1..np {
for p in 1..np {
if part_count[p] > part_count[majority_part] {
if part_count[p] > part_count[majority_part] {
majority_part = p;
majority_part = p;
}
}
}
}


let mut parts_present = 0i32;
let mut parts_present = 0i32;
for p in 0..np {
for p in 0..np {
if part_count[p] > 0 {
if part_count[p] > 0 {
parts_present += 1;
parts_present += 1;
}
}
}
}
if parts_present <= 1 {
if parts_present <= 1 {
continue;
continue;
}
}


for _iter in 0..np {
for _iter in 0..np {
if parts_present <= 1 {
if parts_present <= 1 {
break;
break;
}
}
if nodes_in_part_mirror[majority_part] >= challenge.max_part_size as i32 {
if nodes_in_part_mirror[majority_part] >= challenge.max_part_size as i32 {
break;
break;
}
}


let mut min_part = usize::MAX;
let mut min_part = usize::MAX;
let mut min_cnt = 0i32;
let mut min_cnt = 0i32;
for p in 0..np {
for p in 0..np {
if p == majority_part {
if p == majority_part {
continue;
continue;
}
}
let cnt = part_count[p];
let cnt = part_count[p];
if cnt <= 0 {
if cnt <= 0 {
continue;
continue;
}
}
if min_part == usize::MAX || cnt < min_cnt || (cnt == min_cnt && p < min_part)
if min_part == usize::MAX || cnt < min_cnt || (cnt == min_cnt && p < min_part)
{
{
min_part = p;
min_part = p;
min_cnt = cnt;
min_cnt = cnt;
}
}
}
}
if min_part == usize::MAX {
if min_part == usize::MAX {
break;
break;
}
}


let mut moved_any = false;
let mut moved_any = false;
for k in start..end {
for k in start..end {
if part_count[min_part] <= 0 {
if part_count[min_part] <= 0 {
break;
break;
}
}
if nodes_in_part_mirror[majority_part] >= challenge.max_part_size as i32 {
if nodes_in_part_mirror[majority_part] >= challenge.max_part_size as i32 {
break;
break;
}
}


let node = hyperedge_nodes_host[k] as usize;
let node = hyperedge_nodes_host[k] as usize;
if partition_mirror[node] != min_part as i32 {
if partition_mirror[node] != min_part as i32 {
continue;
continue;
}
}
if nodes_in_part_mirror[min_part] <= 1 {
if nodes_in_part_mirror[min_part] <= 1 {
continue;
continue;
}
}


partition_mirror[node] = majority_part as i32;
partition_mirror[node] = majority_part as i32;
nodes_in_part_mirror[min_part] -= 1;
nodes_in_part_mirror[min_part] -= 1;
nodes_in_part_mirror[majority_part] += 1;
nodes_in_part_mirror[majority_part] += 1;
part_count[min_part] -= 1;
part_count[min_part] -= 1;
part_count[majority_part] += 1;
part_count[majority_part] += 1;
moved_any = true;
moved_any = true;
if part_count[min_part] == 0 {
if part_count[min_part] == 0 {
parts_present -= 1;
parts_present -= 1;
break;
break;
}
}
}
}


if !moved_any {
if !moved_any {
break;
break;
}
}
}
}
}
}
};
};


let mut sorted_move_nodes: Vec<i32> = Vec::with_capacity(challenge.num_nodes as usize);
let mut sorted_move_nodes: Vec<i32> = Vec::with_capacity(challenge.num_nodes as usize);
let mut sorted_move_parts: Vec<i32> = Vec::with_capacity(challenge.num_nodes as usize);
let mut sorted_move_parts: Vec<i32> = Vec::with_capacity(challenge.num_nodes as usize);
let mut valid_moves: Vec<(usize, i32)> = Vec::with_capacity(challenge.num_nodes as usize);
let mut valid_moves: Vec<(usize, i32)> = Vec::with_capacity(challenge.num_nodes as usize);


let mut stagnant_rounds = 0usize;
let mut stagnant_rounds = 0usize;
let max_stagnant_rounds = 30usize;
let max_stagnant_rounds = 30usize;


let mut node_tabu_until: Vec<usize> = vec![0; challenge.num_nodes as usize];
let mut node_tabu_until: Vec<usize> = vec![0; challenge.num_nodes as usize];


let mut tgt_used: Vec<usize> = vec![0; num_parts_usize];
let mut tgt_used: Vec<usize> = vec![0; num_parts_usize];
let mut tgt_quota: Vec<usize> = vec![0; num_parts_usize];
let mut tgt_quota: Vec<usize> = vec![0; num_parts_usize];


let mut total_moves_executed = 0usize;
let mut total_moves_executed = 0usize;
let mut total_perturbations = 0usize;
let mut total_perturbations = 0usize;


for round in 0..refinement_rounds {
for round in 0..refinement_rounds {
unsafe {
unsafe {
stream
stream
.launch_builder(&precompute_edge_flags_kernel)
.launch_builder(&precompute_edge_flags_kernel)
.arg(&(challenge.num_hyperedges as i32))
.arg(&(challenge.num_hyperedges as i32))
.arg(&(challenge.num_nodes as i32))
.arg(&(challenge.num_nodes as i32))
.arg(&challenge.d_hyperedge_nodes)
.arg(&challenge.d_hyperedge_nodes)
.arg(&challenge.d_hyperedge_offsets)
.arg(&challenge.d_hyperedge_offsets)
.arg(&d_partition)
.arg(&d_partition)
.arg(&mut d_edge_flags_all)
.arg(&mut d_edge_flags_all)
.arg(&mut d_edge_flags_double)
.arg(&mut d_edge_flags_double)
.launch(hedge_cfg.clone())?;
.launch(hedge_cfg.clone())?;
}
}
unsafe {
unsafe {
stream
stream
.launch_builder(&compute_moves_kernel)
.launch_builder(&compute_moves_kernel)
.arg(&(challenge.num_nodes as i32))
.arg(&(challenge.num_nodes as i32))
.arg(&(challenge.num_parts as i32))
.arg(&(challenge.num_parts as i32))
.arg(&(challenge.max_part_size as i32))
.arg(&(challenge.max_part_size as i32))
.arg(&challenge.d_node_hyperedges)
.arg(&challenge.d_node_hyperedges)
.arg(&challenge.d_node_offsets)
.arg(&challenge.d_node_offsets)
.arg(&d_partition)
.arg(&d_partition)
.arg(&d_nodes_in_part)
.arg(&d_nodes_in_part)
.arg(&d_edge_flags_all)
.arg(&d_edge_flags_all)
.arg(&d_edge_flags_double)
.arg(&d_edge_flags_double)
.arg(&mut d_move_priorities)
.arg(&mut d_move_priorities)
.launch(cfg.clone())?;
.launch(cfg.clone())?;
}
}


stream.memcpy_dtoh(&d_move_priorities, &mut move_keys_host)?;
stream.memcpy_dtoh(&d_move_priorities, &mut move_keys_host)?;


valid_moves.clear();
valid_moves.clear();
let max_gain = move_keys_host.iter().filter(|&&k| k as u32 != 0x80000000).map(|&k| k >> 16).max().unwrap_or(0);
let max_gain = move_keys_host.iter().filter(|&&k| k as u32 != 0x80000000).map(|&k| k >> 16).max().unwrap_or(0);
let aspiration_threshold = (max_gain * 3) / 4;
let aspiration_threshold = (max_gain * 3) / 4;
let mut rng_state = 123456789u64.wrapping_add(round as u64);
let mut rng_state = 123456789u64.wrapping_add(round as u64);


for (node, &key) in move_keys_host.iter().enumerate() {
for (node, &key) in move_keys_host.iter().enumerate() {
if key as u32 != 0x80000000 {
if key as u32 != 0x80000000 {
let gain = key >> 16;
let gain = key >> 16;
let is_tabu = node_tabu_until[node] > round && gain < aspiration_threshold;
let is_tabu = node_tabu_until[node] > round && gain < aspiration_threshold;
if !is_tabu {
if !is_tabu {
if gain > 0 {
if gain > 0 {
valid_moves.push((node, key));
valid_moves.push((node, key));
} else if gain >= -3 {
} else if gain >= -3 {
let rounds_left = refinement_rounds.saturating_sub(round) as u64;
let rounds_left = refinement_rounds.saturating_sub(round) as u64;
let penalty = if gain < 0 { (-gain) as u64 } else { 0 };
let penalty = if gain < 0 { (-gain) as u64 } else { 0 };
let probability_num = rounds_left * rounds_left;
let probability_num = rounds_left * rounds_left;
let probability_den = (refinement_rounds as u64 * refinement_rounds as u64)
let probability_den = (refinement_rounds as u64 * refinement_rounds as u64)
.saturating_mul(1 + penalty * 5);
.saturating_mul(1 + penalty * 5);
rng_state = rng_state.wrapping_mul(6364136223846793005u64).wrapping_add(1442695040888963407u64);
rng_state = rng_state.wrapping_mul(6364136223846793005u64).wrapping_add(1442695040888963407u64);
if probability_den > 0 && (rng_state % probability_den) < probability_num {
if probability_den > 0 && (rng_state % probability_den) < probability_num {
let new_key = (0 << 16) | (key & 0xFFFF);
let new_key = (0 << 16) | (key & 0xFFFF);
valid_moves.push((node, new_key));
valid_moves.push((node, new_key));
}
}
}
}
}
}
}
}
}
}


if valid_moves.is_empty() {
if valid_moves.is_empty() {
break;
break;
}
}


let cmp = |a: &(usize, i32), b: &(usize, i32)| b.1.cmp(&a.1).then(a.0.cmp(&b.0));
let cmp = |a: &(usize, i32), b: &(usize, i32)| b.1.cmp(&a.1).then(a.0.cmp(&b.0));


let mut k_base = valid_moves.len();
let mut k_base = valid_moves.len();
let adaptive_limit = if round < 50 {
let adaptive_limit = if round < 50 {
move_limit / 2
move_limit / 2
} else if round < 200 {
} else if round < 200 {
(move_limit * 3) / 4
(move_limit * 3) / 4
} else {
} else {
move_limit / 4
move_limit / 4
};
};
if k_base > adaptive_limit {
if k_base > adaptive_limit {
k_base = adaptive_limit;
k_base = adaptive_limit;
}
}


let k_cand = std::cmp::min(valid_moves.len(), k_base.saturating_add(extra_window));
let k_cand = std::cmp::min(valid_moves.len(), k_base.saturating_add(extra_window));


if k_cand > 1 {
if k_cand > 1 {
valid_moves.select_nth_unstable_by(k_cand - 1, cmp);
valid_moves.select_nth_unstable_by(k_cand - 1, cmp);
valid_moves[..k_cand].sort_unstable_by(cmp);
valid_moves[..k_cand].sort_unstable_by(cmp);
} else {
} else {
valid_moves[..k_cand].sort_unstable_by(cmp);
valid_moves[..k_cand].sort_unstable_by(cmp);
}
}


let slack = if round < 64 {
let slack = if round < 64 {
slack_early
slack_early
} else if round < 256 {
} else if round < 256 {
slack_mid
slack_mid
} else {
} else {
slack_late
slack_late
};
};


tgt_used.fill(0);
tgt_used.fill(0);
for p in 0..num_parts_usize {
for p in 0..num_parts_usize {
let free = (challenge.max_part_size as i32 - nodes_in_part_host[p]).max(0) as usize;
let free = (challenge.max_part_size as i32 - nodes_in_part_host[p]).max(0) as usize;
tgt_quota[p] = std::cmp::max(1, free.saturating_add(slack));
tgt_quota[p] = std::cmp::max(1, free.saturating_add(slack));
}
}


sorted_move_nodes.clear();
sorted_move_nodes.clear();
sorted_move_parts.clear();
sorted_move_parts.clear();
for &(node, key) in valid_moves[..k_cand].iter() {
for &(node, key) in valid_moves[..k_cand].iter() {
if sorted_move_nodes.len() >= k_base {
if sorted_move_nodes.len() >= k_base {
break;
break;
}
}
let tgt = (key & 63) as usize;
let tgt = (key & 63) as usize;
if tgt < num_parts_usize && tgt_used[tgt] < tgt_quota[tgt] {
if tgt < num_parts_usize && tgt_used[tgt] < tgt_quota[tgt] {
tgt_used[tgt] += 1;
tgt_used[tgt] += 1;
sorted_move_nodes.push(node as i32);
sorted_move_nodes.push(node as i32);
sorted_move_parts.push(tgt as i32);
sorted_move_parts.push(tgt as i32);
}
}
}
}


if sorted_move_nodes.is_empty() {
if sorted_move_nodes.is_empty() {
let take = std::cmp::min(k_base, k_cand);
let take = std::cmp::min(k_base, k_cand);
sorted_move_nodes.extend(valid_moves[..take].iter().map(|(n, _)| *n as i32));
sorted_move_nodes.extend(valid_moves[..take].iter().map(|(n, _)| *n as i32));
sorted_move_parts.extend(valid_moves[..take].iter().map(|(_, key)| (key & 63) as i32));
sorted_move_parts.extend(valid_moves[..take].iter().map(|(_, key)| (key & 63) as i32));
}
}


let mut moves_executed = simulate_execute_moves(
let mut moves_executed = simulate_execute_moves(
&mut partition_host_refine,
&mut partition_host_refine,
&mut nodes_in_part_host,
&mut nodes_in_part_host,
&sorted_move_nodes,
&sorted_move_nodes,
&sorted_move_parts,
&sorted_move_parts,
);
);


if moves_executed > 0 {
if moves_executed > 0 {
stream.memcpy_htod(&partition_host_refine, &mut d_partition)?;
stream.memcpy_htod(&partition_host_refine, &mut d_partition)?;
stream.memcpy_htod(&nodes_in_part_host, &mut d_nodes_in_part)?;
stream.memcpy_htod(&nodes_in_part_host, &mut d_nodes_in_part)?;
}
}


if moves_executed == 0 && k_cand > k_base {
if moves_executed == 0 && k_cand > k_base {
let fail_mark_len = std::cmp::min(sorted_move_nodes.len(), tabu_fail_mark_len);
let fail_mark_len = std::cmp::min(sorted_move_nodes.len(), tabu_fail_mark_len);
for &node in sorted_move_nodes.iter().take(fail_mark_len) {
for &node in sorted_move_nodes.iter().take(fail_mark_len) {
node_tabu_until[node as usize] = round + tabu_fail_tenure;
node_tabu_until[node as usize] = round + tabu_fail_tenure;
}
}


sorted_move_nodes.clear();
sorted_move_nodes.clear();
sorted_move_parts.clear();
sorted_move_parts.clear();
let tail = &valid_moves[k_base..k_cand];
let tail = &valid_moves[k_base..k_cand];
let take = std::cmp::min(tail.len(), k_base);
let take = std::cmp::min(tail.len(), k_base);
sorted_move_nodes.extend(tail.iter().take(take).map(|(n, _)| *n as i32));
sorted_move_nodes.extend(tail.iter().take(take).map(|(n, _)| *n as i32));
sorted_move_parts.extend(tail.iter().take(take).map(|(_, key)| (key & 63) as i32));
sorted_move_parts.extend(tail.iter().take(take).map(|(_, key)| (key & 63) as i32));


if !sorted_move_nodes.is_empty() {
if !sorted_move_nodes.is_empty() {
moves_executed = simulate_execute_moves(
moves_executed = simulate_execute_moves(
&mut partition_host_refine,
&mut partition_host_refine,
&mut nodes_in_part_host,
&mut nodes_in_part_host,
&sorted_move_nodes,
&sorted_move_nodes,
&sorted_move_parts,
&sorted_move_parts,
);
);
if moves_executed > 0 {
if moves_executed > 0 {
stream.memcpy_htod(&partition_host_refine, &mut d_partition)?;
stream.memcpy_htod(&partition_host_refine, &mut d_partition)?;
stream.memcpy_htod(&nodes_in_part_host, &mut d_nodes_in_part)?;
stream.memcpy_htod(&nodes_in_part_host, &mut d_nodes_in_part)?;
}
}
}
}
}
}


total_moves_executed += moves_executed as usize;
total_moves_executed += moves_executed as usize;


if moves_executed > 0 {
if moves_executed > 0 {
let mark_len = std::cmp::min(
let mark_len = std::cmp::min(
sorted_move_nodes.len(),
sorted_move_nodes.len(),
std::cmp::max(
std::cmp::max(
tabu_mark_base,
tabu_mark_base,
(moves_executed as usize).saturating_mul(tabu_mark_mult),
(moves_executed as usize).saturating_mul(tabu_mark_mult),
),
),