#[cfg(feature = "gpu")]
use approx::assert_relative_eq;
#[cfg(feature = "gpu")]
use infomeasure::estimators::entropy::{Entropy, LocalValues};
#[cfg(feature = "gpu")]
use ndarray::Array2;
#[cfg(feature = "gpu")]
use crate::test_helpers::{generate_random_nd_data, measure_execution_time};
#[test]
#[cfg(feature = "gpu")]
fn test_box_kernel_cpu_vs_gpu() {
let seed = 42;
let size = 1000; let bandwidths = [0.5, 1.0, 2.0];
let dimensions = [1, 2, 3, 4, 8];
println!("Testing box kernel CPU vs GPU implementation");
for &dim in &dimensions {
for &bandwidth in &bandwidths {
let data = generate_random_nd_data(size, dim, seed);
let test_name = format!("Box Kernel (dim={dim}, bandwidth={bandwidth}, size={size})");
println!("Testing {test_name}");
match dim {
1 => compare_box_kernel_cpu_vs_gpu::<1>(data, bandwidth, &test_name),
2 => compare_box_kernel_cpu_vs_gpu::<2>(data, bandwidth, &test_name),
3 => compare_box_kernel_cpu_vs_gpu::<3>(data, bandwidth, &test_name),
4 => compare_box_kernel_cpu_vs_gpu::<4>(data, bandwidth, &test_name),
8 => compare_box_kernel_cpu_vs_gpu::<8>(data, bandwidth, &test_name),
_ => panic!("Unsupported dimension: {dim}"),
};
}
}
}
#[cfg(feature = "gpu")]
fn compare_box_kernel_cpu_vs_gpu<const K: usize>(
data: Array2<f64>,
bandwidth: f64,
test_name: &str,
) {
let kernel = Entropy::nd_kernel_with_type::<K>(data.clone(), "box".to_string(), bandwidth);
let cpu_local_entropy = kernel.box_kernel_local_values();
let cpu_global_entropy = cpu_local_entropy.mean().unwrap();
let gpu_local_entropy = kernel.local_values();
let gpu_global_entropy = gpu_local_entropy.mean().unwrap();
println!("{test_name} - CPU global entropy: {cpu_global_entropy}");
println!("{test_name} - GPU global entropy: {gpu_global_entropy}");
let epsilon = 1e-6;
let max_relative = 1e-6;
assert_relative_eq!(
cpu_global_entropy,
gpu_global_entropy,
epsilon = epsilon,
max_relative = max_relative
);
let sample_size = cpu_local_entropy.len().min(10);
let step = cpu_local_entropy.len() / sample_size.max(1);
for i in (0..cpu_local_entropy.len()).step_by(step.max(1)) {
if i < cpu_local_entropy.len() && i < gpu_local_entropy.len() {
let cpu_val = cpu_local_entropy[i];
let gpu_val = gpu_local_entropy[i];
if cpu_val.abs() > 1e-6 && gpu_val.abs() > 1e-6 {
let epsilon = 1e-6;
let max_relative = 1e-5;
assert_relative_eq!(
cpu_val,
gpu_val,
epsilon = epsilon,
max_relative = max_relative
);
}
}
}
println!("{test_name} - CPU and GPU implementations match");
}
#[test]
#[cfg(feature = "gpu")]
fn test_box_kernel_gpu_fallback() {
let seed = 42;
let small_size = 50; let bandwidth = 1.0;
let dimensions = [1, 2, 3, 4, 8];
println!("Testing box kernel GPU fallback mechanism for small datasets");
for &dim in &dimensions {
let small_data = generate_random_nd_data(small_size, dim, seed);
let test_name =
format!("Box Kernel Fallback (small dataset, dim={dim}, size={small_size})");
println!("Testing {test_name}");
match dim {
1 => test_box_kernel_fallback::<1>(small_data, bandwidth, &test_name, "small_dataset"),
2 => test_box_kernel_fallback::<2>(small_data, bandwidth, &test_name, "small_dataset"),
3 => test_box_kernel_fallback::<3>(small_data, bandwidth, &test_name, "small_dataset"),
4 => test_box_kernel_fallback::<4>(small_data, bandwidth, &test_name, "small_dataset"),
8 => test_box_kernel_fallback::<8>(small_data, bandwidth, &test_name, "small_dataset"),
_ => panic!("Unsupported dimension: {dim}"),
};
}
println!("Testing box kernel GPU fallback mechanism for large dimensions");
let large_size = 1000;
let large_dim = 8; let large_data = generate_random_nd_data(large_size, large_dim, seed);
let test_name =
format!("Box Kernel Fallback (large dimension, dim={large_dim}, size={large_size})");
println!("Testing {test_name}");
test_box_kernel_fallback::<8>(large_data, bandwidth, &test_name, "large_dimension");
}
#[cfg(feature = "gpu")]
fn test_box_kernel_fallback<const K: usize>(
data: Array2<f64>,
bandwidth: f64,
test_name: &str,
fallback_type: &str,
) {
let kernel = Entropy::nd_kernel_with_type::<K>(data.clone(), "box".to_string(), bandwidth);
let cpu_local_entropy = kernel.box_kernel_local_values();
let cpu_global_entropy = cpu_local_entropy.mean().unwrap();
let gpu_local_entropy = kernel.local_values();
let gpu_global_entropy = gpu_local_entropy.mean().unwrap();
println!("{test_name} - CPU global entropy: {cpu_global_entropy}");
println!("{test_name} - GPU with fallback global entropy: {gpu_global_entropy}");
if fallback_type == "small_dataset" {
assert_eq!(cpu_global_entropy, gpu_global_entropy);
assert_eq!(cpu_local_entropy.len(), gpu_local_entropy.len());
for i in 0..cpu_local_entropy.len() {
assert_eq!(cpu_local_entropy[i], gpu_local_entropy[i]);
}
println!("{test_name} - CPU and GPU with fallback implementations match exactly");
} else {
assert_relative_eq!(
cpu_global_entropy,
gpu_global_entropy,
epsilon = 1e-6,
max_relative = 1e-6
);
let sample_size = cpu_local_entropy.len().min(10);
let step = cpu_local_entropy.len() / sample_size.max(1);
for i in (0..cpu_local_entropy.len()).step_by(step.max(1)) {
if i < cpu_local_entropy.len() && i < gpu_local_entropy.len() {
let cpu_val = cpu_local_entropy[i];
let gpu_val = gpu_local_entropy[i];
if cpu_val.abs() > 1e-6 && gpu_val.abs() > 1e-6 {
assert_relative_eq!(cpu_val, gpu_val, epsilon = 1e-6, max_relative = 1e-5);
}
}
}
println!("{test_name} - CPU and GPU implementations match approximately");
}
}
#[test]
#[cfg(feature = "gpu")]
#[ignore]
fn test_box_kernel_performance() {
let seed = 42;
let sizes = [1000, 10000, 100000];
let bandwidth = 1.0;
let dimensions = [1, 2, 3, 4, 8];
let num_runs = 3;
println!("Measuring box kernel CPU vs GPU performance");
println!("| Dimensions | Size | CPU Time (ms) | GPU Time (ms) | Speedup |");
println!("|------------|------|---------------|---------------|---------|");
for &dim in &dimensions {
for &size in &sizes {
if size >= 100000 && dim >= 4 {
continue;
}
let data = generate_random_nd_data(size, dim, seed);
match dim {
1 => measure_box_kernel_performance::<1>(data, bandwidth, num_runs),
2 => measure_box_kernel_performance::<2>(data, bandwidth, num_runs),
3 => measure_box_kernel_performance::<3>(data, bandwidth, num_runs),
4 => measure_box_kernel_performance::<4>(data, bandwidth, num_runs),
8 => measure_box_kernel_performance::<8>(data, bandwidth, num_runs),
_ => panic!("Unsupported dimension: {dim}"),
};
}
}
}
#[cfg(feature = "gpu")]
fn measure_box_kernel_performance<const K: usize>(
data: Array2<f64>,
bandwidth: f64,
num_runs: usize,
) {
let kernel = Entropy::nd_kernel_with_type::<K>(data.clone(), "box".to_string(), bandwidth);
let mut cpu_total_time = 0.0;
for _ in 0..num_runs {
let duration = measure_execution_time(|| {
let _ = kernel.box_kernel_local_values();
});
cpu_total_time += duration.as_secs_f64() * 1000.0; }
let cpu_avg_time = cpu_total_time / num_runs as f64;
let mut gpu_total_time = 0.0;
for _ in 0..num_runs {
let duration = measure_execution_time(|| {
let _ = kernel.local_values();
});
gpu_total_time += duration.as_secs_f64() * 1000.0; }
let gpu_avg_time = gpu_total_time / num_runs as f64;
let speedup = if gpu_avg_time > 0.0 {
cpu_avg_time / gpu_avg_time
} else {
0.0
};
println!(
"| {K} | {} | {cpu_avg_time:.2} | {gpu_avg_time:.2} | {speedup:.2}x |",
data.nrows()
);
}