scirs2_sparse/adaptive_memory_compression/
compressor.rs

1//! Adaptive Memory Compressor
2//!
3//! This module contains the main compressor implementation that coordinates
4//! all compression, caching, and out-of-core operations.
5
6use super::access_tracking::{AccessEvent, AccessPattern, AccessTracker, AccessType};
7use super::cache::{BlockCache, BlockId};
8use super::compressed_data::{BlockType, CompressedBlock, CompressedMatrix};
9use super::compression::{CompressionEngine, CompressionResult};
10use super::config::{AdaptiveCompressionConfig, CompressionAlgorithm};
11use super::memory_mapping::{MemoryMappingConfig, MemoryMappingManager};
12use super::out_of_core::OutOfCoreManager;
13use super::stats::AccessPatternType;
14use super::stats::{CompressionMetadata, CompressionStats, MemoryStats};
15use crate::error::{SparseError, SparseResult};
16use scirs2_core::numeric::{Float, NumAssign, SparseElement};
17use std::marker::PhantomData;
18use std::sync::atomic::{AtomicUsize, Ordering};
19use std::sync::{Arc, Mutex};
20
21/// Adaptive memory compression manager
22pub struct AdaptiveMemoryCompressor {
23    config: AdaptiveCompressionConfig,
24    memory_usage: AtomicUsize,
25    compression_stats: Arc<Mutex<CompressionStats>>,
26    block_cache: Arc<Mutex<BlockCache>>,
27    access_tracker: Arc<Mutex<AccessTracker>>,
28    compression_engine: CompressionEngine,
29    hierarchical_levels: Vec<CompressionLevel>,
30    out_of_core_manager: Option<OutOfCoreManager>,
31    memory_mapping_manager: Option<MemoryMappingManager>,
32}
33
34/// Hierarchical compression level configuration
35#[derive(Debug, Clone)]
36struct CompressionLevel {
37    level: u8,
38    compression_ratio: f64,
39    algorithm: CompressionAlgorithm,
40    block_size: usize,
41    access_threshold: usize,
42}
43
44/// Compression strategy selection
45#[derive(Debug)]
46struct CompressionStrategy {
47    algorithm: CompressionAlgorithm,
48    block_size: usize,
49    hierarchical: bool,
50    predicted_ratio: f64,
51}
52
53/// Sparsity pattern analysis results
54#[derive(Debug, Default)]
55struct SparsityPatternAnalysis {
56    avg_nnz_per_row: f64,
57    max_nnz_per_row: usize,
58    min_nnz_per_row: usize,
59    sequential_patterns: usize,
60    clustering_factor: f64,
61    bandwidth: usize,
62}
63
64/// Access pattern information for a matrix
65#[derive(Debug, Default)]
66struct AccessPatternInfo {
67    total_accesses: usize,
68    avg_temporal_locality: f64,
69    avg_spatial_locality: f64,
70    pattern_count: usize,
71}
72
73/// Cache statistics for internal use
74#[derive(Debug)]
75struct CacheStats {
76    hits: usize,
77    misses: usize,
78    hit_ratio: f64,
79}
80
81impl AdaptiveMemoryCompressor {
82    /// Create a new adaptive memory compressor
83    pub fn new(config: AdaptiveCompressionConfig) -> SparseResult<Self> {
84        let block_cache = BlockCache::new(config.cache_size);
85        let access_tracker = AccessTracker::new(); // Default capacity
86        let compression_engine = CompressionEngine::new();
87
88        // Initialize hierarchical compression levels
89        let hierarchical_levels = vec![
90            CompressionLevel {
91                level: 1,
92                compression_ratio: 2.0,
93                algorithm: CompressionAlgorithm::RLE,
94                block_size: config.block_size,
95                access_threshold: 100,
96            },
97            CompressionLevel {
98                level: 2,
99                compression_ratio: 4.0,
100                algorithm: CompressionAlgorithm::Delta,
101                block_size: config.block_size / 2,
102                access_threshold: 50,
103            },
104            CompressionLevel {
105                level: 3,
106                compression_ratio: 8.0,
107                algorithm: CompressionAlgorithm::LZ77,
108                block_size: config.block_size / 4,
109                access_threshold: 10,
110            },
111        ];
112
113        // Initialize out-of-core manager if enabled
114        let out_of_core_manager = if config.out_of_core {
115            Some(OutOfCoreManager::new(&config.temp_directory)?)
116        } else {
117            None
118        };
119
120        // Initialize memory mapping manager if enabled
121        let memory_mapping_manager = if config.memory_mapping {
122            let mapping_config = MemoryMappingConfig {
123                read_only: false,
124                write_through: true,
125                prefetch: true,
126                page_size_hint: 4096,
127            };
128            Some(MemoryMappingManager::new(mapping_config))
129        } else {
130            None
131        };
132
133        Ok(Self {
134            config,
135            memory_usage: AtomicUsize::new(0),
136            compression_stats: Arc::new(Mutex::new(CompressionStats::new())),
137            block_cache: Arc::new(Mutex::new(block_cache)),
138            access_tracker: Arc::new(Mutex::new(access_tracker)),
139            compression_engine,
140            hierarchical_levels,
141            out_of_core_manager,
142            memory_mapping_manager,
143        })
144    }
145
146    /// Compress sparse matrix data adaptively
147    #[allow(clippy::too_many_arguments)]
148    pub fn compress_matrix<T>(
149        &mut self,
150        matrix_id: u64,
151        rows: usize,
152        indptr: &[usize],
153        indices: &[usize],
154        data: &[T],
155    ) -> SparseResult<CompressedMatrix<T>>
156    where
157        T: Float + SparseElement + NumAssign + Send + Sync + Copy + std::fmt::Debug,
158    {
159        let total_size = std::mem::size_of_val(indptr)
160            + std::mem::size_of_val(indices)
161            + std::mem::size_of_val(data);
162
163        // Check if compression is needed
164        let current_usage = self.memory_usage.load(Ordering::Relaxed);
165        let usage_ratio = (current_usage + total_size) as f64 / self.config.memory_budget as f64;
166
167        if usage_ratio < self.config.compression_threshold && !self.config.adaptive_compression {
168            // No compression needed
169            return self.create_uncompressed_matrix(matrix_id, rows, indptr, indices, data);
170        }
171
172        let start_time = std::time::Instant::now();
173
174        // Determine optimal compression strategy
175        let compression_strategy =
176            self.determine_compression_strategy(matrix_id, rows, indptr, indices)?;
177
178        // Apply compression based on strategy
179        let compressed_blocks = self.apply_compression_strategy(
180            &compression_strategy,
181            matrix_id,
182            rows,
183            indptr,
184            indices,
185            data,
186        )?;
187
188        let compression_time = start_time.elapsed().as_secs_f64();
189
190        // Update statistics
191        self.update_compression_stats(total_size, &compressed_blocks, compression_time);
192
193        // Update memory usage
194        let compressed_size = compressed_blocks
195            .iter()
196            .map(|b| b.compressed_data.len())
197            .sum::<usize>();
198        self.memory_usage
199            .fetch_add(compressed_size, Ordering::Relaxed);
200
201        // Handle out-of-core storage if needed
202        self.handle_out_of_core_storage(&compressed_blocks)?;
203
204        let mut compressed_matrix = CompressedMatrix::new(
205            matrix_id,
206            rows,
207            if !indptr.is_empty() {
208                *indices.iter().max().unwrap_or(&0) + 1
209            } else {
210                0
211            },
212            compression_strategy.algorithm,
213            compression_strategy.block_size,
214        );
215
216        // Add the compressed blocks to the matrix
217        for block in compressed_blocks {
218            compressed_matrix.add_block(block);
219        }
220
221        Ok(compressed_matrix)
222    }
223
224    /// Decompress matrix data
225    pub fn decompress_matrix<T>(
226        &mut self,
227        compressed_matrix: &CompressedMatrix<T>,
228    ) -> SparseResult<(Vec<usize>, Vec<usize>, Vec<T>)>
229    where
230        T: Float
231            + SparseElement
232            + NumAssign
233            + Send
234            + Sync
235            + Copy
236            + std::fmt::Debug
237            + scirs2_core::numeric::FromPrimitive,
238    {
239        let start_time = std::time::Instant::now();
240
241        let mut indptr = Vec::new();
242        let mut indices = Vec::new();
243        let mut data = Vec::new();
244
245        // Decompress each block
246        for block in compressed_matrix.get_blocks_row_major() {
247            // Check cache first
248            let decompressed_data =
249                if let Some(cached_data) = self.get_cached_block(&block.blockid)? {
250                    cached_data
251                } else {
252                    // Decompress and cache
253                    let decompressed =
254                        self.decompress_block(block, compressed_matrix.compression_algorithm)?;
255                    self.cache_block(&block.blockid, &decompressed)?;
256                    decompressed
257                };
258
259            // Parse decompressed data based on block type
260            match block.block_type {
261                BlockType::IndPtr => {
262                    indptr.extend(self.parse_indptr_data(&decompressed_data)?);
263                }
264                BlockType::Indices => {
265                    indices.extend(self.parse_indices_data(&decompressed_data)?);
266                }
267                BlockType::Data => {
268                    data.extend(self.parse_data_values::<T>(&decompressed_data)?);
269                }
270                BlockType::Combined => {
271                    let (block_indptr, block_indices, block_data) =
272                        self.parse_combined_data::<T>(&decompressed_data)?;
273                    indptr.extend(block_indptr);
274                    indices.extend(block_indices);
275                    data.extend(block_data);
276                }
277                BlockType::Metadata => {
278                    // Handle metadata blocks if needed
279                }
280            }
281        }
282
283        let decompression_time = start_time.elapsed().as_secs_f64();
284
285        // Update statistics
286        if let Ok(mut stats) = self.compression_stats.lock() {
287            stats.decompression_time += decompression_time;
288        }
289
290        // Record access pattern
291        self.record_matrix_access(compressed_matrix.matrixid, AccessType::Read);
292
293        Ok((indptr, indices, data))
294    }
295
296    /// Get memory usage statistics
297    pub fn get_memory_stats(&self) -> MemoryStats {
298        let current_usage = self.memory_usage.load(Ordering::Relaxed);
299        let usage_ratio = current_usage as f64 / self.config.memory_budget as f64;
300
301        let compression_stats = self
302            .compression_stats
303            .lock()
304            .expect("Operation failed")
305            .clone();
306        let cache_stats = self.get_cache_stats();
307
308        let mut memory_stats = MemoryStats::new(self.config.memory_budget, self.config.out_of_core);
309        memory_stats.update_memory_usage(current_usage);
310        memory_stats.compression_stats = compression_stats;
311        memory_stats.cache_hits = cache_stats.hits;
312        memory_stats.cache_misses = cache_stats.misses;
313        memory_stats.cache_hit_ratio = cache_stats.hit_ratio;
314        memory_stats
315    }
316
317    /// Get compression statistics
318    pub fn get_stats(&self) -> CompressionStats {
319        self.compression_stats
320            .lock()
321            .expect("Operation failed")
322            .clone()
323    }
324
325    /// Optimize for sequential access patterns
326    pub fn optimize_for_sequential_access(&mut self) {
327        // Note: AccessTracker doesn't have set_access_pattern_hint method
328        // This optimization is handled internally by the tracker
329    }
330
331    /// Optimize for random access patterns
332    pub fn optimize_for_random_access(&mut self) {
333        // Note: AccessTracker doesn't have set_access_pattern_hint method
334        // This optimization is handled internally by the tracker
335    }
336
337    /// Clear cache and reset statistics
338    pub fn reset(&mut self) -> SparseResult<()> {
339        // Clear cache
340        if let Ok(mut cache) = self.block_cache.lock() {
341            cache.clear();
342        }
343
344        // Reset statistics
345        if let Ok(mut stats) = self.compression_stats.lock() {
346            *stats = CompressionStats::new();
347        }
348
349        // Reset access tracker
350        if let Ok(mut tracker) = self.access_tracker.lock() {
351            tracker.cleanup_old_patterns(0); // Remove all patterns
352        }
353
354        // Reset memory usage
355        self.memory_usage.store(0, Ordering::Relaxed);
356
357        // Cleanup out-of-core files
358        if let Some(ref mut manager) = self.out_of_core_manager {
359            manager.cleanup()?;
360        }
361
362        Ok(())
363    }
364
365    // Private helper methods
366
367    fn determine_compression_strategy(
368        &self,
369        matrix_id: u64,
370        rows: usize,
371        indptr: &[usize],
372        indices: &[usize],
373    ) -> SparseResult<CompressionStrategy> {
374        // Analyze matrix characteristics
375        let nnz = indices.len();
376        let density = if rows > 0 && !indices.is_empty() {
377            let max_col = *indices.iter().max().unwrap_or(&0);
378            nnz as f64 / (rows as f64 * (max_col + 1) as f64)
379        } else {
380            0.0
381        };
382
383        // Analyze sparsity patterns
384        let pattern_analysis = self.analyze_sparsity_patterns(indptr, indices);
385
386        // Check access patterns if available
387        let access_info = self.get_access_pattern_info(matrix_id);
388
389        // Select compression algorithm based on analysis
390        let algorithm = if self.config.adaptive_compression {
391            self.select_adaptive_algorithm(density, &pattern_analysis, &access_info)
392        } else {
393            self.config.compression_algorithm
394        };
395
396        // Determine block size
397        let block_size = self.determine_optimal_block_size(rows, nnz, density);
398
399        Ok(CompressionStrategy {
400            algorithm,
401            block_size,
402            hierarchical: self.config.hierarchical_compression,
403            predicted_ratio: self.predict_compression_ratio(algorithm, density, &pattern_analysis),
404        })
405    }
406
407    fn apply_compression_strategy<T>(
408        &mut self,
409        strategy: &CompressionStrategy,
410        matrix_id: u64,
411        rows: usize,
412        indptr: &[usize],
413        indices: &[usize],
414        data: &[T],
415    ) -> SparseResult<Vec<CompressedBlock>>
416    where
417        T: Float + SparseElement + NumAssign + Send + Sync + Copy + std::fmt::Debug,
418    {
419        // Serialize matrix components
420        let indptr_data = self.serialize_indptr(indptr)?;
421        let indices_data = self.serialize_indices(indices)?;
422        let data_data = self.serialize_data(data)?;
423
424        let mut blocks = Vec::new();
425
426        // Compress indptr
427        let indptr_block_id = BlockId::new(matrix_id, 0, 0);
428        let indptr_result = self.compression_engine.compress(
429            &indptr_data,
430            strategy.algorithm,
431            &indptr_block_id,
432            BlockType::IndPtr,
433        )?;
434        blocks.push(CompressedBlock::new(
435            indptr_block_id,
436            BlockType::IndPtr,
437            indptr_result.compressed_data,
438            indptr_data.len(),
439            (indptr_result.compression_ratio.clamp(1.0, 10.0) as u8).max(1),
440        ));
441
442        // Compress indices
443        let indices_block_id = BlockId::new(matrix_id, 0, 1);
444        let indices_result = self.compression_engine.compress(
445            &indices_data,
446            strategy.algorithm,
447            &indices_block_id,
448            BlockType::Indices,
449        )?;
450        blocks.push(CompressedBlock::new(
451            indices_block_id,
452            BlockType::Indices,
453            indices_result.compressed_data,
454            indices_data.len(),
455            (indices_result.compression_ratio.clamp(1.0, 10.0) as u8).max(1),
456        ));
457
458        // Compress data
459        let data_block_id = BlockId::new(matrix_id, 0, 2);
460        let data_result = self.compression_engine.compress(
461            &data_data,
462            strategy.algorithm,
463            &data_block_id,
464            BlockType::Data,
465        )?;
466        blocks.push(CompressedBlock::new(
467            data_block_id,
468            BlockType::Data,
469            data_result.compressed_data,
470            data_data.len(),
471            (data_result.compression_ratio.clamp(1.0, 10.0) as u8).max(1),
472        ));
473
474        Ok(blocks)
475    }
476
477    fn analyze_sparsity_patterns(
478        &self,
479        indptr: &[usize],
480        indices: &[usize],
481    ) -> SparsityPatternAnalysis {
482        let mut analysis = SparsityPatternAnalysis::default();
483
484        if indptr.len() <= 1 {
485            return analysis;
486        }
487
488        let rows = indptr.len() - 1;
489
490        // Analyze row distribution
491        let mut row_nnz = Vec::new();
492        for row in 0..rows {
493            row_nnz.push(indptr[row + 1] - indptr[row]);
494        }
495
496        analysis.avg_nnz_per_row = row_nnz.iter().sum::<usize>() as f64 / rows as f64;
497        analysis.max_nnz_per_row = *row_nnz.iter().max().unwrap_or(&0);
498        analysis.min_nnz_per_row = *row_nnz.iter().min().unwrap_or(&0);
499
500        // Analyze column patterns
501        analysis.sequential_patterns = self.count_sequential_patterns(indices);
502        analysis.clustering_factor = self.calculate_clustering_factor(indptr, indices);
503        analysis.bandwidth = self.calculate_bandwidth(indptr, indices);
504
505        analysis
506    }
507
508    fn count_sequential_patterns(&self, indices: &[usize]) -> usize {
509        let mut sequential_count = 0;
510        let mut current_sequence = 0;
511
512        for window in indices.windows(2) {
513            if window[1] == window[0] + 1 {
514                current_sequence += 1;
515            } else {
516                if current_sequence >= 3 {
517                    sequential_count += current_sequence;
518                }
519                current_sequence = 0;
520            }
521        }
522
523        if current_sequence >= 3 {
524            sequential_count += current_sequence;
525        }
526
527        sequential_count
528    }
529
530    fn calculate_clustering_factor(&self, indptr: &[usize], indices: &[usize]) -> f64 {
531        if indptr.len() <= 1 {
532            return 0.0;
533        }
534
535        let mut total_distance = 0.0;
536        let mut total_pairs = 0;
537
538        let rows = indptr.len() - 1;
539        for row in 0..rows {
540            let start = indptr[row];
541            let end = indptr[row + 1];
542
543            if end > start + 1 {
544                for i in start..(end - 1) {
545                    total_distance += (indices[i + 1] - indices[i]) as f64;
546                    total_pairs += 1;
547                }
548            }
549        }
550
551        if total_pairs > 0 {
552            total_distance / total_pairs as f64
553        } else {
554            0.0
555        }
556    }
557
558    fn calculate_bandwidth(&self, indptr: &[usize], indices: &[usize]) -> usize {
559        if indptr.len() <= 1 {
560            return 0;
561        }
562
563        let mut max_bandwidth = 0;
564        let rows = indptr.len() - 1;
565
566        for row in 0..rows {
567            let start = indptr[row];
568            let end = indptr[row + 1];
569
570            if end > start {
571                let min_col = indices[start];
572                let max_col = indices[end - 1];
573                let bandwidth = max_col.saturating_sub(min_col);
574                max_bandwidth = max_bandwidth.max(bandwidth);
575            }
576        }
577
578        max_bandwidth
579    }
580
581    fn get_access_pattern_info(&self, _matrix_id: u64) -> AccessPatternInfo {
582        let access_tracker = self.access_tracker.lock().expect("Operation failed");
583        // AccessTracker doesn't have get_matrix_access_info, so we'll use available methods
584        let stats = access_tracker.get_statistics();
585        AccessPatternInfo {
586            total_accesses: stats.total_access_events,
587            avg_temporal_locality: 0.5, // Default value
588            avg_spatial_locality: 0.5,  // Default value
589            pattern_count: stats.total_tracked_blocks,
590        }
591    }
592
593    fn select_adaptive_algorithm(
594        &self,
595        density: f64,
596        pattern_analysis: &SparsityPatternAnalysis,
597        access_info: &AccessPatternInfo,
598    ) -> CompressionAlgorithm {
599        // Decision tree for algorithm selection
600        if density > 0.1 {
601            // Dense matrices benefit from general compression
602            CompressionAlgorithm::LZ77
603        } else if pattern_analysis.sequential_patterns
604            > pattern_analysis.avg_nnz_per_row as usize * 10
605        {
606            // High sequential patterns favor RLE
607            CompressionAlgorithm::RLE
608        } else if pattern_analysis.clustering_factor < 2.0 {
609            // Low clustering suggests delta encoding
610            CompressionAlgorithm::Delta
611        } else if access_info.avg_temporal_locality > 0.8 {
612            // High temporal locality suggests sparse-optimized
613            CompressionAlgorithm::SparseOptimized
614        } else {
615            // Default to Huffman for general case
616            CompressionAlgorithm::Huffman
617        }
618    }
619
620    fn determine_optimal_block_size(&self, rows: usize, nnz: usize, density: f64) -> usize {
621        let base_block_size = self.config.block_size;
622
623        // Adjust block size based on matrix characteristics
624        let size_factor = if rows > 1_000_000 {
625            2.0 // Larger blocks for large matrices
626        } else if rows < 10_000 {
627            0.5 // Smaller blocks for small matrices
628        } else {
629            1.0
630        };
631
632        let density_factor = if density > 0.1 {
633            1.5 // Larger blocks for denser matrices
634        } else {
635            1.0
636        };
637
638        let nnz_factor = if nnz > 10_000_000 {
639            1.5 // Larger blocks for many non-zeros
640        } else {
641            1.0
642        };
643
644        let optimal_size =
645            (base_block_size as f64 * size_factor * density_factor * nnz_factor) as usize;
646        optimal_size.clamp(4096, 16 * 1024 * 1024) // 4KB to 16MB range
647    }
648
649    fn predict_compression_ratio(
650        &self,
651        algorithm: CompressionAlgorithm,
652        density: f64,
653        pattern_analysis: &SparsityPatternAnalysis,
654    ) -> f64 {
655        let base_ratio = algorithm.expected_compression_ratio();
656
657        // Adjust based on matrix characteristics
658        let adjustment = if pattern_analysis.bandwidth > 100000 {
659            0.8 // Lower compression for high bandwidth
660        } else if pattern_analysis.bandwidth < 100 {
661            1.2 // Higher compression for low bandwidth
662        } else {
663            1.0
664        };
665
666        base_ratio * adjustment
667    }
668
669    fn handle_out_of_core_storage(&mut self, blocks: &[CompressedBlock]) -> SparseResult<()> {
670        if let Some(ref mut manager) = self.out_of_core_manager {
671            for block in blocks {
672                if block.compressed_data.len() > self.config.block_size {
673                    // Store large blocks out-of-core
674                    manager.write_block_to_disk(block)?;
675                }
676            }
677        }
678        Ok(())
679    }
680
681    fn get_cached_block(&self, block_id: &BlockId) -> SparseResult<Option<Vec<u8>>> {
682        if let Ok(mut cache) = self.block_cache.lock() {
683            Ok(cache
684                .get(block_id)
685                .map(|cached_block| cached_block.data.clone()))
686        } else {
687            Ok(None)
688        }
689    }
690
691    fn cache_block(&self, block_id: &BlockId, data: &[u8]) -> SparseResult<()> {
692        use super::cache::CachedBlock;
693        if let Ok(mut cache) = self.block_cache.lock() {
694            let cached_block = CachedBlock::new(data.to_vec(), false, 1);
695            cache.insert(block_id.clone(), cached_block);
696        }
697        Ok(())
698    }
699
700    fn record_matrix_access(&self, matrix_id: u64, access_type: AccessType) {
701        if let Ok(mut tracker) = self.access_tracker.lock() {
702            let block_id = BlockId::new(matrix_id, 0, 0); // Default block position
703            tracker.record_access(block_id, access_type, 1024); // Default size
704        }
705    }
706
707    fn decompress_block(
708        &mut self,
709        block: &CompressedBlock,
710        algorithm: CompressionAlgorithm,
711    ) -> SparseResult<Vec<u8>> {
712        self.compression_engine
713            .decompress(&block.compressed_data, algorithm, block.original_size)
714    }
715
716    fn create_uncompressed_matrix<T>(
717        &self,
718        matrix_id: u64,
719        rows: usize,
720        indptr: &[usize],
721        indices: &[usize],
722        data: &[T],
723    ) -> SparseResult<CompressedMatrix<T>>
724    where
725        T: Float + SparseElement + NumAssign + Send + Sync + Copy + std::fmt::Debug,
726    {
727        let mut matrix = CompressedMatrix::new(
728            matrix_id,
729            rows,
730            if !indptr.is_empty() {
731                *indices.iter().max().unwrap_or(&0) + 1
732            } else {
733                0
734            },
735            CompressionAlgorithm::None,
736            self.config.block_size,
737        );
738
739        // Create uncompressed blocks
740        let indptr_data = self.serialize_indptr(indptr)?;
741        let indices_data = self.serialize_indices(indices)?;
742        let data_data = self.serialize_data(data)?;
743
744        matrix.add_block(CompressedBlock::new(
745            BlockId::new(matrix_id, 0, 0),
746            BlockType::IndPtr,
747            indptr_data.clone(),
748            indptr_data.len(),
749            0,
750        ));
751
752        matrix.add_block(CompressedBlock::new(
753            BlockId::new(matrix_id, 0, 1),
754            BlockType::Indices,
755            indices_data.clone(),
756            indices_data.len(),
757            0,
758        ));
759
760        matrix.add_block(CompressedBlock::new(
761            BlockId::new(matrix_id, 0, 2),
762            BlockType::Data,
763            data_data.clone(),
764            data_data.len(),
765            0,
766        ));
767
768        Ok(matrix)
769    }
770
771    fn update_compression_stats(
772        &self,
773        original_size: usize,
774        blocks: &[CompressedBlock],
775        compression_time: f64,
776    ) {
777        if let Ok(mut stats) = self.compression_stats.lock() {
778            let compressed_size = blocks
779                .iter()
780                .map(|b| b.compressed_data.len())
781                .sum::<usize>();
782
783            stats.update_compression(original_size, compressed_size, compression_time);
784        }
785    }
786
787    fn get_cache_stats(&self) -> CacheStats {
788        if let Ok(cache) = self.block_cache.lock() {
789            let stats = cache.get_stats();
790            CacheStats {
791                hits: stats.hit_count,
792                misses: stats.miss_count,
793                hit_ratio: if stats.hit_count + stats.miss_count > 0 {
794                    stats.hit_count as f64 / (stats.hit_count + stats.miss_count) as f64
795                } else {
796                    0.0
797                },
798            }
799        } else {
800            CacheStats {
801                hits: 0,
802                misses: 0,
803                hit_ratio: 0.0,
804            }
805        }
806    }
807
808    // Serialization methods
809    fn serialize_indptr(&self, indptr: &[usize]) -> SparseResult<Vec<u8>> {
810        let mut serialized = Vec::new();
811        serialized.extend_from_slice(&indptr.len().to_le_bytes());
812        for &value in indptr {
813            serialized.extend_from_slice(&value.to_le_bytes());
814        }
815        Ok(serialized)
816    }
817
818    fn serialize_indices(&self, indices: &[usize]) -> SparseResult<Vec<u8>> {
819        let mut serialized = Vec::new();
820        serialized.extend_from_slice(&indices.len().to_le_bytes());
821        for &value in indices {
822            serialized.extend_from_slice(&value.to_le_bytes());
823        }
824        Ok(serialized)
825    }
826
827    fn serialize_data<T>(&self, data: &[T]) -> SparseResult<Vec<u8>>
828    where
829        T: Float + SparseElement + NumAssign + Send + Sync + Copy,
830    {
831        let mut serialized = Vec::new();
832        serialized.extend_from_slice(&data.len().to_le_bytes());
833        for &value in data {
834            let bytes = value.to_f64().unwrap_or(0.0).to_le_bytes();
835            serialized.extend_from_slice(&bytes);
836        }
837        Ok(serialized)
838    }
839
840    // Parsing methods
841    fn parse_indptr_data(&self, data: &[u8]) -> SparseResult<Vec<usize>> {
842        if data.len() < 8 {
843            return Ok(Vec::new());
844        }
845
846        let length = usize::from_le_bytes([
847            data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
848        ]);
849        let mut indptr = Vec::with_capacity(length);
850
851        let mut offset = 8;
852        for _ in 0..length {
853            if offset + 8 <= data.len() {
854                let value = usize::from_le_bytes([
855                    data[offset],
856                    data[offset + 1],
857                    data[offset + 2],
858                    data[offset + 3],
859                    data[offset + 4],
860                    data[offset + 5],
861                    data[offset + 6],
862                    data[offset + 7],
863                ]);
864                indptr.push(value);
865                offset += 8;
866            }
867        }
868
869        Ok(indptr)
870    }
871
872    fn parse_indices_data(&self, data: &[u8]) -> SparseResult<Vec<usize>> {
873        self.parse_indptr_data(data) // Same format
874    }
875
876    fn parse_data_values<T>(&self, data: &[u8]) -> SparseResult<Vec<T>>
877    where
878        T: Float
879            + SparseElement
880            + NumAssign
881            + Send
882            + Sync
883            + Copy
884            + scirs2_core::numeric::FromPrimitive,
885    {
886        if data.len() < 8 {
887            return Ok(Vec::new());
888        }
889
890        let length = usize::from_le_bytes([
891            data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
892        ]);
893        let mut values = Vec::with_capacity(length);
894
895        let mut offset = 8;
896        for _ in 0..length {
897            if offset + 8 <= data.len() {
898                let value_f64 = f64::from_le_bytes([
899                    data[offset],
900                    data[offset + 1],
901                    data[offset + 2],
902                    data[offset + 3],
903                    data[offset + 4],
904                    data[offset + 5],
905                    data[offset + 6],
906                    data[offset + 7],
907                ]);
908                if let Some(value) = T::from_f64(value_f64) {
909                    values.push(value);
910                }
911                offset += 8;
912            }
913        }
914
915        Ok(values)
916    }
917
918    fn parse_combined_data<T>(&self, _data: &[u8]) -> SparseResult<(Vec<usize>, Vec<usize>, Vec<T>)>
919    where
920        T: Float + SparseElement + NumAssign + Send + Sync + Copy,
921    {
922        // Placeholder for combined data parsing
923        Ok((Vec::new(), Vec::new(), Vec::new()))
924    }
925}
926
927#[cfg(test)]
928mod tests {
929    use super::*;
930    use tempfile::TempDir;
931
932    #[test]
933    fn test_compressor_creation() {
934        let config = AdaptiveCompressionConfig::default();
935        let compressor = AdaptiveMemoryCompressor::new(config);
936        assert!(compressor.is_ok());
937    }
938
939    #[test]
940    fn test_matrix_compression_roundtrip() {
941        let config = AdaptiveCompressionConfig {
942            compression_algorithm: CompressionAlgorithm::None,
943            out_of_core: false,
944            memory_mapping: false,
945            ..Default::default()
946        };
947        let mut compressor = AdaptiveMemoryCompressor::new(config).expect("Operation failed");
948
949        let indptr = vec![0, 2, 3];
950        let indices = vec![0, 1, 1];
951        let data = vec![1.0, 2.0, 3.0];
952
953        let compressed = compressor
954            .compress_matrix(1, 2, &indptr, &indices, &data)
955            .expect("Operation failed");
956
957        let (decompressed_indptr, decompressed_indices, decompressed_data) = compressor
958            .decompress_matrix(&compressed)
959            .expect("Operation failed");
960
961        assert_eq!(decompressed_indptr, indptr);
962        assert_eq!(decompressed_indices, indices);
963        assert_eq!(decompressed_data.len(), data.len());
964    }
965
966    #[test]
967    fn test_memory_stats() {
968        let config = AdaptiveCompressionConfig::default();
969        let compressor = AdaptiveMemoryCompressor::new(config).expect("Operation failed");
970        let stats = compressor.get_memory_stats();
971
972        assert_eq!(stats.current_memory_usage, 0);
973        assert!(stats.memory_usage_ratio >= 0.0);
974    }
975
976    #[test]
977    fn test_access_pattern_optimization() {
978        let config = AdaptiveCompressionConfig::default();
979        let mut compressor = AdaptiveMemoryCompressor::new(config).expect("Operation failed");
980
981        // Test sequential optimization
982        compressor.optimize_for_sequential_access();
983
984        // Test random optimization
985        compressor.optimize_for_random_access();
986
987        // Should not panic (implicit in test succeeding)
988    }
989
990    #[test]
991    fn test_compressor_reset() {
992        let temp_dir = TempDir::new().expect("Operation failed");
993        let config = AdaptiveCompressionConfig {
994            temp_directory: temp_dir
995                .path()
996                .to_str()
997                .expect("Operation failed")
998                .to_string(),
999            ..Default::default()
1000        };
1001        let mut compressor = AdaptiveMemoryCompressor::new(config).expect("Operation failed");
1002
1003        // Add some data
1004        let indptr = vec![0, 1];
1005        let indices = vec![0];
1006        let data = vec![1.0];
1007
1008        let _ = compressor.compress_matrix(1, 1, &indptr, &indices, &data);
1009
1010        // Reset should work
1011        let result = compressor.reset();
1012        assert!(result.is_ok());
1013
1014        let stats = compressor.get_memory_stats();
1015        assert_eq!(stats.current_memory_usage, 0);
1016    }
1017}