Skip to main content

torsh_sparse/
lib.rs

1//! Sparse tensor operations for ToRSh
2//!
3//! This crate provides comprehensive sparse tensor representations and operations,
4//! supporting multiple sparse formats including COO, CSR, CSC, BSR, DIA, ELL, DSR, and RLE.
5//!
6//! ## Key Features
7//!
8//! - **Multiple Sparse Formats**: Support for COO, CSR, CSC, BSR, DIA, ELL, DSR, and RLE formats
9//! - **Automatic Format Selection**: Intelligent format selection based on sparsity patterns
10//! - **Neural Network Integration**: Sparse layers, optimizers, and activation functions
11//! - **GPU Acceleration**: CUDA support for sparse operations
12//! - **Memory Management**: Advanced memory pooling and optimization
13//! - **Interoperability**: Integration with SciPy, MATLAB, and HDF5
14//! - **Performance Tools**: Profiling, benchmarking, and autotuning capabilities
15//!
16//! ## Usage Examples
17//!
18//! ```rust,no_run
19//! use torsh_sparse::{CooTensor, CsrTensor, SparseFormat, SparseTensor};
20//! use torsh_core::Shape;
21//!
22//! // Create a COO tensor from triplets
23//! let triplets = vec![(0, 0, 1.0f32), (1, 1, 2.0f32), (2, 2, 3.0f32)];
24//! let coo = CooTensor::from_triplets(triplets, (3, 3)).expect("valid triplets");
25//!
26//! // Convert to CSR format for efficient row operations
27//! let csr = coo.to_csr().expect("COO to CSR conversion");
28//!
29//! // Perform sparse operations
30//! let result = csr.transpose().expect("transpose");
31//! ```
32//!
33//! ## Performance Considerations
34//!
35//! - **COO**: Best for construction and format conversion
36//! - **CSR**: Optimized for row-based operations and matrix-vector multiplication
37//! - **CSC**: Optimized for column-based operations
38//! - **BSR**: Efficient for block-structured sparse matrices
39//! - **DIA**: Memory-efficient for diagonal-dominant matrices
40//! - **ELL**: SIMD-friendly format for GPU operations
41
42// Version information
43pub const VERSION: &str = env!("CARGO_PKG_VERSION");
44pub const VERSION_MAJOR: u32 = 0;
45pub const VERSION_MINOR: u32 = 1;
46pub const VERSION_PATCH: u32 = 0;
47
48use std::collections::HashMap;
49use torsh_core::{DType, DeviceType, Result, Shape, TorshError};
50use torsh_tensor::Tensor;
51
52/// Convenience type alias for Results in this crate
53pub type TorshResult<T> = Result<T>;
54
55pub mod autograd;
56pub mod bsr;
57pub mod conversions;
58pub mod coo;
59pub mod csc;
60pub mod csr;
61pub mod custom_kernels;
62pub mod dia;
63pub mod dsr;
64pub mod ell;
65pub mod gpu;
66pub mod hdf5_support;
67pub mod hybrid;
68pub mod layers;
69pub mod linalg;
70pub mod matlab_compat;
71/// MATLAB Level-5 MAT-file container used by [`matlab_compat`].
72#[cfg(feature = "matlab")]
73pub mod matlab_mat5;
74pub mod matrix_market;
75pub mod memory_management;
76pub mod nn;
77pub mod ops;
78pub mod optimizers;
79pub mod pattern_analysis;
80pub mod performance_tools;
81pub mod rle;
82pub mod scipy_sparse;
83pub mod scirs2_integration;
84
85// Enhanced SciRS2 integration
86#[cfg(feature = "scirs2-integration")]
87pub mod scirs2_sparse_integration;
88pub mod symmetric;
89pub mod unified_interface;
90
91// Re-exports
92pub use bsr::BsrTensor;
93pub use coo::CooTensor;
94pub use csc::CscTensor;
95pub use csr::CsrTensor;
96pub use dia::DiaTensor;
97pub use dsr::DsrTensor;
98pub use ell::EllTensor;
99pub use rle::RleTensor;
100pub use symmetric::{SymmetricMode, SymmetricTensor};
101
102// GPU support
103pub use gpu::{CudaSparseOps, CudaSparseTensor, CudaSparseTensorFactory};
104
105// Autograd support
106pub use autograd::{SparseAutogradTensor, SparseData, SparseGradFn, SparseGradientAccumulator};
107
108// SciRS2 integration: native sparse arithmetic + optional scirs2-sparse interop
109pub use scirs2_integration::{scirs2_add, scirs2_enhanced_ops};
110
111// Enhanced SciRS2 sparse integration
112#[cfg(feature = "scirs2-integration")]
113pub use scirs2_sparse_integration::{
114    create_gpu_sparse_processor, create_nn_sparse_processor, create_sparse_processor,
115    SciRS2SparseProcessor, SparseConfig as ScirsSparseConfig,
116};
117
118// Neural network layers and optimizers
119pub use nn::{
120    // Type aliases for convenience
121    Format,
122    GraphConvolution,
123    InitConfig,
124    LayerConfig,
125    SparseAdam,
126    SparseAttention,
127    // Advanced layer implementations
128    SparseConv2d,
129    SparseConverter,
130    SparseEmbedding,
131    SparseEmbeddingStats,
132    // Configuration types
133    // SparseFormat, // Defined locally to avoid conflict
134    SparseInitConfig,
135    SparseLayer,
136    SparseLayerConfig,
137    // Layer implementations
138    SparseLinear,
139    SparseMemoryStats,
140    // Core traits
141    SparseOptimizer,
142    SparsePatternAnalysis,
143    // Optimizers
144    SparseSGD,
145    SparseStats,
146    // Utilities
147    SparseWeightGenerator,
148};
149
150// Hybrid formats and utilities
151pub use hybrid::{auto_select_format, HybridTensor, PartitionStrategy, SparsityPattern};
152
153// Pattern analysis utilities
154pub use pattern_analysis::{
155    AdvancedSparsityPattern, ClusteringAlgorithm, MatrixReorderer, PatternAnalyzer,
156    PatternStatistics, PatternVisualizer, ReorderingAlgorithm,
157};
158
159// Performance tools
160pub use performance_tools::{
161    AutoTuner, BenchmarkConfig, CachePerformanceResult, HardwareBenchmark, MemoryAnalysis,
162    OperationStatistics, PerformanceExporter, PerformanceMeasurement, PerformanceReport, PlotData,
163    SparseProfiler, SystemInfo, TensorBoardExporter, TrendAnalysis, TrendAnalyzer, TrendDirection,
164};
165
166// Matrix Market I/O
167pub use matrix_market::{
168    MatrixMarketField, MatrixMarketFormat, MatrixMarketHeader, MatrixMarketIO, MatrixMarketObject,
169    MatrixMarketSize, MatrixMarketSymmetry, MatrixMarketUtils,
170};
171
172// Custom optimized kernels
173pub use custom_kernels::{
174    ElementWiseKernels, FormatConversionKernels, KernelDispatcher, ReductionKernels,
175    SparseMatMulKernels,
176};
177
178// SciPy sparse interoperability
179pub use scipy_sparse::{ScipyFormat, ScipySparseData, ScipySparseIntegration};
180
181// MATLAB sparse interoperability
182pub use matlab_compat::{
183    export_to_matlab_script, matlab_sparse_from_triplets, MatlabSparseCompat, MatlabSparseMatrix,
184};
185
186// HDF5 sparse interoperability
187pub use hdf5_support::{load_sparse_matrix, save_sparse_matrix, Hdf5SparseIO, Hdf5SparseMetadata};
188
189// Unified sparse tensor interface
190pub use unified_interface::{
191    AccessPatterns, MemoryStats, OptimizationConfig, OptimizationFlags, OptimizationReport,
192    PerformanceHints, PerformanceSummary, TensorMetadata, UnifiedSparseTensor,
193    UnifiedSparseTensorFactory,
194};
195
196// Memory management
197pub use memory_management::{
198    create_sparse_with_memory_management, MemoryAwareSparseBuilder, MemoryPoolConfig, MemoryReport,
199    MemoryStatistics, SparseMemoryHandle, SparseMemoryManager, SparseMemoryPool,
200};
201
202// Conversion utilities
203pub use conversions::{direct_conversions, optimization, patterns, validation, ConversionHints};
204
205/// Layout format for sparse tensors
206///
207/// Different sparse formats are optimized for different use cases and access patterns.
208/// Choose the appropriate format based on your matrix characteristics and operations.
209#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
210pub enum SparseFormat {
211    /// Coordinate format (COO) - stores (row, col, value) triplets
212    ///
213    /// **Best for**: Matrix construction, format conversion, random insertion
214    /// **Memory**: 3 * nnz storage (row indices, col indices, values)
215    /// **Operations**: Efficient addition, inefficient matrix-vector multiplication
216    Coo,
217
218    /// Compressed Sparse Row (CSR) - row-oriented compressed format
219    ///
220    /// **Best for**: Matrix-vector multiplication, row slicing, iterating by rows
221    /// **Memory**: (nnz + n + 1) storage (values, col_indices, row_ptr)
222    /// **Operations**: Fast row access, efficient SpMV, slow column access
223    Csr,
224
225    /// Compressed Sparse Column (CSC) - column-oriented compressed format
226    ///
227    /// **Best for**: Matrix-vector multiplication (A^T * x), column slicing
228    /// **Memory**: (nnz + m + 1) storage (values, row_indices, col_ptr)
229    /// **Operations**: Fast column access, efficient transpose operations
230    Csc,
231
232    /// Block Sparse Row (BSR) - stores dense blocks in sparse locations
233    ///
234    /// **Best for**: Matrices with dense block structure, finite element methods
235    /// **Memory**: Efficient for matrices with natural block structure
236    /// **Operations**: BLAS-optimized operations on dense blocks
237    Bsr,
238
239    /// Diagonal format (DIA) - stores diagonals efficiently
240    ///
241    /// **Best for**: Matrices with few non-zero diagonals, finite difference schemes
242    /// **Memory**: Very compact for diagonal-dominant matrices
243    /// **Operations**: Fast diagonal operations, limited to diagonal patterns
244    Dia,
245
246    /// Dynamic Sparse Row (DSR) - dynamic insertion/deletion support
247    ///
248    /// **Best for**: Matrices that change structure frequently during computation
249    /// **Memory**: Tree-based storage allows dynamic modifications
250    /// **Operations**: Efficient insertion/deletion, slower than static formats
251    Dsr,
252
253    /// ELLPACK format (ELL) - fixed-width row storage
254    ///
255    /// **Best for**: GPU operations, SIMD vectorization, matrices with uniform row density
256    /// **Memory**: Can have significant overhead for irregular matrices
257    /// **Operations**: SIMD-friendly, efficient on parallel architectures
258    Ell,
259
260    /// Run-Length Encoded format (RLE) - compresses consecutive zeros
261    ///
262    /// **Best for**: Matrices with long runs of consecutive non-zeros
263    /// **Memory**: Excellent compression for specific patterns
264    /// **Operations**: Specialized for pattern-specific matrices
265    Rle,
266
267    /// Symmetric sparse format (SYM) - stores only lower/upper triangle
268    ///
269    /// **Best for**: Symmetric matrices from finite element analysis, optimization
270    /// **Memory**: Roughly half the storage of equivalent full format
271    /// **Operations**: Specialized symmetric operations, automatic symmetry enforcement
272    Symmetric,
273}
274
275/// Trait for sparse tensor operations
276///
277/// This trait provides a unified interface for all sparse tensor formats,
278/// enabling polymorphic operations and seamless format conversions.
279///
280/// ## Implementation Notes
281///
282/// All sparse tensor types implement this trait, allowing for:
283/// - Format-agnostic operations through trait objects
284/// - Automatic format selection based on operation requirements
285/// - Efficient conversion between different sparse representations
286///
287/// ## Example
288///
289/// ```rust,no_run
290/// use torsh_sparse::{SparseTensor, CooTensor};
291///
292/// fn analyze_sparsity(tensor: &dyn SparseTensor) -> f32 {
293///     tensor.sparsity()
294/// }
295///
296/// let coo = CooTensor::from_triplets(vec![(0, 0, 1.0)], (10, 10)).expect("valid triplets");
297/// println!("Sparsity: {:.2}%", analyze_sparsity(&coo) * 100.0);
298/// ```
299pub trait SparseTensor {
300    /// Get the sparse format used by this tensor
301    ///
302    /// Returns the specific sparse format (COO, CSR, CSC, etc.) that this tensor uses internally.
303    /// This can be used for format-specific optimizations or debugging.
304    fn format(&self) -> SparseFormat;
305
306    /// Get the shape of the tensor
307    ///
308    /// Returns a reference to the tensor's shape, which describes its dimensions.
309    /// For sparse matrices, this is typically a 2D shape [rows, cols].
310    fn shape(&self) -> &Shape;
311
312    /// Get the data type of the tensor elements
313    ///
314    /// Returns the DType (typically F32 for f32 values) used to store the non-zero elements.
315    fn dtype(&self) -> DType;
316
317    /// Get the device where the tensor is stored
318    ///
319    /// Returns the device type (CPU, CUDA, etc.) where the tensor data resides.
320    fn device(&self) -> DeviceType;
321
322    /// Get the number of non-zero elements
323    ///
324    /// Returns the count of explicitly stored non-zero values. Note that this may include
325    /// some actual zeros that are explicitly stored in the sparse representation.
326    fn nnz(&self) -> usize;
327
328    /// Convert to dense tensor representation
329    ///
330    /// Creates a full dense tensor with all zeros filled in. This can be memory-intensive
331    /// for large sparse matrices. Use with caution for matrices with high dimensions.
332    ///
333    /// # Performance Note
334    /// This operation has O(m*n) memory complexity and should be avoided for large matrices.
335    fn to_dense(&self) -> TorshResult<Tensor>;
336
337    /// Convert to COO (Coordinate) format
338    ///
339    /// Converts to COO format, which stores explicit (row, col, value) triplets.
340    /// This is useful for format conversion and matrix construction operations.
341    fn to_coo(&self) -> TorshResult<CooTensor>;
342
343    /// Convert to CSR (Compressed Sparse Row) format
344    ///
345    /// Converts to CSR format, which is optimized for row-wise operations and
346    /// matrix-vector multiplication (Ax).
347    fn to_csr(&self) -> TorshResult<CsrTensor>;
348
349    /// Convert to CSC (Compressed Sparse Column) format
350    ///
351    /// Converts to CSC format, which is optimized for column-wise operations and
352    /// matrix-vector multiplication with transposed matrices (A^T x).
353    fn to_csc(&self) -> TorshResult<CscTensor>;
354
355    /// Calculate sparsity ratio (fraction of zero elements)
356    ///
357    /// Returns a value between 0.0 and 1.0, where:
358    /// - 0.0 = completely dense (no zeros)
359    /// - 1.0 = completely sparse (all zeros)
360    ///
361    /// Formula: sparsity = 1.0 - (nnz / total_elements)
362    ///
363    /// # Example
364    /// ```rust,no_run
365    /// # use torsh_sparse::{SparseTensor, CooTensor};
366    /// let tensor = CooTensor::from_triplets(vec![(0, 0, 1.0)], (10, 10)).expect("valid triplets");
367    /// assert_eq!(tensor.sparsity(), 0.99); // 99% sparse (1 non-zero out of 100 elements)
368    /// ```
369    fn sparsity(&self) -> f32 {
370        let total_elements = self.shape().numel();
371        if total_elements == 0 {
372            0.0
373        } else {
374            1.0 - (self.nnz() as f32 / total_elements as f32)
375        }
376    }
377
378    /// Cast as Any for downcasting to concrete types
379    fn as_any(&self) -> &dyn std::any::Any;
380}
381
382/// Create a sparse tensor from a dense tensor
383pub fn sparse_from_dense(
384    dense: &Tensor,
385    format: SparseFormat,
386    threshold: Option<f32>,
387) -> TorshResult<Box<dyn SparseTensor + Send + Sync>> {
388    let threshold = threshold.unwrap_or(0.0);
389
390    match format {
391        SparseFormat::Coo => {
392            let coo = CooTensor::from_dense(dense, threshold)?;
393            Ok(Box::new(coo))
394        }
395        SparseFormat::Csr => {
396            let csr = CsrTensor::from_dense(dense, threshold)?;
397            Ok(Box::new(csr))
398        }
399        SparseFormat::Csc => {
400            let csc = CscTensor::from_dense(dense, threshold)?;
401            Ok(Box::new(csc))
402        }
403        SparseFormat::Bsr => {
404            // For BSR, use a default block size of 2x2
405            let coo = CooTensor::from_dense(dense, threshold)?;
406            let bsr = BsrTensor::from_coo(&coo, (2, 2))?;
407            Ok(Box::new(bsr))
408        }
409        SparseFormat::Dia => {
410            let dia = DiaTensor::from_dense(dense, threshold)?;
411            Ok(Box::new(dia))
412        }
413        SparseFormat::Dsr => {
414            let dsr = DsrTensor::from_dense(dense, threshold)?;
415            Ok(Box::new(dsr))
416        }
417        SparseFormat::Ell => {
418            let ell = EllTensor::from_dense(dense, threshold)?;
419            Ok(Box::new(ell))
420        }
421        SparseFormat::Rle => {
422            let rle = RleTensor::from_dense(dense, threshold)?;
423            Ok(Box::new(rle))
424        }
425        SparseFormat::Symmetric => {
426            let sym = SymmetricTensor::from_dense(dense, SymmetricMode::Upper, threshold)?;
427            Ok(Box::new(sym))
428        }
429    }
430}
431
432/// Automatically create the optimal sparse tensor format from a dense tensor
433///
434/// This function analyzes the sparsity pattern and selects the most efficient
435/// sparse format for the given tensor.
436pub fn sparse_auto_from_dense(
437    dense: &Tensor,
438    threshold: Option<f32>,
439) -> TorshResult<Box<dyn SparseTensor + Send + Sync>> {
440    let threshold = threshold.unwrap_or(0.0);
441    let optimal_format = hybrid::auto_select_format(dense, threshold)?;
442    sparse_from_dense(dense, optimal_format, Some(threshold))
443}
444
445/// Create a hybrid sparse tensor with automatic partitioning
446///
447/// This creates a hybrid tensor that can use different sparse formats
448/// for different regions of the matrix, optimizing for both storage and computation.
449pub fn sparse_hybrid_from_dense(
450    dense: &Tensor,
451    strategy: PartitionStrategy,
452    threshold: Option<f32>,
453) -> TorshResult<HybridTensor> {
454    let threshold = threshold.unwrap_or(0.0);
455    let coo = CooTensor::from_dense(dense, threshold)?;
456    HybridTensor::from_sparse(coo, strategy)
457}
458
459/// Format selection configuration for advanced users
460#[derive(Debug, Clone)]
461pub struct FormatConfig {
462    /// Threshold for considering elements as zero
463    pub threshold: f32,
464    /// Minimum density to consider a region as dense
465    pub dense_threshold: f32,
466    /// Block size for block-based analysis
467    pub block_size: (usize, usize),
468    /// Whether to enable hybrid format selection
469    pub enable_hybrid: bool,
470    /// Whether to analyze sparsity patterns
471    pub analyze_patterns: bool,
472}
473
474impl Default for FormatConfig {
475    fn default() -> Self {
476        Self {
477            threshold: 0.0,
478            dense_threshold: 0.1,
479            block_size: (32, 32),
480            enable_hybrid: false,
481            analyze_patterns: true,
482        }
483    }
484}
485
486impl FormatConfig {
487    /// Create a configuration optimized for memory efficiency
488    pub fn memory_optimized() -> Self {
489        Self {
490            threshold: 1e-12,
491            dense_threshold: 0.05,
492            block_size: (16, 16),
493            enable_hybrid: true,
494            analyze_patterns: true,
495        }
496    }
497
498    /// Create a configuration optimized for computational performance
499    pub fn performance_optimized() -> Self {
500        Self {
501            threshold: 1e-8,
502            dense_threshold: 0.2,
503            block_size: (64, 64),
504            enable_hybrid: false,
505            analyze_patterns: false,
506        }
507    }
508
509    /// Validate the configuration parameters
510    pub fn validate(&self) -> TorshResult<()> {
511        if self.threshold < 0.0 {
512            return Err(TorshError::InvalidArgument(
513                "Threshold must be non-negative".to_string(),
514            ));
515        }
516
517        if self.dense_threshold < 0.0 || self.dense_threshold > 1.0 {
518            return Err(TorshError::InvalidArgument(
519                "Dense threshold must be between 0.0 and 1.0".to_string(),
520            ));
521        }
522
523        if self.block_size.0 == 0 || self.block_size.1 == 0 {
524            return Err(TorshError::InvalidArgument(
525                "Block size dimensions must be positive".to_string(),
526            ));
527        }
528
529        Ok(())
530    }
531
532    /// Create a configuration with custom threshold
533    pub fn with_threshold(mut self, threshold: f32) -> Self {
534        self.threshold = threshold;
535        self
536    }
537
538    /// Create a configuration with custom block size
539    pub fn with_block_size(mut self, block_size: (usize, usize)) -> Self {
540        self.block_size = block_size;
541        self
542    }
543
544    /// Enable or disable hybrid format selection
545    pub fn with_hybrid(mut self, enable: bool) -> Self {
546        self.enable_hybrid = enable;
547        self
548    }
549}
550
551/// Advanced sparse tensor creation with detailed configuration
552pub fn sparse_from_dense_with_config(
553    dense: &Tensor,
554    config: FormatConfig,
555) -> TorshResult<Box<dyn SparseTensor + Send + Sync>> {
556    // Validate configuration first
557    config.validate()?;
558
559    if config.enable_hybrid {
560        let strategy = if config.analyze_patterns {
561            PartitionStrategy::PatternBased
562        } else {
563            PartitionStrategy::BlockBased {
564                block_size: config.block_size,
565            }
566        };
567
568        let hybrid = sparse_hybrid_from_dense(dense, strategy, Some(config.threshold))?;
569        Ok(Box::new(hybrid))
570    } else {
571        sparse_auto_from_dense(dense, Some(config.threshold))
572    }
573}
574
575/// Utility to convert between sparse formats
576pub fn convert_sparse_format(
577    sparse: &dyn SparseTensor,
578    target_format: SparseFormat,
579) -> TorshResult<Box<dyn SparseTensor + Send + Sync>> {
580    match target_format {
581        SparseFormat::Coo => Ok(Box::new(sparse.to_coo()?)),
582        SparseFormat::Csr => Ok(Box::new(sparse.to_csr()?)),
583        SparseFormat::Csc => Ok(Box::new(sparse.to_csc()?)),
584        SparseFormat::Bsr => {
585            let coo = sparse.to_coo()?;
586            let bsr = BsrTensor::from_coo(&coo, (2, 2))?;
587            Ok(Box::new(bsr))
588        }
589        SparseFormat::Dia => {
590            let coo = sparse.to_coo()?;
591            let dia = DiaTensor::from_coo(&coo)?;
592            Ok(Box::new(dia))
593        }
594        SparseFormat::Dsr => {
595            let coo = sparse.to_coo()?;
596            let dsr = DsrTensor::from_coo(&coo)?;
597            Ok(Box::new(dsr))
598        }
599        SparseFormat::Ell => {
600            let coo = sparse.to_coo()?;
601            let ell = EllTensor::from_coo(&coo)?;
602            Ok(Box::new(ell))
603        }
604        SparseFormat::Rle => {
605            let coo = sparse.to_coo()?;
606            let rle = RleTensor::from_coo(&coo)?;
607            Ok(Box::new(rle))
608        }
609        SparseFormat::Symmetric => {
610            let coo = sparse.to_coo()?;
611            let sym = SymmetricTensor::from_coo(&coo, SymmetricMode::Upper, 1e-6)?;
612            Ok(Box::new(sym))
613        }
614    }
615}
616
617// Performance comparison utilities are already defined above in this module
618
619/// Analyze sparse tensor characteristics for format optimization
620#[derive(Debug, Clone)]
621pub struct SparseAnalysis {
622    /// Current sparse format
623    pub format: SparseFormat,
624    /// Number of non-zero elements
625    pub nnz: usize,
626    /// Sparsity ratio (0.0 = dense, 1.0 = empty)
627    pub sparsity: f32,
628    /// Recommended optimal format
629    pub recommended_format: SparseFormat,
630    /// Detected sparsity pattern
631    pub pattern: SparsityPattern,
632    /// Storage efficiency (bytes per non-zero element)
633    pub storage_efficiency: f32,
634}
635
636/// Format performance comparison result
637#[derive(Debug, Clone)]
638pub struct FormatPerformanceComparison {
639    /// Test tensor characteristics
640    pub tensor_info: SparseAnalysis,
641    /// Performance results for each format
642    pub format_results: HashMap<SparseFormat, FormatPerformanceResult>,
643    /// Recommended format based on overall performance
644    pub recommended_format: SparseFormat,
645    /// Performance improvement factor over worst format
646    pub improvement_factor: f32,
647}
648
649/// Performance result for a specific format
650#[derive(Debug, Clone)]
651pub struct FormatPerformanceResult {
652    /// Format tested
653    pub format: SparseFormat,
654    /// Memory usage in bytes
655    pub memory_usage: usize,
656    /// Creation time in nanoseconds
657    pub creation_time_ns: u64,
658    /// Matrix-vector multiplication time in nanoseconds (if applicable)
659    pub spmv_time_ns: Option<u64>,
660    /// Format conversion time from COO in nanoseconds
661    pub conversion_time_ns: u64,
662    /// Overall performance score (lower is better)
663    pub performance_score: f32,
664}
665
666/// Compare performance across different sparse formats for a given tensor
667///
668/// This function converts a sparse tensor to all supported formats and measures
669/// performance characteristics including memory usage, conversion time, and
670/// operation performance. Useful for determining the optimal format for
671/// specific use cases and access patterns.
672///
673/// # Arguments
674/// * `sparse` - The input sparse tensor to analyze
675/// * `include_operations` - Whether to benchmark actual operations (slower but more accurate)
676///
677/// # Returns
678/// A comprehensive performance comparison across all supported formats
679///
680/// # Example
681/// ```rust,no_run
682/// use torsh_sparse::{CooTensor, compare_format_performance};
683///
684/// let triplets = vec![(0, 0, 1.0f32), (1, 1, 2.0f32), (100, 100, 3.0f32)];
685/// let coo = CooTensor::from_triplets(triplets, (1000, 1000)).expect("valid triplets");
686///
687/// let comparison = compare_format_performance(&coo, true).expect("comparison");
688/// println!("Recommended format: {:?}", comparison.recommended_format);
689/// println!("Performance improvement: {:.2}x", comparison.improvement_factor);
690/// ```
691pub fn compare_format_performance(
692    sparse: &dyn SparseTensor,
693    include_operations: bool,
694) -> TorshResult<FormatPerformanceComparison> {
695    // Get basic tensor analysis
696    let tensor_info = analyze_sparse_tensor(sparse)?;
697    let mut format_results = HashMap::new();
698
699    // Convert to COO as baseline
700    let coo = sparse.to_coo()?;
701
702    // Test each format
703    let formats_to_test = vec![
704        SparseFormat::Coo,
705        SparseFormat::Csr,
706        SparseFormat::Csc,
707        SparseFormat::Bsr,
708        SparseFormat::Dia,
709        SparseFormat::Dsr,
710        SparseFormat::Ell,
711        SparseFormat::Rle,
712        SparseFormat::Symmetric,
713    ];
714
715    for format in formats_to_test {
716        let result = benchmark_format_performance(&coo, format, include_operations)?;
717        format_results.insert(format, result);
718    }
719
720    // Determine best format based on overall score
721    let recommended_format = format_results
722        .iter()
723        .min_by(|a, b| {
724            a.1.performance_score
725                .partial_cmp(&b.1.performance_score)
726                .unwrap_or(std::cmp::Ordering::Equal)
727        })
728        .map(|(format, _)| *format)
729        .unwrap_or(SparseFormat::Csr);
730
731    // Calculate improvement factor
732    let best_score = format_results[&recommended_format].performance_score;
733    let worst_score = format_results
734        .values()
735        .map(|r| r.performance_score)
736        .fold(0.0f32, |a, b| a.max(b));
737
738    let improvement_factor = if best_score > 0.0 {
739        worst_score / best_score
740    } else {
741        1.0
742    };
743
744    Ok(FormatPerformanceComparison {
745        tensor_info,
746        format_results,
747        recommended_format,
748        improvement_factor,
749    })
750}
751
752/// Benchmark performance characteristics for a specific format
753fn benchmark_format_performance(
754    coo: &CooTensor,
755    format: SparseFormat,
756    include_operations: bool,
757) -> TorshResult<FormatPerformanceResult> {
758    use std::time::Instant;
759
760    // Measure conversion time
761    let conversion_start = Instant::now();
762    let converted = match format {
763        SparseFormat::Coo => Box::new(coo.clone()) as Box<dyn SparseTensor + Send + Sync>,
764        SparseFormat::Csr => Box::new(coo.to_csr()?) as Box<dyn SparseTensor + Send + Sync>,
765        SparseFormat::Csc => Box::new(coo.to_csc()?) as Box<dyn SparseTensor + Send + Sync>,
766        SparseFormat::Bsr => {
767            let bsr = BsrTensor::from_coo(coo, (2, 2))?;
768            Box::new(bsr) as Box<dyn SparseTensor + Send + Sync>
769        }
770        SparseFormat::Dia => {
771            let dia = DiaTensor::from_coo(coo)?;
772            Box::new(dia) as Box<dyn SparseTensor + Send + Sync>
773        }
774        SparseFormat::Dsr => {
775            let dsr = DsrTensor::from_coo(coo)?;
776            Box::new(dsr) as Box<dyn SparseTensor + Send + Sync>
777        }
778        SparseFormat::Ell => {
779            let ell = EllTensor::from_coo(coo)?;
780            Box::new(ell) as Box<dyn SparseTensor + Send + Sync>
781        }
782        SparseFormat::Rle => {
783            let rle = RleTensor::from_coo(coo)?;
784            Box::new(rle) as Box<dyn SparseTensor + Send + Sync>
785        }
786        SparseFormat::Symmetric => {
787            let sym = SymmetricTensor::from_coo(coo, SymmetricMode::Upper, 1e-6)?;
788            Box::new(sym) as Box<dyn SparseTensor + Send + Sync>
789        }
790    };
791    let conversion_time_ns = conversion_start.elapsed().as_nanos() as u64;
792
793    // Estimate memory usage (simplified)
794    let memory_usage = estimate_memory_usage(&*converted);
795
796    // Measure creation time (conversion time serves as proxy)
797    let creation_time_ns = conversion_time_ns;
798
799    // Optionally measure operation performance
800    let spmv_time_ns = if include_operations && coo.shape().dims()[0] <= 1000 {
801        // Only benchmark on reasonably sized matrices
802        measure_spmv_performance(&*converted).ok()
803    } else {
804        None
805    };
806
807    // Calculate overall performance score (weighted combination of factors)
808    let mut performance_score = 0.0f32;
809
810    // Memory efficiency (normalized by nnz)
811    let memory_per_nnz = if converted.nnz() > 0 {
812        memory_usage as f32 / converted.nnz() as f32
813    } else {
814        0.0
815    };
816    performance_score += memory_per_nnz * 0.3; // 30% weight
817
818    // Conversion time (normalized)
819    performance_score += (conversion_time_ns as f32 / 1_000_000.0) * 0.2; // 20% weight in ms
820
821    // Operation performance (if available)
822    if let Some(spmv_ns) = spmv_time_ns {
823        performance_score += (spmv_ns as f32 / 1_000_000.0) * 0.5; // 50% weight in ms
824    } else {
825        // If no operation benchmark, increase weight of other factors
826        performance_score += memory_per_nnz * 0.25; // Additional 25% to memory
827        performance_score += (conversion_time_ns as f32 / 1_000_000.0) * 0.25; // Additional 25% to conversion
828    }
829
830    Ok(FormatPerformanceResult {
831        format,
832        memory_usage,
833        creation_time_ns,
834        spmv_time_ns,
835        conversion_time_ns,
836        performance_score,
837    })
838}
839
840/// Estimate memory usage for a sparse tensor (simplified calculation)
841fn estimate_memory_usage(tensor: &dyn SparseTensor) -> usize {
842    let nnz = tensor.nnz();
843    match tensor.format() {
844        SparseFormat::Coo => nnz * 12, // 3 arrays (row, col, val) * 4 bytes each
845        SparseFormat::Csr => nnz * 8 + tensor.shape().dims()[0] * 4, // vals + indices + row_ptr
846        SparseFormat::Csc => nnz * 8 + tensor.shape().dims()[1] * 4, // vals + indices + col_ptr
847        SparseFormat::Bsr => nnz * 8,  // Approximate for block storage
848        SparseFormat::Dia => nnz * 8,  // Diagonal storage
849        SparseFormat::Dsr => nnz * 16, // Dynamic storage with overhead
850        SparseFormat::Ell => nnz * 8,  // ELLPACK storage
851        SparseFormat::Rle => nnz * 6,  // Run-length encoded
852        SparseFormat::Symmetric => nnz * 6, // Roughly half storage
853    }
854}
855
856/// Measure sparse matrix-vector multiplication performance
857fn measure_spmv_performance(tensor: &dyn SparseTensor) -> TorshResult<u64> {
858    use std::time::Instant;
859    use torsh_tensor::creation::ones;
860
861    // Create a dense vector for multiplication
862    let vector = ones::<f32>(&[tensor.shape().dims()[1]])?;
863
864    // Warm-up run
865    let _ = crate::ops::spmm(tensor, &vector)?;
866
867    // Measured run
868    let start = Instant::now();
869    let _ = crate::ops::spmm(tensor, &vector)?;
870    let duration = start.elapsed();
871
872    Ok(duration.as_nanos() as u64)
873}
874
875/// Analyze a sparse tensor and provide optimization recommendations
876pub fn analyze_sparse_tensor(sparse: &dyn SparseTensor) -> TorshResult<SparseAnalysis> {
877    let format = sparse.format();
878    let nnz = sparse.nnz();
879    let sparsity = sparse.sparsity();
880    let shape = sparse.shape();
881
882    // Convert to COO for pattern analysis
883    let coo = sparse.to_coo()?;
884    let triplets = coo.triplets();
885    let pattern = hybrid::HybridTensor::analyze_sparsity_pattern(&triplets, shape)?;
886
887    // Recommend optimal format based on analysis
888    let recommended_format = match pattern {
889        SparsityPattern::Diagonal => SparseFormat::Dia,
890        SparsityPattern::Banded { .. } => {
891            // Check if matrix is symmetric for symmetric format recommendation
892            if is_matrix_symmetric(&coo) {
893                SparseFormat::Symmetric
894            } else {
895                SparseFormat::Ell
896            }
897        }
898        SparsityPattern::BlockDiagonal { .. } => SparseFormat::Bsr,
899        SparsityPattern::Random => {
900            // Check for run-length encoding opportunities
901            if has_consecutive_patterns(&coo) {
902                SparseFormat::Rle
903            } else if sparsity > 0.9 {
904                SparseFormat::Coo
905            } else {
906                SparseFormat::Csr // Default for sparsity <= 0.9
907            }
908        }
909    };
910
911    // Estimate storage efficiency (rough approximation)
912    let storage_efficiency = match format {
913        SparseFormat::Coo => 12.0, // 3 values per element (row, col, val)
914        SparseFormat::Csr => 8.0 + (4.0 * shape.dims()[0] as f32 / nnz as f32), // values + indices + row pointers
915        SparseFormat::Csc => 8.0 + (4.0 * shape.dims()[1] as f32 / nnz as f32), // values + indices + col pointers
916        SparseFormat::Bsr => 8.0,       // Approximate for block storage
917        SparseFormat::Dia => 8.0,       // Diagonal storage
918        SparseFormat::Dsr => 16.0,      // Dynamic sparse row (higher overhead for BTreeMap)
919        SparseFormat::Ell => 8.0,       // ELLPACK storage
920        SparseFormat::Rle => 6.0,       // Run-length encoding (row, col, length, values)
921        SparseFormat::Symmetric => 6.0, // Half storage for symmetric matrices
922    };
923
924    Ok(SparseAnalysis {
925        format,
926        nnz,
927        sparsity,
928        recommended_format,
929        pattern,
930        storage_efficiency,
931    })
932}
933
934/// Check if a COO matrix is symmetric
935fn is_matrix_symmetric(coo: &CooTensor) -> bool {
936    use std::collections::HashMap;
937
938    let triplets = coo.triplets();
939    let mut element_map: HashMap<(usize, usize), f32> = HashMap::new();
940
941    // Build element map
942    for (row, col, value) in &triplets {
943        element_map.insert((*row, *col), *value);
944    }
945
946    // Check symmetry
947    for (row, col, value) in &triplets {
948        if *row != *col {
949            if let Some(&sym_value) = element_map.get(&(*col, *row)) {
950                if (value - sym_value).abs() > 1e-6 {
951                    return false;
952                }
953            } else {
954                return false;
955            }
956        }
957    }
958
959    true
960}
961
962/// Check if a COO matrix has consecutive patterns that would benefit from RLE
963fn has_consecutive_patterns(coo: &CooTensor) -> bool {
964    use std::collections::HashMap;
965
966    let triplets = coo.triplets();
967    let mut row_elements: HashMap<usize, Vec<usize>> = HashMap::new();
968
969    // Group elements by row
970    for (row, col, _) in &triplets {
971        row_elements.entry(*row).or_default().push(*col);
972    }
973
974    let mut consecutive_count = 0;
975    let mut total_elements = 0;
976
977    // Check for consecutive patterns in each row
978    for (_, mut cols_in_row) in row_elements {
979        cols_in_row.sort_unstable();
980        total_elements += cols_in_row.len();
981
982        for window in cols_in_row.windows(2) {
983            if window[1] == window[0] + 1 {
984                consecutive_count += 1;
985            }
986        }
987    }
988
989    // Return true if more than 30% of elements are part of consecutive sequences
990    if total_elements == 0 {
991        false
992    } else {
993        (consecutive_count as f32 / total_elements as f32) > 0.3
994    }
995}
996
997/// Prelude module for convenient imports
998pub mod prelude {
999    pub use crate::autograd::{
1000        SparseAutogradTensor, SparseData, SparseGradFn, SparseGradientAccumulator,
1001    };
1002    pub use crate::bsr::BsrTensor;
1003    pub use crate::coo::CooTensor;
1004    pub use crate::csc::CscTensor;
1005    pub use crate::csr::CsrTensor;
1006    pub use crate::dia::DiaTensor;
1007    pub use crate::dsr::DsrTensor;
1008    pub use crate::ell::EllTensor;
1009    pub use crate::gpu::{CudaSparseOps, CudaSparseTensor, CudaSparseTensorFactory};
1010    pub use crate::rle::RleTensor;
1011    pub use crate::symmetric::{SymmetricMode, SymmetricTensor};
1012    // Re-export from lib.rs instead of unified_interface
1013    pub use crate::{
1014        analyze_sparse_tensor, compare_format_performance, sparse_from_dense, SparseFormat,
1015        SparseTensor,
1016    };
1017}
1018
1019#[cfg(test)]
1020mod tests {
1021    use super::*;
1022    use torsh_tensor::creation::zeros;
1023
1024    #[test]
1025    fn test_sparse_format() {
1026        // Test sparse format creation
1027        let dense = zeros::<f32>(&[3, 4]).unwrap();
1028
1029        // Set some non-zero values
1030        // dense[[0, 1]] = 1.0
1031        // dense[[1, 2]] = 2.0
1032        // dense[[2, 0]] = 3.0
1033
1034        let sparse = sparse_from_dense(&dense, SparseFormat::Coo, None).unwrap();
1035        assert_eq!(sparse.format(), SparseFormat::Coo);
1036        assert_eq!(sparse.shape(), &dense.shape());
1037    }
1038
1039    #[test]
1040    fn test_format_performance_comparison() {
1041        // Create a simple sparse tensor for testing
1042        let triplets = vec![(0, 0, 1.0f32), (1, 1, 2.0f32), (2, 2, 3.0f32)];
1043        let coo = CooTensor::from_triplets(triplets, (10, 10)).unwrap();
1044
1045        // Test performance comparison without operations (faster)
1046        let comparison = compare_format_performance(&coo, false).unwrap();
1047
1048        // Verify basic properties
1049        assert!(!comparison.format_results.is_empty());
1050        assert!(comparison.improvement_factor >= 1.0);
1051
1052        // Check that COO format is present in results
1053        assert!(comparison.format_results.contains_key(&SparseFormat::Coo));
1054
1055        // Verify recommended format is valid
1056        assert!(comparison
1057            .format_results
1058            .contains_key(&comparison.recommended_format));
1059
1060        // Check that performance scores are reasonable
1061        for result in comparison.format_results.values() {
1062            assert!(result.performance_score >= 0.0);
1063            assert!(result.memory_usage > 0);
1064            // conversion_time_ns is u64, so it's always >= 0
1065        }
1066    }
1067
1068    #[test]
1069    fn test_sparse_analysis() {
1070        // Create a diagonal sparse tensor
1071        let triplets = vec![(0, 0, 1.0f32), (1, 1, 2.0f32), (2, 2, 3.0f32)];
1072        let coo = CooTensor::from_triplets(triplets, (3, 3)).unwrap();
1073
1074        let analysis = analyze_sparse_tensor(&coo).unwrap();
1075
1076        // Verify analysis properties
1077        assert_eq!(analysis.format, SparseFormat::Coo);
1078        assert_eq!(analysis.nnz, 3);
1079        assert!(analysis.sparsity > 0.0 && analysis.sparsity <= 1.0);
1080        assert!(analysis.storage_efficiency > 0.0);
1081
1082        // For a diagonal matrix, DIA format should be recommended
1083        assert_eq!(analysis.recommended_format, SparseFormat::Dia);
1084    }
1085}