1pub 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
52pub 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#[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#[cfg(feature = "scirs2-integration")]
87pub mod scirs2_sparse_integration;
88pub mod symmetric;
89pub mod unified_interface;
90
91pub 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
102pub use gpu::{CudaSparseOps, CudaSparseTensor, CudaSparseTensorFactory};
104
105pub use autograd::{SparseAutogradTensor, SparseData, SparseGradFn, SparseGradientAccumulator};
107
108pub use scirs2_integration::{scirs2_add, scirs2_enhanced_ops};
110
111#[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
118pub use nn::{
120 Format,
122 GraphConvolution,
123 InitConfig,
124 LayerConfig,
125 SparseAdam,
126 SparseAttention,
127 SparseConv2d,
129 SparseConverter,
130 SparseEmbedding,
131 SparseEmbeddingStats,
132 SparseInitConfig,
135 SparseLayer,
136 SparseLayerConfig,
137 SparseLinear,
139 SparseMemoryStats,
140 SparseOptimizer,
142 SparsePatternAnalysis,
143 SparseSGD,
145 SparseStats,
146 SparseWeightGenerator,
148};
149
150pub use hybrid::{auto_select_format, HybridTensor, PartitionStrategy, SparsityPattern};
152
153pub use pattern_analysis::{
155 AdvancedSparsityPattern, ClusteringAlgorithm, MatrixReorderer, PatternAnalyzer,
156 PatternStatistics, PatternVisualizer, ReorderingAlgorithm,
157};
158
159pub use performance_tools::{
161 AutoTuner, BenchmarkConfig, CachePerformanceResult, HardwareBenchmark, MemoryAnalysis,
162 OperationStatistics, PerformanceExporter, PerformanceMeasurement, PerformanceReport, PlotData,
163 SparseProfiler, SystemInfo, TensorBoardExporter, TrendAnalysis, TrendAnalyzer, TrendDirection,
164};
165
166pub use matrix_market::{
168 MatrixMarketField, MatrixMarketFormat, MatrixMarketHeader, MatrixMarketIO, MatrixMarketObject,
169 MatrixMarketSize, MatrixMarketSymmetry, MatrixMarketUtils,
170};
171
172pub use custom_kernels::{
174 ElementWiseKernels, FormatConversionKernels, KernelDispatcher, ReductionKernels,
175 SparseMatMulKernels,
176};
177
178pub use scipy_sparse::{ScipyFormat, ScipySparseData, ScipySparseIntegration};
180
181pub use matlab_compat::{
183 export_to_matlab_script, matlab_sparse_from_triplets, MatlabSparseCompat, MatlabSparseMatrix,
184};
185
186pub use hdf5_support::{load_sparse_matrix, save_sparse_matrix, Hdf5SparseIO, Hdf5SparseMetadata};
188
189pub use unified_interface::{
191 AccessPatterns, MemoryStats, OptimizationConfig, OptimizationFlags, OptimizationReport,
192 PerformanceHints, PerformanceSummary, TensorMetadata, UnifiedSparseTensor,
193 UnifiedSparseTensorFactory,
194};
195
196pub use memory_management::{
198 create_sparse_with_memory_management, MemoryAwareSparseBuilder, MemoryPoolConfig, MemoryReport,
199 MemoryStatistics, SparseMemoryHandle, SparseMemoryManager, SparseMemoryPool,
200};
201
202pub use conversions::{direct_conversions, optimization, patterns, validation, ConversionHints};
204
205#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
210pub enum SparseFormat {
211 Coo,
217
218 Csr,
224
225 Csc,
231
232 Bsr,
238
239 Dia,
245
246 Dsr,
252
253 Ell,
259
260 Rle,
266
267 Symmetric,
273}
274
275pub trait SparseTensor {
300 fn format(&self) -> SparseFormat;
305
306 fn shape(&self) -> &Shape;
311
312 fn dtype(&self) -> DType;
316
317 fn device(&self) -> DeviceType;
321
322 fn nnz(&self) -> usize;
327
328 fn to_dense(&self) -> TorshResult<Tensor>;
336
337 fn to_coo(&self) -> TorshResult<CooTensor>;
342
343 fn to_csr(&self) -> TorshResult<CsrTensor>;
348
349 fn to_csc(&self) -> TorshResult<CscTensor>;
354
355 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 fn as_any(&self) -> &dyn std::any::Any;
380}
381
382pub 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 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
432pub 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
445pub 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#[derive(Debug, Clone)]
461pub struct FormatConfig {
462 pub threshold: f32,
464 pub dense_threshold: f32,
466 pub block_size: (usize, usize),
468 pub enable_hybrid: bool,
470 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 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 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 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 pub fn with_threshold(mut self, threshold: f32) -> Self {
534 self.threshold = threshold;
535 self
536 }
537
538 pub fn with_block_size(mut self, block_size: (usize, usize)) -> Self {
540 self.block_size = block_size;
541 self
542 }
543
544 pub fn with_hybrid(mut self, enable: bool) -> Self {
546 self.enable_hybrid = enable;
547 self
548 }
549}
550
551pub fn sparse_from_dense_with_config(
553 dense: &Tensor,
554 config: FormatConfig,
555) -> TorshResult<Box<dyn SparseTensor + Send + Sync>> {
556 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
575pub 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#[derive(Debug, Clone)]
621pub struct SparseAnalysis {
622 pub format: SparseFormat,
624 pub nnz: usize,
626 pub sparsity: f32,
628 pub recommended_format: SparseFormat,
630 pub pattern: SparsityPattern,
632 pub storage_efficiency: f32,
634}
635
636#[derive(Debug, Clone)]
638pub struct FormatPerformanceComparison {
639 pub tensor_info: SparseAnalysis,
641 pub format_results: HashMap<SparseFormat, FormatPerformanceResult>,
643 pub recommended_format: SparseFormat,
645 pub improvement_factor: f32,
647}
648
649#[derive(Debug, Clone)]
651pub struct FormatPerformanceResult {
652 pub format: SparseFormat,
654 pub memory_usage: usize,
656 pub creation_time_ns: u64,
658 pub spmv_time_ns: Option<u64>,
660 pub conversion_time_ns: u64,
662 pub performance_score: f32,
664}
665
666pub fn compare_format_performance(
692 sparse: &dyn SparseTensor,
693 include_operations: bool,
694) -> TorshResult<FormatPerformanceComparison> {
695 let tensor_info = analyze_sparse_tensor(sparse)?;
697 let mut format_results = HashMap::new();
698
699 let coo = sparse.to_coo()?;
701
702 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 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 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
752fn benchmark_format_performance(
754 coo: &CooTensor,
755 format: SparseFormat,
756 include_operations: bool,
757) -> TorshResult<FormatPerformanceResult> {
758 use std::time::Instant;
759
760 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 let memory_usage = estimate_memory_usage(&*converted);
795
796 let creation_time_ns = conversion_time_ns;
798
799 let spmv_time_ns = if include_operations && coo.shape().dims()[0] <= 1000 {
801 measure_spmv_performance(&*converted).ok()
803 } else {
804 None
805 };
806
807 let mut performance_score = 0.0f32;
809
810 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; performance_score += (conversion_time_ns as f32 / 1_000_000.0) * 0.2; if let Some(spmv_ns) = spmv_time_ns {
823 performance_score += (spmv_ns as f32 / 1_000_000.0) * 0.5; } else {
825 performance_score += memory_per_nnz * 0.25; performance_score += (conversion_time_ns as f32 / 1_000_000.0) * 0.25; }
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
840fn estimate_memory_usage(tensor: &dyn SparseTensor) -> usize {
842 let nnz = tensor.nnz();
843 match tensor.format() {
844 SparseFormat::Coo => nnz * 12, SparseFormat::Csr => nnz * 8 + tensor.shape().dims()[0] * 4, SparseFormat::Csc => nnz * 8 + tensor.shape().dims()[1] * 4, SparseFormat::Bsr => nnz * 8, SparseFormat::Dia => nnz * 8, SparseFormat::Dsr => nnz * 16, SparseFormat::Ell => nnz * 8, SparseFormat::Rle => nnz * 6, SparseFormat::Symmetric => nnz * 6, }
854}
855
856fn measure_spmv_performance(tensor: &dyn SparseTensor) -> TorshResult<u64> {
858 use std::time::Instant;
859 use torsh_tensor::creation::ones;
860
861 let vector = ones::<f32>(&[tensor.shape().dims()[1]])?;
863
864 let _ = crate::ops::spmm(tensor, &vector)?;
866
867 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
875pub 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 let coo = sparse.to_coo()?;
884 let triplets = coo.triplets();
885 let pattern = hybrid::HybridTensor::analyze_sparsity_pattern(&triplets, shape)?;
886
887 let recommended_format = match pattern {
889 SparsityPattern::Diagonal => SparseFormat::Dia,
890 SparsityPattern::Banded { .. } => {
891 if is_matrix_symmetric(&coo) {
893 SparseFormat::Symmetric
894 } else {
895 SparseFormat::Ell
896 }
897 }
898 SparsityPattern::BlockDiagonal { .. } => SparseFormat::Bsr,
899 SparsityPattern::Random => {
900 if has_consecutive_patterns(&coo) {
902 SparseFormat::Rle
903 } else if sparsity > 0.9 {
904 SparseFormat::Coo
905 } else {
906 SparseFormat::Csr }
908 }
909 };
910
911 let storage_efficiency = match format {
913 SparseFormat::Coo => 12.0, SparseFormat::Csr => 8.0 + (4.0 * shape.dims()[0] as f32 / nnz as f32), SparseFormat::Csc => 8.0 + (4.0 * shape.dims()[1] as f32 / nnz as f32), SparseFormat::Bsr => 8.0, SparseFormat::Dia => 8.0, SparseFormat::Dsr => 16.0, SparseFormat::Ell => 8.0, SparseFormat::Rle => 6.0, SparseFormat::Symmetric => 6.0, };
923
924 Ok(SparseAnalysis {
925 format,
926 nnz,
927 sparsity,
928 recommended_format,
929 pattern,
930 storage_efficiency,
931 })
932}
933
934fn 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 for (row, col, value) in &triplets {
943 element_map.insert((*row, *col), *value);
944 }
945
946 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
962fn 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 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 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 if total_elements == 0 {
991 false
992 } else {
993 (consecutive_count as f32 / total_elements as f32) > 0.3
994 }
995}
996
997pub 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 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 let dense = zeros::<f32>(&[3, 4]).unwrap();
1028
1029 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 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 let comparison = compare_format_performance(&coo, false).unwrap();
1047
1048 assert!(!comparison.format_results.is_empty());
1050 assert!(comparison.improvement_factor >= 1.0);
1051
1052 assert!(comparison.format_results.contains_key(&SparseFormat::Coo));
1054
1055 assert!(comparison
1057 .format_results
1058 .contains_key(&comparison.recommended_format));
1059
1060 for result in comparison.format_results.values() {
1062 assert!(result.performance_score >= 0.0);
1063 assert!(result.memory_usage > 0);
1064 }
1066 }
1067
1068 #[test]
1069 fn test_sparse_analysis() {
1070 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 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 assert_eq!(analysis.recommended_format, SparseFormat::Dia);
1084 }
1085}