mod common;
use chrono::{DateTime, TimeDelta, Utc};
use common::epoch;
use splimes::{Backend, GpuConfig, GpuPoolStats, Interpolator, Precision, Resolution, Spline};
const CHUNK: u32 = 1_024;
const fn set_bytes(precision: Precision, capacity: u64) -> u64 {
let value = if matches!(precision, Precision::F64) { 8 } else { 4 };
capacity * (2 * value + 12) + 64
}
const CAP: u64 = set_bytes(Precision::F32, CHUNK as u64) + set_bytes(Precision::F32, 128);
fn series() -> (Vec<DateTime<Utc>>, Vec<f64>) {
let ts = (0..40).map(|i| epoch() + TimeDelta::microseconds(i * 100 + (i * 37) % 60)).collect();
let vs = (0..40).map(|i| (f64::from(i) * 0.3).sin() * 10.0).collect();
(ts, vs)
}
fn call(precision: Precision, points: i64) -> GpuPoolStats {
let (ts, vs) = series();
let (start, end) = (ts[0], ts[0] + TimeDelta::microseconds(points - 1));
let make = |backend| Interpolator::new(Spline::Cubic, Resolution::Microseconds).backend(backend).gpu_precision(precision);
let gpu = make(Backend::Gpu).run_f64(&ts, &vs, start, end).expect("gpu");
let cpu = make(Backend::Cpu).run_f64(&ts, &vs, start, end).expect("cpu");
assert_eq!(gpu.backend(), Backend::Gpu);
assert_eq!(gpu.len(), usize::try_from(points).expect("small"));
let tolerance = if precision == Precision::F64 { 1e-9 } else { 1e-3 };
for (i, (g, c)) in gpu.values().iter().zip(cpu.values()).enumerate() {
assert!((g - c).abs() <= tolerance, "{precision} {points} points, point {i}: gpu {g} vs cpu {c}");
}
let stats = splimes::gpu_pool_stats().expect("GPU started");
assert!(stats.idle_bytes <= CAP, "the idle sets stay under the cap: {stats:?}");
stats
}
fn expect(stats: GpuPoolStats, idle_sets: usize, idle_bytes: u64, created: u64, reused: u64, evicted: u64, step: &str) {
let want = (idle_sets, idle_bytes, created, reused, evicted);
assert_eq!((stats.idle_sets, stats.idle_bytes, stats.created, stats.reused, stats.evicted), want, "{step}: {stats:?}, want (idle_sets, idle_bytes, created, reused, evicted) = {want:?}");
}
#[test]
fn pool_accounting_is_exact() {
splimes::configure_gpu(GpuConfig::default().with_chunk_points(CHUNK).with_max_pool_bytes(CAP)).expect("first configuration succeeds");
let Some(info) = common::gpu_or_skip("pool_accounting_is_exact") else { return };
let (f32_small, f32_chunk) = (set_bytes(Precision::F32, 128), set_bytes(Precision::F32, CHUNK.into()));
expect(splimes::gpu_pool_stats().expect("GPU started"), 0, 0, 0, 0, 0, "fresh pool");
expect(call(Precision::F32, 100), 1, f32_small, 1, 0, 0, "first small call");
expect(call(Precision::F32, 3 * i64::from(CHUNK)), 2, CAP, 3, 0, 1, "three-chunk call");
expect(call(Precision::F32, 100), 2, CAP, 3, 1, 1, "second small call");
expect(call(Precision::F32, i64::from(CHUNK) + 1), 2, CAP, 3, 3, 1, "chunk-and-one call");
if !info.supports_f64 {
eprintln!("pool_accounting_is_exact: {} has no f64; the f64 half is skipped", info.name);
return;
}
let f64_512 = set_bytes(Precision::F64, 512);
let kept = f32_small + f64_512;
assert!(kept + f32_chunk > CAP && kept <= CAP);
expect(call(Precision::F64, 300), 2, kept, 4, 3, 2, "300-point f64 call");
expect(call(Precision::F64, 100), 2, kept, 4, 4, 2, "100-point f64 call");
assert!(kept + set_bytes(Precision::F64, CHUNK.into()) > CAP);
expect(call(Precision::F64, 3 * i64::from(CHUNK)), 2, kept, 6, 4, 4, "three-chunk f64 call");
}