#[cfg(test)]
mod tests {
use chrono::{DateTime, Utc};
use serial_test::serial;
use crate::{
Resolution, auto_interpolate, gpu::types::GpuInterpolator, gpu_interpolate, helpers::TargetTimesIterator, parallel_interpolate, splines::quadratic, tests::{
linear::tests::{COS_THRESHOLD, POINTS, RESOLUTION, Z_THRESHOLD, check_similarity}, plot_terminal
}
};
#[tokio::test]
#[serial(gpu_tests)]
async fn test_quadratic_interpolation() {
if !crate::tests::gpu_available_or_skip("Quadratic") {
return;
}
GpuInterpolator::clear_buffer_pool_static().expect("Failed to clear GPU buffer pool");
let mut points = POINTS.clone();
let start = {
let s = points.first().map_or_else(Utc::now, |p| p.timestamp);
match RESOLUTION {
Resolution::Nanoseconds => s - chrono::Duration::nanoseconds(10),
Resolution::Microseconds => s - chrono::Duration::microseconds(10),
Resolution::Milliseconds | Resolution::Seconds => s - chrono::Duration::seconds(10),
Resolution::Minutes => s - chrono::Duration::minutes(10),
Resolution::Hours => s - chrono::Duration::hours(10),
Resolution::Days => s - chrono::Duration::days(10),
Resolution::Weeks => s - chrono::Duration::weeks(10),
Resolution::Months => s - chrono::Duration::days(30 * 10),
Resolution::Years => s - chrono::Duration::days(365 * 10),
}
};
let end = {
let e = points.last().map_or_else(Utc::now, |p| p.timestamp);
match RESOLUTION {
Resolution::Nanoseconds => e + chrono::Duration::nanoseconds(10),
Resolution::Microseconds => e + chrono::Duration::microseconds(10),
Resolution::Milliseconds | Resolution::Seconds => e + chrono::Duration::seconds(10),
Resolution::Minutes => e + chrono::Duration::minutes(10),
Resolution::Hours => e + chrono::Duration::hours(10),
Resolution::Days => e + chrono::Duration::days(10),
Resolution::Weeks => e + chrono::Duration::weeks(10),
Resolution::Months => e + chrono::Duration::days(30 * 10),
Resolution::Years => e + chrono::Duration::days(365 * 10),
}
};
let target_times_iter = TargetTimesIterator::new(start, end, RESOLUTION);
let spline = crate::Spline::Quadratic; let input_count = points.len();
let output_count = target_times_iter.estimate_len().unwrap();
println!("Quadratic: Input count: {input_count}, Output count: {output_count}");
println!("Quadratic: CPU Test Starting...");
let timer = tokio::time::Instant::now();
let cpu = quadratic(&mut points, &start, &end, &RESOLUTION).await.unwrap();
let cpu_time = timer.elapsed();
println!("Quadratic: CPU Interpolation took: {cpu_time:?}");
let cpu_len = cpu.len();
let (cpu_timestamps, cpu_values): (Vec<_>, Vec<_>) = cpu.iter().map(|p| (p.timestamp, p.value.clone())).unzip();
plot_terminal("Quadratic: CPU Interpolation Results", cpu.clone()).unwrap();
drop(cpu);
println!("Quadratic: Parallel Test Starting...");
let timer = tokio::time::Instant::now();
let parallel = parallel_interpolate(&mut points, &start, &end, spline, RESOLUTION).await.unwrap();
let parallel_time = timer.elapsed();
println!("Quadratic: Parallel Interpolation took: {parallel_time:?}");
let parallel_len = parallel.len();
let (parallel_timestamps, parallel_values): (Vec<_>, Vec<_>) = parallel.iter().map(|p| (p.timestamp, p.value.clone())).unzip();
plot_terminal("Quadratic: Parallel Interpolation Results", parallel.clone()).unwrap();
drop(parallel);
println!("Quadratic: Comparing CPU and Parallel results...");
assert_eq!(cpu_len, parallel_len);
let (cpu_z_scores, parallel_z_scores, similarity) = check_similarity(&cpu_values, ¶llel_values);
assert!(similarity >= COS_THRESHOLD, "Cosine similarity is below threshold: {COS_THRESHOLD}, similarity: {similarity}");
assert!(similarity >= COS_THRESHOLD, "Cosine similarity is below threshold: {COS_THRESHOLD}, similarity: {similarity}");
for (i, (cpu_z, parallel_z)) in cpu_z_scores.iter().zip(parallel_z_scores.iter()).enumerate() {
assert!(cpu_z.abs() < Z_THRESHOLD, "CPU value at index {i} is an outlier: {cpu_z}");
assert!(parallel_z.abs() < Z_THRESHOLD, "Parallel value at index {i} is an outlier: {parallel_z}");
}
assert_eq!(cpu_timestamps, parallel_timestamps);
drop(parallel_timestamps);
drop(parallel_values);
println!("Quadratic: GPU Test Starting...");
let timer = tokio::time::Instant::now();
let gpu = gpu_interpolate(&mut points, start, end, RESOLUTION, spline).await.unwrap();
let gpu_time = timer.elapsed();
println!("Quadratic: GPU Interpolation took: {gpu_time:?}");
let gpu_len = gpu.len();
let (gpu_timestamps, gpu_values): (Vec<_>, Vec<_>) = gpu.iter().map(|p| (p.timestamp, p.value.clone())).unzip();
plot_terminal("Quadratic: GPU Interpolation Results", gpu.clone()).unwrap();
drop(gpu);
println!("Quadratic: Comparing CPU and GPU results...");
assert_eq!(cpu_len, gpu_len);
let (cpu_z_scores, gpu_z_scores, similarity) = check_similarity(&cpu_values, &gpu_values);
assert!(similarity >= COS_THRESHOLD, "GPU: Cosine similarity is below threshold: {similarity}");
for (i, (cpu_z, gpu_z)) in cpu_z_scores.iter().zip(gpu_z_scores.iter()).enumerate() {
assert!(cpu_z.abs() < Z_THRESHOLD, "CPU value at index {i} is an outlier: {cpu_z}");
assert!(gpu_z.abs() < Z_THRESHOLD, "GPU value at index {i} is an outlier: {gpu_z}");
}
assert_eq!(cpu_timestamps, gpu_timestamps);
drop(gpu_timestamps);
drop(gpu_values);
println!("Quadratic: Auto Test Starting...");
let auto_timer = tokio::time::Instant::now();
let auto = auto_interpolate(&mut points, start, end, RESOLUTION, spline).await.unwrap();
let auto_time = auto_timer.elapsed();
println!("Quadratic: Auto Interpolation took: {auto_time:?}");
let auto_len = auto.len();
let (auto_timestamps, auto_values): (Vec<_>, Vec<_>) = auto.iter().map(|p| (p.timestamp, p.value.clone())).unzip();
plot_terminal("Quadratic: Auto Interpolation Results", auto.clone()).unwrap();
drop(auto);
println!("Quadratic: Comparing CPU and Auto results...");
assert_eq!(cpu_len, auto_len);
let (cpu_z_scores, auto_z_scores, similarity) = check_similarity(&cpu_values, &auto_values);
assert!(similarity >= COS_THRESHOLD, "Auto: Cosine similarity is below threshold: {similarity}");
for (i, (cpu_z, auto_z)) in cpu_z_scores.iter().zip(auto_z_scores.iter()).enumerate() {
assert!(cpu_z.abs() < Z_THRESHOLD, "CPU value at index {i} is an outlier: {cpu_z}");
assert!(auto_z.abs() < Z_THRESHOLD, "Auto value at index {i} is an outlier: {auto_z}");
}
assert_eq!(cpu_timestamps, auto_timestamps);
drop(auto_timestamps);
drop(auto_values);
if auto_time < gpu_time && auto_time < cpu_time && auto_time < parallel_time {
let percentage_difference = (gpu_time.checked_sub(auto_time).unwrap().as_nanos() as f64 / auto_time.as_nanos() as f64) * 100.0;
println!("Quadratic: Auto was faster by {percentage_difference:.2}%");
} else if gpu_time < auto_time && gpu_time < cpu_time && gpu_time < parallel_time {
let percentage_difference = (auto_time.checked_sub(gpu_time).unwrap().as_nanos() as f64 / gpu_time.as_nanos() as f64) * 100.0;
println!("Quadratic: GPU was faster by {percentage_difference:.2}%");
} else if cpu_time < auto_time && cpu_time < gpu_time && cpu_time < parallel_time {
let percentage_difference = (auto_time.checked_sub(cpu_time).unwrap().as_nanos() as f64 / cpu_time.as_nanos() as f64) * 100.0;
println!("Quadratic: CPU was faster by {percentage_difference:.2}%");
} else if parallel_time < auto_time && parallel_time < gpu_time && parallel_time < cpu_time && parallel_time < parallel_time {
let percentage_difference = (auto_time.checked_sub(parallel_time).unwrap().as_nanos() as f64 / parallel_time.as_nanos() as f64) * 100.0;
println!("Quadratic: SIMD was faster by {percentage_difference:.2}%");
} else {
let st = parallel_time.as_nanos();
let gt = gpu_time.as_nanos();
let dif = gt.abs_diff(st);
let percentage_difference = (dif as f64 / gpu_time.as_nanos() as f64) * 100.0;
println!("Quadratic: Parallel SIMD was faster by {percentage_difference:.2}%");
}
}
#[tokio::test]
#[serial(gpu_tests)]
async fn test_target_times() {
let points = POINTS.clone();
let start = {
let s = points.first().map_or_else(Utc::now, |p| p.timestamp);
match RESOLUTION {
Resolution::Nanoseconds => s - chrono::Duration::nanoseconds(10),
Resolution::Microseconds => s - chrono::Duration::microseconds(10),
Resolution::Milliseconds | Resolution::Seconds => s - chrono::Duration::seconds(10),
Resolution::Minutes => s - chrono::Duration::minutes(10),
Resolution::Hours => s - chrono::Duration::hours(10),
Resolution::Days => s - chrono::Duration::days(10),
Resolution::Weeks => s - chrono::Duration::weeks(10),
Resolution::Months => s - chrono::Duration::days(30 * 10),
Resolution::Years => s - chrono::Duration::days(365 * 10),
}
};
let end = {
let e = points.last().map_or_else(Utc::now, |p| p.timestamp);
match RESOLUTION {
Resolution::Nanoseconds => e + chrono::Duration::nanoseconds(10),
Resolution::Microseconds => e + chrono::Duration::microseconds(10),
Resolution::Milliseconds | Resolution::Seconds => e + chrono::Duration::seconds(10),
Resolution::Minutes => e + chrono::Duration::minutes(10),
Resolution::Hours => e + chrono::Duration::hours(10),
Resolution::Days => e + chrono::Duration::days(10),
Resolution::Weeks => e + chrono::Duration::weeks(10),
Resolution::Months => e + chrono::Duration::days(30 * 10),
Resolution::Years => e + chrono::Duration::days(365 * 10),
}
};
let target_times_1: Vec<DateTime<Utc>> = TargetTimesIterator::new(start, end, RESOLUTION).flatten().collect();
let target_times_2: Vec<DateTime<Utc>> = TargetTimesIterator::new(start, end, RESOLUTION).flatten().collect();
println!("Target times 1: {}, Target times 2: {}", target_times_1.len(), target_times_2.len());
assert_eq!(target_times_1, target_times_2, "Target times iterators should produce the same results");
}
}