#![recursion_limit = "256"]
#![warn(clippy::pedantic, clippy::nursery, clippy::all)]
#![allow(clippy::multiple_crate_versions, clippy::used_underscore_binding, clippy::similar_names, clippy::module_name_repetitions, clippy::module_inception, clippy::cast_precision_loss)]
use std::sync::LazyLock;
use anyhow::{Result, bail};
use chrono::{DateTime, Utc};
pub use optimizations::{apply_fast_path, cpu_interpolate, parallel_interpolate};
pub use types::{BASE_BATCH_SIZE, Error, POINT_SIZE, Point, Resolution, Spline};
mod gpu;
pub mod helpers; mod optimizations;
mod splines;
#[cfg(test)]
mod tests;
mod types;
#[cfg(doctest)]
#[doc = include_str!("../README.md")]
struct ReadmeDoctests;
#[cfg(feature = "gpu-eager-init")]
#[ctor::ctor(unsafe)]
fn _gpu_startup_init() {
let _ = gpu::force_init_gpu();
}
static _GPU_AUTO_INIT: LazyLock<()> = LazyLock::new(|| {
let _ = gpu::force_init_gpu();
});
#[inline]
fn ensure_gpu_init() {
let () = &*_GPU_AUTO_INIT;
}
pub use gpu::{BufferPoolStats, GpuConfig, gpu_interpolate};
pub use helpers::{InterpolationStrategy, estimate_output_points, generate_target_times, should_use_gpu};
pub use splines::{DAYS_IN_MONTH, DAYS_IN_YEAR, SECONDS_IN_DAY, SECONDS_IN_HOUR, SECONDS_IN_MINUTE, SECONDS_IN_MONTH, SECONDS_IN_WEEK, SECONDS_IN_YEAR};
pub fn prewarm_gpu() -> Result<()> {
gpu::force_init_gpu()
}
pub fn prewarm_gpu_with_config(config: GpuConfig) -> Result<()> {
gpu::types::request_gpu_config(config)?;
gpu::force_init_gpu()
}
#[must_use]
pub fn gpu_config_applied() -> bool {
gpu::types::gpu_config_requested()
}
#[must_use]
pub fn effective_gpu_config() -> GpuConfig {
gpu::types::effective_gpu_config()
}
pub fn gpu_buffer_pool_stats() -> Result<BufferPoolStats> {
Ok(gpu::types::GpuInterpolator::get_buffer_pool_static()?.stats())
}
pub async fn auto_interpolate(points: &mut [Point], start: DateTime<Utc>, end: DateTime<Utc>, resolution: Resolution, spline: Spline) -> Result<Vec<Point>> {
ensure_gpu_init();
if points.is_empty() {
bail!(Error::InsufficientMeasurementsError);
}
if start >= end {
bail!(Error::InvalidTimeRangeError);
}
if points.len() == 1 {
let target_times = helpers::generate_target_times(start, end, resolution);
let constant_value = points[0].value.clone();
return Ok(target_times.into_iter().map(|t| Point { timestamp: t, value: constant_value.clone() }).collect());
}
let effective_spline = select_best_available_spline(spline, points.len());
let estimated_output_points = helpers::estimate_output_points(start, end, resolution);
let effective_spline = apply_fast_path(effective_spline, points.len());
match helpers::should_use_gpu(points.len(), estimated_output_points) {
helpers::InterpolationStrategy::GpuPrimary => {
if let Ok(result) = gpu_interpolate(points, start, end, resolution, effective_spline).await {
return Ok(result);
}
parallel_interpolate(points, &start, &end, effective_spline, resolution).await
}
helpers::InterpolationStrategy::GpuThenParallel => {
let gpu_result = tokio::time::timeout(std::time::Duration::from_secs(10), gpu_interpolate(points, start, end, resolution, effective_spline)).await;
match gpu_result {
Ok(Ok(result)) => Ok(result),
_ => {
parallel_interpolate(points, &start, &end, effective_spline, resolution).await
}
}
}
helpers::InterpolationStrategy::Parallel => {
parallel_interpolate(points, &start, &end, effective_spline, resolution).await
}
helpers::InterpolationStrategy::Cpu => {
cpu_interpolate(points, start, end, resolution, effective_spline).await
}
}
}
#[must_use]
const fn select_best_available_spline(requested: Spline, point_count: usize) -> Spline {
if point_count >= requested.number_of_points_required() {
return requested;
}
match requested {
Spline::Linear => {
Spline::Linear
}
Spline::Cubic | Spline::Quadratic => {
if point_count >= 3 { Spline::Quadratic } else { Spline::Linear }
}
Spline::Polynomial(_degree, _bounds) => {
let max_usable_degree = point_count.saturating_sub(1);
if max_usable_degree > 3 {
requested
} else if max_usable_degree == 3 {
Spline::Cubic
} else if max_usable_degree == 2 {
Spline::Quadratic
} else {
Spline::Linear
}
}
}
}