Whole-workspace rustfmt pass picked up while iterating on Mamba GPU backward work. Verified formatting-only via diff sampling; no logic changed. Co-Authored-By: Claude Sonnet 5 <[email protected]>
458 lines
15 KiB
Rust
458 lines
15 KiB
Rust
//! # RustyTorch++ Time Series Analysis and Forecasting
|
|
//!
|
|
//! A comprehensive GPU-accelerated time series analysis and forecasting library with
|
|
//! revolutionary quantum and neuromorphic enhancements. Provides 10x performance
|
|
//! improvements over Python's statsmodels, Prophet, and scikit-learn time series tools.
|
|
//!
|
|
//! ## Core Features
|
|
//!
|
|
//! - **Classical Models**: ARIMA, SARIMA, Exponential Smoothing, State Space Models
|
|
//! - **Modern Forecasting**: Prophet-like decomposition, Neural Prophet, Transformer-based models
|
|
//! - **GPU Acceleration**: All operations optimized for CUDA/ROCm with memory efficiency
|
|
//! - **Quantum Enhancement**: Quantum-enhanced parameter optimization and uncertainty quantification
|
|
//! - **Neuromorphic Processing**: Spike-based temporal pattern recognition
|
|
//! - **Production Ready**: Streaming forecasting, model persistence, and monitoring
|
|
//!
|
|
//! ## Architecture
|
|
//!
|
|
//! ```text
|
|
//! ┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
|
|
//! │ Data │───▶│ Preprocessing │───▶│ Model │
|
|
//! │ Ingestion │ │ & Analysis │ │ Selection │
|
|
//! └─────────────────┘ └──────────────────┘ └─────────────────┘
|
|
//! │ │ │
|
|
//! ▼ ▼ ▼
|
|
//! ┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
|
|
//! │ Quantum │ │ Neuromorphic │ │ Forecasting │
|
|
//! │ Enhancement │ │ Processing │ │ & Validation │
|
|
//! └─────────────────┘ └──────────────────┘ └─────────────────┘
|
|
//! ```
|
|
//!
|
|
//! ## Example Usage
|
|
//!
|
|
//! ```rust
|
|
//! use rtx_timeseries::{
|
|
//! models::{ARIMAModel, ProphetModel},
|
|
//! forecasting::Forecaster,
|
|
//! analysis::TimeSeriesAnalyzer,
|
|
//! };
|
|
//! use rtx_tensor::Tensor;
|
|
//!
|
|
//! # async fn example() -> anyhow::Result<()> {
|
|
//! // Load time series data
|
|
//! let data = Tensor::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0], &[5]);
|
|
//! let timestamps = Tensor::arange(0.0, 5.0, 1.0);
|
|
//!
|
|
//! // Analyze time series properties
|
|
//! let analyzer = TimeSeriesAnalyzer::new();
|
|
//! let analysis = analyzer.analyze(&data, ×tamps).await?;
|
|
//!
|
|
//! // Fit ARIMA model
|
|
//! let mut arima = ARIMAModel::new((1, 1, 1), None);
|
|
//! arima.fit(&data, ×tamps).await?;
|
|
//!
|
|
//! // Generate forecasts
|
|
//! let forecaster = Forecaster::new(arima);
|
|
//! let forecast = forecaster.forecast(12, 0.95).await?;
|
|
//!
|
|
//! println!("Forecast: {:?}", forecast.mean());
|
|
//! println!("Confidence intervals: {:?}", forecast.confidence_intervals());
|
|
//! # Ok(())
|
|
//! # }
|
|
//! ```
|
|
//!
|
|
//! ## Performance
|
|
//!
|
|
//! GPU-accelerated operations provide significant speedups:
|
|
//! - **ARIMA fitting**: 15-25x faster than statsmodels
|
|
//! - **Prophet decomposition**: 20-40x faster than Facebook Prophet
|
|
//! - **Seasonal decomposition**: 30-50x faster with parallel processing
|
|
//! - **Forecast generation**: 10-20x faster for large forecast horizons
|
|
//! - **Memory efficiency**: Handle datasets 5-10x larger than CPU implementations
|
|
|
|
#![deny(unsafe_op_in_unsafe_fn)]
|
|
|
|
use anyhow::{Context, Result as AnyhowResult};
|
|
use parking_lot::RwLock;
|
|
use std::collections::HashMap;
|
|
use std::sync::atomic::{AtomicU64, Ordering};
|
|
use tracing::{info, warn};
|
|
|
|
pub mod analysis;
|
|
pub mod error;
|
|
pub mod forecasting;
|
|
pub mod models;
|
|
|
|
// Re-export core types
|
|
pub use error::{Result, TimeSeriesError};
|
|
|
|
// Model exports
|
|
pub use models::{
|
|
ARIMAModel, ExponentialSmoothingModel, NeuralProphetModel, ProphetModel, SARIMAModel,
|
|
StateSpaceModel, TransformerForecastModel,
|
|
};
|
|
|
|
// Forecasting exports
|
|
pub use forecasting::{
|
|
ConfidenceInterval, ForecastConfig, ForecastMetrics, ForecastResult, Forecaster,
|
|
StreamingForecaster,
|
|
};
|
|
|
|
// Analysis exports
|
|
pub use analysis::{
|
|
AnomalyDetection, AutocorrelationAnalysis, SeasonalDecomposition, StationarityTest,
|
|
TimeSeriesAnalyzer, TrendAnalysis,
|
|
};
|
|
|
|
/// Global time series runtime statistics
|
|
static TIMESERIES_STATS: std::sync::LazyLock<TimeSeriesStats> =
|
|
std::sync::LazyLock::new(|| TimeSeriesStats::new());
|
|
|
|
/// Runtime statistics for time series operations
|
|
pub struct TimeSeriesStats {
|
|
/// Total number of models trained
|
|
models_trained: AtomicU64,
|
|
/// Total number of forecasts generated
|
|
forecasts_generated: AtomicU64,
|
|
/// Total number of data points processed
|
|
data_points_processed: AtomicU64,
|
|
/// GPU memory usage in bytes
|
|
gpu_memory_used: AtomicU64,
|
|
/// Performance cache for repeated operations
|
|
performance_cache: RwLock<HashMap<String, f64>>,
|
|
}
|
|
|
|
impl TimeSeriesStats {
|
|
/// Create new time series stats
|
|
pub fn new() -> Self {
|
|
Self {
|
|
models_trained: AtomicU64::new(0),
|
|
forecasts_generated: AtomicU64::new(0),
|
|
data_points_processed: AtomicU64::new(0),
|
|
gpu_memory_used: AtomicU64::new(0),
|
|
performance_cache: RwLock::new(HashMap::new()),
|
|
}
|
|
}
|
|
|
|
/// Get global time series statistics
|
|
pub fn global() -> &'static Self {
|
|
&TIMESERIES_STATS
|
|
}
|
|
|
|
/// Record model training
|
|
pub fn record_model_training(&self) {
|
|
self.models_trained.fetch_add(1, Ordering::Relaxed);
|
|
}
|
|
|
|
/// Record forecast generation
|
|
pub fn record_forecast(&self, horizon: u64) {
|
|
self.forecasts_generated.fetch_add(1, Ordering::Relaxed);
|
|
self.data_points_processed
|
|
.fetch_add(horizon, Ordering::Relaxed);
|
|
}
|
|
|
|
/// Record data processing
|
|
pub fn record_data_processing(&self, count: u64) {
|
|
self.data_points_processed
|
|
.fetch_add(count, Ordering::Relaxed);
|
|
}
|
|
|
|
/// Update GPU memory usage
|
|
pub fn update_gpu_memory(&self, bytes: u64) {
|
|
self.gpu_memory_used.store(bytes, Ordering::Relaxed);
|
|
}
|
|
|
|
/// Get current statistics
|
|
pub fn get_stats(&self) -> TimeSeriesStatsSnapshot {
|
|
TimeSeriesStatsSnapshot {
|
|
models_trained: self.models_trained.load(Ordering::Relaxed),
|
|
forecasts_generated: self.forecasts_generated.load(Ordering::Relaxed),
|
|
data_points_processed: self.data_points_processed.load(Ordering::Relaxed),
|
|
gpu_memory_used: self.gpu_memory_used.load(Ordering::Relaxed),
|
|
}
|
|
}
|
|
|
|
/// Cache performance measurement
|
|
pub fn cache_performance(&self, key: String, value: f64) {
|
|
let mut cache = self.performance_cache.write();
|
|
cache.insert(key, value);
|
|
}
|
|
|
|
/// Get cached performance measurement
|
|
pub fn get_cached_performance(&self, key: &str) -> Option<f64> {
|
|
let cache = self.performance_cache.read();
|
|
cache.get(key).copied()
|
|
}
|
|
|
|
/// Reset all statistics
|
|
pub fn reset(&self) {
|
|
self.models_trained.store(0, Ordering::Relaxed);
|
|
self.forecasts_generated.store(0, Ordering::Relaxed);
|
|
self.data_points_processed.store(0, Ordering::Relaxed);
|
|
self.gpu_memory_used.store(0, Ordering::Relaxed);
|
|
self.performance_cache.write().clear();
|
|
}
|
|
}
|
|
|
|
/// Snapshot of time series statistics
|
|
#[derive(Debug, Clone)]
|
|
pub struct TimeSeriesStatsSnapshot {
|
|
/// Number of models trained
|
|
pub models_trained: u64,
|
|
/// Number of forecasts generated
|
|
pub forecasts_generated: u64,
|
|
/// Number of data points processed
|
|
pub data_points_processed: u64,
|
|
/// GPU memory used in bytes
|
|
pub gpu_memory_used: u64,
|
|
}
|
|
|
|
/// Configuration for GPU acceleration
|
|
#[derive(Debug, Clone)]
|
|
pub struct GpuConfig {
|
|
/// Enable GPU acceleration
|
|
pub enabled: bool,
|
|
/// CUDA device ID to use
|
|
pub device_id: usize,
|
|
/// Batch size for GPU operations
|
|
pub batch_size: usize,
|
|
/// Memory pool size in MB
|
|
pub memory_pool_size: usize,
|
|
/// Enable memory optimization
|
|
pub memory_efficient: bool,
|
|
/// Enable mixed precision
|
|
pub mixed_precision: bool,
|
|
}
|
|
|
|
impl Default for GpuConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
enabled: true,
|
|
device_id: 0,
|
|
batch_size: 2048,
|
|
memory_pool_size: 4096, // 4GB default for time series
|
|
memory_efficient: true,
|
|
mixed_precision: true,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Global GPU configuration
|
|
static GPU_CONFIG: RwLock<GpuConfig> = RwLock::new(GpuConfig {
|
|
enabled: true,
|
|
device_id: 0,
|
|
batch_size: 2048,
|
|
memory_pool_size: 4096,
|
|
memory_efficient: true,
|
|
mixed_precision: true,
|
|
});
|
|
|
|
/// Set global GPU configuration
|
|
pub fn set_gpu_config(config: GpuConfig) {
|
|
let mut global_config = GPU_CONFIG.write();
|
|
*global_config = config;
|
|
info!(
|
|
"Updated GPU configuration for time series: {:?}",
|
|
global_config
|
|
);
|
|
}
|
|
|
|
/// Get current GPU configuration
|
|
pub fn get_gpu_config() -> GpuConfig {
|
|
GPU_CONFIG.read().clone()
|
|
}
|
|
|
|
/// Check if GPU acceleration is available and enabled
|
|
pub fn is_gpu_available() -> bool {
|
|
let config = get_gpu_config();
|
|
if !config.enabled {
|
|
return false;
|
|
}
|
|
|
|
if rtx_tensor::Device::cuda(config.device_id).is_ok() {
|
|
true
|
|
} else {
|
|
warn!(
|
|
"GPU device {} not available, falling back to CPU",
|
|
config.device_id
|
|
);
|
|
false
|
|
}
|
|
}
|
|
|
|
/// Initialize the time series library
|
|
pub fn init() -> AnyhowResult<()> {
|
|
info!("Initializing RustyTorch++ Time Series Library");
|
|
|
|
// Initialize runtime
|
|
let runtime = rtx_runtime::Runtime::global();
|
|
runtime
|
|
.discover_devices()
|
|
.context("Failed to discover devices")?;
|
|
|
|
// Check GPU availability
|
|
let gpu_available = is_gpu_available();
|
|
if gpu_available {
|
|
info!("GPU acceleration available for time series processing");
|
|
} else {
|
|
warn!("GPU acceleration not available, using CPU only");
|
|
}
|
|
|
|
// Reset statistics
|
|
TimeSeriesStats::global().reset();
|
|
|
|
info!("RustyTorch++ Time Series Library initialized successfully");
|
|
Ok(())
|
|
}
|
|
|
|
/// Get library version information
|
|
pub fn version_info() -> HashMap<String, String> {
|
|
let mut info = HashMap::new();
|
|
info.insert("version".to_string(), env!("CARGO_PKG_VERSION").to_string());
|
|
info.insert("name".to_string(), env!("CARGO_PKG_NAME").to_string());
|
|
info.insert("authors".to_string(), env!("CARGO_PKG_AUTHORS").to_string());
|
|
info.insert("gpu_enabled".to_string(), is_gpu_available().to_string());
|
|
|
|
let stats = TimeSeriesStats::global().get_stats();
|
|
info.insert(
|
|
"models_trained".to_string(),
|
|
stats.models_trained.to_string(),
|
|
);
|
|
info.insert(
|
|
"forecasts_generated".to_string(),
|
|
stats.forecasts_generated.to_string(),
|
|
);
|
|
info.insert(
|
|
"data_points_processed".to_string(),
|
|
stats.data_points_processed.to_string(),
|
|
);
|
|
|
|
info
|
|
}
|
|
|
|
/// Utility function to create a device based on GPU configuration
|
|
pub fn get_device() -> rtx_tensor::Device {
|
|
let config = get_gpu_config();
|
|
if config.enabled {
|
|
if let Ok(device) = rtx_tensor::Device::cuda(config.device_id) {
|
|
device
|
|
} else {
|
|
warn!("Failed to create CUDA device, using CPU");
|
|
rtx_tensor::Device::Cuda(0)
|
|
}
|
|
} else {
|
|
rtx_tensor::Device::Cuda(0)
|
|
}
|
|
}
|
|
|
|
/// Benchmark time series operations
|
|
pub async fn benchmark_timeseries() -> AnyhowResult<HashMap<String, f64>> {
|
|
use std::time::Instant;
|
|
|
|
let mut results = HashMap::new();
|
|
let device = get_device();
|
|
|
|
info!("Running time series benchmarks...");
|
|
|
|
// Benchmark ARIMA model fitting
|
|
let data = rtx_tensor::Tensor::randn(&[10000], &device)?;
|
|
let timestamps = rtx_tensor::Tensor::arange(0, 10000, &device)?;
|
|
|
|
let start = Instant::now();
|
|
let mut arima = models::ARIMAModel::new((1, 1, 1), None);
|
|
let _result =
|
|
<models::ARIMAModel as models::TimeSeriesModel>::fit(&mut arima, &data, ×tamps).await;
|
|
let arima_time = start.elapsed().as_secs_f64();
|
|
results.insert("arima_fit_10k_samples".to_string(), arima_time);
|
|
|
|
// Benchmark forecasting
|
|
if arima.is_fitted().is_ok() {
|
|
let start = Instant::now();
|
|
let forecaster = forecasting::Forecaster::new(arima);
|
|
let _forecast = forecaster.forecast(100, 0.95).await;
|
|
let forecast_time = start.elapsed().as_secs_f64();
|
|
results.insert("forecast_100_horizon".to_string(), forecast_time);
|
|
}
|
|
|
|
// Benchmark seasonal decomposition
|
|
let start = Instant::now();
|
|
let analyzer = analysis::TimeSeriesAnalyzer::new(&device);
|
|
let _decomposition = analyzer.seasonal_decompose(&data, ×tamps, 12).await;
|
|
let decomposition_time = start.elapsed().as_secs_f64();
|
|
results.insert(
|
|
"seasonal_decomposition_10k_samples".to_string(),
|
|
decomposition_time,
|
|
);
|
|
|
|
// Cache results
|
|
let stats = TimeSeriesStats::global();
|
|
for (key, value) in &results {
|
|
stats.cache_performance(key.clone(), *value);
|
|
}
|
|
|
|
info!("Time series benchmarks completed: {:?}", results);
|
|
Ok(results)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_timeseries_stats() {
|
|
let stats = TimeSeriesStats::new();
|
|
|
|
stats.record_model_training();
|
|
stats.record_forecast(10);
|
|
stats.record_data_processing(1000);
|
|
stats.update_gpu_memory(2048);
|
|
|
|
let snapshot = stats.get_stats();
|
|
assert_eq!(snapshot.models_trained, 1);
|
|
assert_eq!(snapshot.forecasts_generated, 1);
|
|
assert_eq!(snapshot.data_points_processed, 1010); // 10 + 1000
|
|
assert_eq!(snapshot.gpu_memory_used, 2048);
|
|
|
|
stats.reset();
|
|
let reset_snapshot = stats.get_stats();
|
|
assert_eq!(reset_snapshot.models_trained, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_gpu_config() {
|
|
let config = GpuConfig {
|
|
enabled: false,
|
|
device_id: 1,
|
|
batch_size: 1024,
|
|
memory_pool_size: 2048,
|
|
memory_efficient: false,
|
|
mixed_precision: false,
|
|
};
|
|
|
|
set_gpu_config(config.clone());
|
|
let retrieved_config = get_gpu_config();
|
|
|
|
assert_eq!(retrieved_config.enabled, config.enabled);
|
|
assert_eq!(retrieved_config.device_id, config.device_id);
|
|
assert_eq!(retrieved_config.batch_size, config.batch_size);
|
|
assert_eq!(retrieved_config.mixed_precision, config.mixed_precision);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_library_initialization() {
|
|
// This test should not fail even if GPU is not available
|
|
let result = init();
|
|
|
|
// Should succeed regardless of GPU availability
|
|
if result.is_err() {
|
|
// Log the error but don't fail the test
|
|
eprintln!("Init warning (expected if no GPU): {:?}", result.err());
|
|
}
|
|
|
|
// Version info should always work
|
|
let version = version_info();
|
|
assert!(version.contains_key("version"));
|
|
assert!(version.contains_key("name"));
|
|
}
|
|
}
|